using System.Buffers.Binary; using System.Collections; namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip; /// /// Encodes a whole-array write (review 07 U10): count elements at the element type's wire width, /// little-endian, exactly the layout a Logix atomic array holds and the read path decodes. Shared by both /// runtimes so the bytes on the wire are transport-independent. /// /// /// Element values CONVERT, they are not exact-matched. ScadaBridge (and any OPC UA client that /// round-trips what it read) writes CLR lists such as int[] to an INT array — the read path itself /// publishes INT/SINT elements as int — so each element goes through the same the /// scalar path uses. A value that does not fit is an (BadOutOfRange) and one that /// cannot convert a / (BadTypeMismatch), all /// thrown before any byte is sent. /// BOOL arrays (bit-packed DWORDs — a byte-per-element encode would clobber neighbour bits), STRING arrays /// (structures) and structure arrays are not encodable here; the driver refuses them BadNotSupported /// before a runtime exists. /// internal static class AbCipArrayCodec { /// Wire width of one element of an encodable array type, or null when the type is not encodable. /// The element type. /// The element width in bytes, or null. public static int? ElementWidth(AbCipDataType type) => type switch { AbCipDataType.SInt or AbCipDataType.USInt => 1, AbCipDataType.Int or AbCipDataType.UInt => 2, AbCipDataType.DInt or AbCipDataType.UDInt or AbCipDataType.Real or AbCipDataType.Dt => 4, AbCipDataType.LInt or AbCipDataType.ULInt or AbCipDataType.LReal => 8, _ => null, }; /// /// The elements of an array write value, or null when the value is not a list (a scalar, a string, null). /// Any non-string is accepted — int[], short[], List<object> … /// /// The client's write value. /// The elements, or null. public static object?[]? Elements(object? value) => value switch { null or string => null, IEnumerable list => [.. list.Cast()], _ => null, }; /// Encodes as elements. /// The element type (must have an ). /// The element values; their count is the element count. /// The little-endian wire bytes, values.Count × ElementWidth(type) long. /// The type is not encodable. public static byte[] Encode(AbCipDataType type, IReadOnlyList values) { var width = ElementWidth(type) ?? throw new NotSupportedException($"Array writes of {type} elements are not supported."); var buffer = new byte[values.Count * width]; for (var i = 0; i < values.Count; i++) { var v = values[i]; var span = buffer.AsSpan(i * width, width); switch (type) { case AbCipDataType.SInt: span[0] = unchecked((byte)Convert.ToSByte(v)); break; case AbCipDataType.USInt: span[0] = Convert.ToByte(v); break; case AbCipDataType.Int: BinaryPrimitives.WriteInt16LittleEndian(span, Convert.ToInt16(v)); break; case AbCipDataType.UInt: BinaryPrimitives.WriteUInt16LittleEndian(span, Convert.ToUInt16(v)); break; case AbCipDataType.DInt: case AbCipDataType.Dt: BinaryPrimitives.WriteInt32LittleEndian(span, Convert.ToInt32(v)); break; case AbCipDataType.UDInt: BinaryPrimitives.WriteUInt32LittleEndian(span, Convert.ToUInt32(v)); break; case AbCipDataType.LInt: BinaryPrimitives.WriteInt64LittleEndian(span, Convert.ToInt64(v)); break; case AbCipDataType.ULInt: BinaryPrimitives.WriteUInt64LittleEndian(span, Convert.ToUInt64(v)); break; case AbCipDataType.Real: BinaryPrimitives.WriteSingleLittleEndian(span, Convert.ToSingle(v)); break; case AbCipDataType.LReal: BinaryPrimitives.WriteDoubleLittleEndian(span, Convert.ToDouble(v)); break; } } return buffer; } }