@@ -20,7 +20,8 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip;
|
||||
public sealed record AbCipTagPath(
|
||||
string? ProgramScope,
|
||||
IReadOnlyList<AbCipTagPathSegment> Segments,
|
||||
int? BitIndex)
|
||||
int? BitIndex,
|
||||
AbCipTagPathSlice? Slice = null)
|
||||
{
|
||||
/// <summary>Rebuild the canonical Logix tag string.</summary>
|
||||
public string ToLibplctagName()
|
||||
@@ -37,10 +38,39 @@ public sealed record AbCipTagPath(
|
||||
if (seg.Subscripts.Count > 0)
|
||||
buf.Append('[').Append(string.Join(",", seg.Subscripts)).Append(']');
|
||||
}
|
||||
if (Slice is not null) buf.Append('[').Append(Slice.Start).Append("..").Append(Slice.End).Append(']');
|
||||
if (BitIndex is not null) buf.Append('.').Append(BitIndex.Value);
|
||||
return buf.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logix-symbol form for issuing a single libplctag tag-create that reads the slice as a
|
||||
/// contiguous buffer — i.e. the bare array name (with the start subscript) without the
|
||||
/// <c>..End</c> suffix. The driver pairs this with <see cref="AbCipTagCreateParams.ElementCount"/>
|
||||
/// = <see cref="AbCipTagPathSlice.Count"/> to issue a single Rockwell array read.
|
||||
/// </summary>
|
||||
public string ToLibplctagSliceArrayName()
|
||||
{
|
||||
if (Slice is null) return ToLibplctagName();
|
||||
var buf = new System.Text.StringBuilder();
|
||||
if (ProgramScope is not null)
|
||||
buf.Append("Program:").Append(ProgramScope).Append('.');
|
||||
|
||||
for (var i = 0; i < Segments.Count; i++)
|
||||
{
|
||||
if (i > 0) buf.Append('.');
|
||||
var seg = Segments[i];
|
||||
buf.Append(seg.Name);
|
||||
if (seg.Subscripts.Count > 0)
|
||||
buf.Append('[').Append(string.Join(",", seg.Subscripts)).Append(']');
|
||||
}
|
||||
// Anchor the read at the slice start; libplctag treats Name=Tag[0] + ElementCount=N as
|
||||
// "read N consecutive elements starting at index 0", which is the exact Rockwell
|
||||
// array-read semantic this PR is wiring up.
|
||||
buf.Append('[').Append(Slice.Start).Append(']');
|
||||
return buf.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Logix-symbolic tag reference. Returns <c>null</c> on a shape the parser
|
||||
/// doesn't support — the driver surfaces that as a config-validation error rather than
|
||||
@@ -91,8 +121,10 @@ public sealed record AbCipTagPath(
|
||||
}
|
||||
|
||||
var segments = new List<AbCipTagPathSegment>(parts.Count);
|
||||
foreach (var part in parts)
|
||||
AbCipTagPathSlice? slice = null;
|
||||
for (var partIdx = 0; partIdx < parts.Count; partIdx++)
|
||||
{
|
||||
var part = parts[partIdx];
|
||||
var bracketIdx = part.IndexOf('[');
|
||||
if (bracketIdx < 0)
|
||||
{
|
||||
@@ -104,6 +136,25 @@ public sealed record AbCipTagPath(
|
||||
var name = part[..bracketIdx];
|
||||
if (!IsValidIdent(name)) return null;
|
||||
var inner = part[(bracketIdx + 1)..^1];
|
||||
|
||||
// Slice syntax `[N..M]` — only allowed on the LAST segment, must not coexist with
|
||||
// multi-dim subscripts, must not be combined with bit-index, and requires M >= N.
|
||||
// Any other shape is rejected so callers see a config-validation error rather than
|
||||
// the driver attempting a best-effort scalar read.
|
||||
if (inner.Contains(".."))
|
||||
{
|
||||
if (partIdx != parts.Count - 1) return null; // slice + sub-element
|
||||
if (bitIndex is not null) return null; // slice + bit index
|
||||
if (inner.Contains(',')) return null; // slice cannot be multi-dim
|
||||
var parts2 = inner.Split("..", 2, StringSplitOptions.None);
|
||||
if (parts2.Length != 2) return null;
|
||||
if (!int.TryParse(parts2[0], out var sliceStart) || sliceStart < 0) return null;
|
||||
if (!int.TryParse(parts2[1], out var sliceEnd) || sliceEnd < sliceStart) return null;
|
||||
slice = new AbCipTagPathSlice(sliceStart, sliceEnd);
|
||||
segments.Add(new AbCipTagPathSegment(name, []));
|
||||
continue;
|
||||
}
|
||||
|
||||
var subs = new List<int>();
|
||||
foreach (var tok in inner.Split(','))
|
||||
{
|
||||
@@ -115,7 +166,7 @@ public sealed record AbCipTagPath(
|
||||
}
|
||||
if (segments.Count == 0) return null;
|
||||
|
||||
return new AbCipTagPath(programScope, segments, bitIndex);
|
||||
return new AbCipTagPath(programScope, segments, bitIndex, slice);
|
||||
}
|
||||
|
||||
private static bool IsValidIdent(string s)
|
||||
@@ -130,3 +181,15 @@ public sealed record AbCipTagPath(
|
||||
|
||||
/// <summary>One path segment: a member name plus any numeric subscripts.</summary>
|
||||
public sealed record AbCipTagPathSegment(string Name, IReadOnlyList<int> Subscripts);
|
||||
|
||||
/// <summary>
|
||||
/// Inclusive-on-both-ends array slice carried on the trailing segment of an
|
||||
/// <see cref="AbCipTagPath"/>. <c>Tag[0..15]</c> parses to <c>Start=0, End=15</c>; the
|
||||
/// planner pairs this with libplctag's <c>ElementCount</c> attribute to issue a single
|
||||
/// Rockwell array read covering <c>End - Start + 1</c> elements.
|
||||
/// </summary>
|
||||
public sealed record AbCipTagPathSlice(int Start, int End)
|
||||
{
|
||||
/// <summary>Total element count covered by the slice (inclusive both ends).</summary>
|
||||
public int Count => End - Start + 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user