feat(abcip): 1-D array read via libplctag + IsArray discovery

This commit is contained in:
Joseph Doherty
2026-06-16 21:55:20 -04:00
parent a82c22c645
commit f4d5a5ee9c
8 changed files with 408 additions and 15 deletions
@@ -553,7 +553,11 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
var tagPath = AbCipTagPath.TryParse(def.TagPath);
var bitIndex = tagPath?.BitIndex;
var value = runtime.DecodeValue(def.DataType, bitIndex);
// Phase 4c — a 1-D array tag decodes the whole buffer into an element-typed CLR
// array (int[]/float[]/bool[]/string[]…); scalar tags keep the single-value path.
var value = def.ElementCount > 1
? runtime.DecodeArray(def.DataType, def.ElementCount)
: runtime.DecodeValue(def.DataType, bitIndex);
results[fb.OriginalIndex] = new DataValueSnapshot(value, AbCipStatusMapper.Good, now, now);
_health = new DriverHealth(DriverState.Healthy, now, null);
}
@@ -850,7 +854,11 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
?? throw new InvalidOperationException(
$"AbCip tag '{def.Name}' has malformed TagPath '{def.TagPath}'.");
var runtime = _tagFactory.Create(device.BuildCreateParams(parsed.ToLibplctagName(), _options.Timeout));
// Phase 4c — a 1-D array tag (ElementCount > 1) sets libplctag's elem_count so the read
// pulls every element in one CIP transaction; the read path then boxes them into a
// typed CLR array. Scalar tags pass the default count of 1, unchanged.
var runtime = _tagFactory.Create(
device.BuildCreateParams(parsed.ToLibplctagName(), _options.Timeout, def.ElementCount));
try
{
await runtime.InitializeAsync(ct).ConfigureAwait(false);
@@ -945,8 +953,8 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
udtFolder.Variable(member.Name, member.Name, new DriverAttributeInfo(
FullName: memberFullName,
DriverDataType: member.DataType.ToDriverDataType(),
IsArray: false,
ArrayDim: null,
IsArray: member.ElementCount > 1,
ArrayDim: member.ElementCount > 1 ? (uint)member.ElementCount : null,
SecurityClass: member.Writable
? SecurityClassification.Operate
: SecurityClassification.ViewOnly,
@@ -983,8 +991,8 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
discoveredFolder.Variable(fullName, discovered.Name, new DriverAttributeInfo(
FullName: fullName,
DriverDataType: discovered.DataType.ToDriverDataType(),
IsArray: false,
ArrayDim: null,
IsArray: discovered.ElementCount > 1,
ArrayDim: discovered.ElementCount > 1 ? (uint)discovered.ElementCount : null,
SecurityClass: discovered.ReadOnly
? SecurityClassification.ViewOnly
: SecurityClassification.Operate,
@@ -999,8 +1007,8 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
private static DriverAttributeInfo ToAttributeInfo(AbCipTagDefinition tag) => new(
FullName: tag.Name,
DriverDataType: tag.DataType.ToDriverDataType(),
IsArray: false,
ArrayDim: null,
IsArray: tag.ElementCount > 1,
ArrayDim: tag.ElementCount > 1 ? (uint)tag.ElementCount : null,
SecurityClass: (tag.Writable && !tag.SafetyTag)
? SecurityClassification.Operate
: SecurityClassification.ViewOnly,
@@ -1102,8 +1110,10 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
/// </summary>
/// <param name="tagName">The name of the tag to create parameters for.</param>
/// <param name="timeout">The timeout for tag operations.</param>
/// <param name="elementCount">libplctag <c>elem_count</c> — 1 for a scalar tag, the array
/// length for a 1-D array tag (Phase 4c). Coerced to a minimum of 1.</param>
/// <returns>The computed tag creation parameters.</returns>
public AbCipTagCreateParams BuildCreateParams(string tagName, TimeSpan timeout) => new(
public AbCipTagCreateParams BuildCreateParams(string tagName, TimeSpan timeout, int elementCount = 1) => new(
Gateway: ParsedAddress.Gateway,
Port: ParsedAddress.Port,
CipPath: ParsedAddress.CipPath,
@@ -1111,7 +1121,8 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
TagName: tagName,
Timeout: timeout,
AllowPacking: Options.AllowPacking ?? Profile.SupportsRequestPacking,
ConnectionSize: Options.ConnectionSize ?? Profile.DefaultConnectionSize);
ConnectionSize: Options.ConnectionSize ?? Profile.DefaultConnectionSize,
ElementCount: elementCount < 1 ? 1 : elementCount);
/// <summary>Disposes all runtime tag handles and clears the caches.</summary>
public void DisposeHandles()