Merge b640-s7 (S7 live codec coverage + STRING/DT BadOutOfRange #632)

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 16:57:59 -04:00
10 changed files with 376 additions and 13 deletions
@@ -1555,14 +1555,21 @@ public sealed class S7Driver
// ReadScalarBlockAsync read, so WriteBytesAsync writes the whole reserved STRING. A value
// longer than StringLength throws ArgumentException (S7.Net rejects overflow; we do NOT
// silently truncate). A null value encodes as the empty string.
return global::S7.Net.Types.S7String.ToByteArray(Convert.ToString(value) ?? "", tag.StringLength);
// #632: surfaced as S7ValueRangeException (→ BadOutOfRange) like the WSTRING codec — a
// client value error, not a comms fault (the raw ArgumentException used to land in the
// generic catch as BadCommunicationError + Degraded).
try { return global::S7.Net.Types.S7String.ToByteArray(Convert.ToString(value) ?? "", tag.StringLength); }
catch (ArgumentException ex) { throw new S7ValueRangeException($"S7 STRING value does not fit the declared {tag.StringLength}-character field: {ex.Message}"); }
case S7DataType.DateTime:
// S7.Net's DateTime.ToByteArray builds the 8-byte DT BCD block. It validates the
// 1990–2089 DT range and throws ArgumentOutOfRangeException for an out-of-range year —
// we surface that (no silent clamp). Value coerced via Convert.ToDateTime (accepts a
// System.DateTime or a parseable timestamp string).
return global::S7.Net.Types.DateTime.ToByteArray(Convert.ToDateTime(value));
// #632: the range refusal maps to BadOutOfRange (see STRING above).
var dt = Convert.ToDateTime(value);
try { return global::S7.Net.Types.DateTime.ToByteArray(dt); }
catch (ArgumentOutOfRangeException ex) { throw new S7ValueRangeException($"S7 DATE_AND_TIME value {dt:O} is outside the DT range (1990–2089): {ex.Message}"); }
// Writes refuse (S7ValueRangeException → BadOutOfRange) rather than truncate or clamp.
case S7DataType.WString: return S7ValueCodecs.EncodeWString(value, tag.StringLength);