feat(modbus-rtu): Modbus RTU-over-TCP transport (Wave 1) #495
Reference in New Issue
Block a user
Delete Branch "feat/modbus-rtu-driver"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds a Modbus RTU-over-TCP transport (RTU CRC-16 framing tunnelled to a serial→Ethernet gateway) to the existing
ModbusDriver, selected by a newTransportconfig field. Purely additive behind the existingIModbusTransportseam — the application protocol (function codes, codecs, planner, coalescing, write path, probe, materialisation, HistoryRead) is byte-for-byte identical across TCP and RTU; only the wire framing differs ([addr][PDU][CRC-16], no MBAP, no TxId). Zero changes to codecs/planner/health.Wave 1 of the driver-expansion program. Plan:
docs/plans/2026-07-24-modbus-rtu-driver.md(11 tasks).What changed
ModbusTransportModeenum (Tcp|RtuOverTcp);ModbusCrc(CRC-16/MODBUS, golden vectors);ModbusRtuFraming(ADU build + FC-aware length-less deframe + response-unit validation + partial-read/truncation handling).ModbusSocketLifecycleextracted fromModbusTcpTransport(behaviour-preserving) and composed by both transports; newModbusRtuOverTcpTransport(single-flight gate — no TxId, per-opCancelAfterdeadline, timeout-vs-caller-cancel distinction,ForTeststream-injection seam).ModbusTransportFactoryswitch (throws on unknown mode);Transportbound on options/DTO/factory with the string-enum guard; driver default closure + Test-Connect probe routed through the factory.Transportselector onModbusDriverForm(Protocol panel).rtu_over_tcppymodbus docker profile (framer=rtu, host:5021, co-runs withstandard) + fixture + read/write integration tests. Confirmed on the wire that pymodbus 3.13 honoursframer=rtuon a TCP server — no stdlib fallback needed.Descoped (by design)
Direct-serial transport (
System.IO.Ports) and Modbus ASCII — RTU buses are reached exclusively via a serial→Ethernet gateway. No per-tag config changes (the existing per-tagUnitIdalready makes a multi-drop bus work).Test Plan
framer=rtupymodbus fixture (10.100.0.35:5021): read HR5→5, write+readback HR200←4242./rungate on docker-dev (rig rebuilt from this branch): AdminUI Transport selector renders + persistsRtuOverTcp; device Test Connect → OK · 23 ms (probe spoke FC03 over RTU framing); deployment06ec1632Sealed (6/6 nodes); Client.CLI readns=2;s=rtu-gate/Device1/HR5→ 5/Good, write HR200=4242 → readback 4242/Good.Follow-ups (non-blocking, tracked)
CrcMismatch/UnitMismatchmembers toDesyncReason(currently all RTU desync modes reportTruncatedFrame; no downstream.Reasonconsumer today).https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.