fix(abcip): thread CIP template-instance-id so discovered-UDT expansion works in production (review)

This commit is contained in:
Joseph Doherty
2026-06-17 20:30:12 -04:00
parent fdd6b332fe
commit 4a7b0fde7b
6 changed files with 239 additions and 20 deletions
@@ -78,12 +78,19 @@ public static class CipSymbolObjectDecoder
var (programScope, simpleName) = SplitProgramScope(name);
var dataType = isStruct ? AbCipDataType.Structure : MapTypeCode((byte)typeCode);
// For a struct symbol the lower 12 bits (typeCode) are NOT a primitive CIP type code —
// they are the CIP template instance id (per the bit-15 struct-flag remarks above). Surface
// it so the driver can fetch the UDT's Template Object during discovery. Atomic symbols
// carry no template id.
var templateInstanceId = isStruct ? (uint?)typeCode : null;
yield return new AbCipDiscoveredTag(
Name: simpleName,
ProgramScope: programScope,
DataType: dataType ?? AbCipDataType.Structure, // unknown type code → treat as opaque
ReadOnly: false, // Symbol Object doesn't carry write-protection bits; lift via AccessControl Object later
IsSystemTag: isSystem);
IsSystemTag: isSystem,
TemplateInstanceId: templateInstanceId);
_ = instanceId; // retained in the wire format for diagnostics; not surfaced to the driver today
}
@@ -45,6 +45,9 @@ public interface IAbCipTagEnumeratorFactory
/// reported a 1-D array (even of length 1). Surfaces the tag as an OPC UA array node at
/// discovery; <see cref="ElementCount"/> alone can't distinguish a scalar from a 1-element
/// array.</param>
/// <param name="TemplateInstanceId">The CIP template instance id for a discovered Structure tag
/// (<c>null</c> for non-structs / when unknown). Used to fetch the UDT's Template Object shape
/// during discovery so the struct can be fanned out into addressable member variables.</param>
public sealed record AbCipDiscoveredTag(
string Name,
string? ProgramScope,
@@ -52,7 +55,8 @@ public sealed record AbCipDiscoveredTag(
bool ReadOnly,
bool IsSystemTag = false,
int ElementCount = 1,
bool IsArray = false);
bool IsArray = false,
uint? TemplateInstanceId = null);
/// <summary>
/// No-op enumerator returning an empty sequence. Useful for tests + strict-config
@@ -45,6 +45,9 @@ internal sealed class LibplctagTagEnumerator : IAbCipTagEnumerator
await _tag.ReadAsync(cancellationToken).ConfigureAwait(false);
var buffer = _tag.GetBuffer();
// The decoder already surfaces each struct tag's CIP template instance id on
// AbCipDiscoveredTag.TemplateInstanceId (from the lower 12 bits of the symbol type), so
// re-yielding the decoded records as-is carries it through to DiscoverAsync's UDT fan-out.
foreach (var tag in CipSymbolObjectDecoder.Decode(buffer))
yield return tag;
}