feat(s7): STRING/WSTRING/CHAR/WCHAR and DTL/DATE/TOD/TIME/S5TIME codecs, TSAP/connection-type override, negotiated PDU surfaced (#632 #288 #289 #294 #295)

New S7DataType members WString/Char/WChar/Dtl/Date/Tod/Time/S5Time mapped onto existing
DriverDataType (String / DateTime / Int32 ms); writes refuse out-of-range with BadOutOfRange.
Optional localTsap/remoteTsap/connectionType through options, factory JSON, probe and AdminUI form.
NegotiatedPduSize snapshotted at connect and exposed via GetDiagnostics (DriverHealth has no bag).

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 14:31:44 -04:00
parent d898262c4a
commit 34c52defb3
18 changed files with 1430 additions and 31 deletions
@@ -66,7 +66,11 @@ public sealed class S7DriverProbe : IDriverProbe
// The Plc is opened and immediately closed — no reads or writes are performed.
// Use a linked CTS so we can distinguish a real caller cancellation from
// S7netplus's internal socket-read timeout (which also surfaces as OCE).
var plc = new Plc(S7CpuTypeMap.ToS7Net(opts.CpuType), host, port, opts.Rack, opts.Slot);
// An authored TSAP / connection-type override applies to Test Connect too — otherwise the probe
// would succeed (or fail) on a different handshake than the driver will use.
var plc = S7Tsap.Resolve(opts) is { } tsap
? new Plc(host, port, tsap)
: new Plc(S7CpuTypeMap.ToS7Net(opts.CpuType), host, port, opts.Rack, opts.Slot);
plc.ReadTimeout = (int)timeout.TotalMilliseconds;
using var handshakeCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
handshakeCts.CancelAfter(timeout);
@@ -76,7 +80,7 @@ public sealed class S7DriverProbe : IDriverProbe
sw.Stop();
if (plc.IsConnected)
return new(true, $"S7 connected (CPU {opts.CpuType})", sw.Elapsed);
return new(true, $"S7 connected (CPU {opts.CpuType}, PDU {plc.MaxPDUSize} bytes)", sw.Elapsed);
return new(false, $"Reachable at {host}:{port} but S7 handshake failed: not connected", null);
}