feat(s7): pure narrow-scalar decode from byte spans
This commit is contained in:
@@ -986,6 +986,80 @@ public sealed class S7Driver
|
||||
((uint)block[offset] << 24) | ((uint)block[offset + 1] << 16)
|
||||
| ((uint)block[offset + 2] << 8) | block[offset + 3];
|
||||
|
||||
/// <summary>
|
||||
/// Byte width one narrow scalar (Bit/Byte/Word/DWord) occupies inside a contiguous block —
|
||||
/// the stride a span planner uses to size and slice a group read. Delegates to
|
||||
/// <see cref="ElementByteSize"/> so the scalar and array paths can never disagree about a
|
||||
/// size's width (a Bit costs a whole byte: S7 block reads are byte-granular).
|
||||
/// </summary>
|
||||
/// <param name="addr">Parsed address carrying the access <see cref="S7Size"/>.</param>
|
||||
/// <returns>Byte width: Bit/Byte = 1, Word = 2, DWord = 4.</returns>
|
||||
internal static int NarrowScalarWidth(S7ParsedAddress addr) => ElementByteSize(addr.Size);
|
||||
|
||||
/// <summary>
|
||||
/// Pure decode step — turns the tag's own slice of a contiguous big-endian block into the
|
||||
/// SEMANTIC CLR value declared by its <see cref="S7DataType"/>, boxed as
|
||||
/// <see cref="object"/>. No network I/O and no driver state: this is the span-read twin of
|
||||
/// <see cref="ReinterpretRawValue"/> (which reinterprets the boxed value S7.Net's
|
||||
/// per-tag string <c>ReadAsync</c> returns), and it deliberately mirrors that method's
|
||||
/// sign/width/conversion semantics arm for arm so a tag reads identically whether it was
|
||||
/// served by one wire request of its own or by one block read shared with its neighbours.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <paramref name="slice"/> is positioned at the tag's OWN first byte —
|
||||
/// <c>slice[0]</c> is the tag's first byte, so <see cref="S7ParsedAddress.ByteOffset"/> is
|
||||
/// the caller's business and is never re-applied here. Only the leading
|
||||
/// <see cref="NarrowScalarWidth"/> bytes are consumed; a longer slice (the rest of the
|
||||
/// block) is fine. Wide/structured types (Int64/UInt64/Float64/String/DateTime) and the
|
||||
/// Timer/Counter areas are NOT narrow — they route through
|
||||
/// <see cref="DecodeScalarBlock"/> — and fall into the mismatch arm here so a mis-routed
|
||||
/// tag fails loudly instead of decoding garbage from a truncated slice.
|
||||
/// </remarks>
|
||||
/// <param name="tag">Tag definition carrying the narrow <see cref="S7DataType"/>.</param>
|
||||
/// <param name="addr">Parsed address carrying the access <see cref="S7Size"/> and (for Bool) the bit position.</param>
|
||||
/// <param name="slice">The tag's slice of the block, big-endian, at least <see cref="NarrowScalarWidth"/> bytes long.</param>
|
||||
/// <returns>The decoded value boxed as <see cref="object"/>, with the same box type <see cref="ReinterpretRawValue"/> produces.</returns>
|
||||
internal static object DecodeNarrowScalar(S7TagDefinition tag, S7ParsedAddress addr, ReadOnlySpan<byte> slice)
|
||||
{
|
||||
// Each arm boxes explicitly: a bare switch expression would unify bool/byte/ushort/short/
|
||||
// uint/int/float to a common type and box THAT, mis-typing every narrow tag (the same trap
|
||||
// DecodeScalarBlock documents). The box type is part of the contract — the publish pipeline
|
||||
// and the OPC UA variable's DataType both key off it.
|
||||
switch (tag.DataType, addr.Size)
|
||||
{
|
||||
// DBX / M{n}.{b}: S7.Net's string read returns the already-extracted bool. From a block
|
||||
// we extract it ourselves at the parsed bit position (0-7, guaranteed by the parser).
|
||||
case (S7DataType.Bool, S7Size.Bit):
|
||||
return (object)((slice[0] & (1 << addr.BitOffset)) != 0);
|
||||
|
||||
// DBB: the whole byte, unsigned (S7.Net boxes a byte).
|
||||
case (S7DataType.Byte, S7Size.Byte):
|
||||
return (object)slice[0];
|
||||
|
||||
// DBW: one big-endian word. UInt16 is the raw word; Int16 is the SAME bits reinterpreted
|
||||
// unchecked (0x8000 => -32768), exactly as ReinterpretRawValue casts S7.Net's ushort.
|
||||
case (S7DataType.UInt16, S7Size.Word):
|
||||
return (object)System.Buffers.Binary.BinaryPrimitives.ReadUInt16BigEndian(slice);
|
||||
case (S7DataType.Int16, S7Size.Word):
|
||||
return (object)unchecked((short)System.Buffers.Binary.BinaryPrimitives.ReadUInt16BigEndian(slice));
|
||||
|
||||
// DBD: one big-endian dword. UInt32 raw; Int32 the same bits unchecked; Float32 the same
|
||||
// bits reinterpreted as IEEE-754 single (never a numeric conversion).
|
||||
case (S7DataType.UInt32, S7Size.DWord):
|
||||
return (object)System.Buffers.Binary.BinaryPrimitives.ReadUInt32BigEndian(slice);
|
||||
case (S7DataType.Int32, S7Size.DWord):
|
||||
return (object)unchecked((int)System.Buffers.Binary.BinaryPrimitives.ReadUInt32BigEndian(slice));
|
||||
case (S7DataType.Float32, S7Size.DWord):
|
||||
return (object)BitConverter.UInt32BitsToSingle(
|
||||
System.Buffers.Binary.BinaryPrimitives.ReadUInt32BigEndian(slice));
|
||||
|
||||
default:
|
||||
throw new System.IO.InvalidDataException(
|
||||
$"S7 narrow-span Read type-mismatch: tag '{tag.Name}' declared {tag.DataType} but address " +
|
||||
$"'{tag.Address}' parsed as Size={addr.Size}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pure reinterpret step — converts the boxed value that S7.Net returns (always an
|
||||
/// unsigned type: <c>bool</c>, <c>byte</c>, <c>ushort</c>, <c>uint</c>) into the
|
||||
|
||||
Reference in New Issue
Block a user