diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs index 376bbe4b..9ffd8b19 100644 --- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs +++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipDriver.cs @@ -191,8 +191,10 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery, /// /// The device the struct lives on. /// The discovered struct type name or a nested-member name. - /// Top-level template instance id when known; null for a - /// nested-struct member (which has no fetchable id in the decoded parent shape). + /// Template instance id when known — the top-level UDT's id, or a + /// nested-struct member's decoded from the parent + /// shape; null only when neither a seeded name-keyed shape nor an id can resolve the struct + /// (the caller then degrades to a single Variable rather than a broken fan-out). /// Cancellation token for any live template read. private async Task ResolveDiscoveredUdtShapeAsync( string deviceHostAddress, string structName, uint? templateInstanceId, CancellationToken cancellationToken) diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/CipTemplateObjectDecoder.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/CipTemplateObjectDecoder.cs index 1d0b1f3c..1bce8e91 100644 --- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/CipTemplateObjectDecoder.cs +++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip/CipTemplateObjectDecoder.cs @@ -83,6 +83,8 @@ public static class CipTemplateObjectDecoder var offset = (int)BinaryPrimitives.ReadUInt32LittleEndian(buffer.AsSpan(blockOffset + 4)); var isStruct = (info & MemberInfoStructFlag) != 0; + // Scalar path only — Rockwell primitive type codes (0xC1-0xD0) fit a byte. Do NOT use for a + // struct member: a nested template id can exceed a byte (see nestedTemplateId below). var typeCode = (byte)(info & MemberInfoTypeCodeMask); var dataType = isStruct ? AbCipDataType.Structure