namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip; /// /// PR abcip-1.3 — issues one libplctag tag-create with ElementCount=N per Rockwell /// array-slice tag (Tag[0..N] in ), then decodes the /// contiguous buffer at element stride into N typed values. Mirrors the whole-UDT /// planner pattern (): pure shape — the planner never /// touches the runtime + never reads the PLC, the driver wires the runtime in. /// /// /// Stride is the natural Logix size of the element type (DInt = 4, Real = 4, LInt = 8). /// Bool / String / Structure slices aren't supported here — Logix packs BOOLs into a host /// byte (no fixed stride), STRING members carry a Length+DATA pair that's not a flat array, /// and structure arrays need the CIP Template Object reader (PR-tracked separately). /// /// Output is a single object[] snapshot value containing the N decoded /// elements at indices 0..Count-1. Pairing with one slice tag = one snapshot keeps the /// ReadAsync 1:1 contract (one fullReference -> one snapshot) intact. /// public static class AbCipArrayReadPlanner { /// /// Build the libplctag create-params + decode descriptor for a slice tag. Returns /// null when the slice element type isn't supported under this declaration-only /// decoder (Bool / String / Structure / unrecognised) — the driver falls back to the /// scalar read path so the operator gets a clean per-element result instead. /// public static AbCipArrayReadPlan? TryBuild( AbCipTagDefinition definition, AbCipTagPath parsedPath, AbCipTagCreateParams baseParams) { ArgumentNullException.ThrowIfNull(definition); ArgumentNullException.ThrowIfNull(parsedPath); ArgumentNullException.ThrowIfNull(baseParams); if (parsedPath.Slice is null) return null; if (!TryGetStride(definition.DataType, out var stride)) return null; var slice = parsedPath.Slice; var createParams = baseParams with { TagName = parsedPath.ToLibplctagSliceArrayName(), ElementCount = slice.Count, }; return new AbCipArrayReadPlan(definition.DataType, slice, stride, createParams); } /// /// Decode .Count elements from at /// element stride. Caller has already invoked /// and confirmed == 0. /// public static object?[] Decode(AbCipArrayReadPlan plan, IAbCipTagRuntime runtime) { ArgumentNullException.ThrowIfNull(plan); ArgumentNullException.ThrowIfNull(runtime); var values = new object?[plan.Slice.Count]; for (var i = 0; i < plan.Slice.Count; i++) values[i] = runtime.DecodeValueAt(plan.ElementType, i * plan.Stride, bitIndex: null); return values; } private static bool TryGetStride(AbCipDataType type, out int stride) { switch (type) { case AbCipDataType.SInt: case AbCipDataType.USInt: stride = 1; return true; case AbCipDataType.Int: case AbCipDataType.UInt: stride = 2; return true; case AbCipDataType.DInt: case AbCipDataType.UDInt: case AbCipDataType.Real: case AbCipDataType.Dt: stride = 4; return true; case AbCipDataType.LInt: case AbCipDataType.ULInt: case AbCipDataType.LReal: stride = 8; return true; default: stride = 0; return false; } } } /// /// Plan output: the libplctag create-params for the single array-read tag plus the /// element-type / stride / slice metadata the decoder needs. /// public sealed record AbCipArrayReadPlan( AbCipDataType ElementType, AbCipTagPathSlice Slice, int Stride, AbCipTagCreateParams CreateParams);