5.5 KiB
Modbus driver — test plan + device-quirk catalog
The Modbus TCP driver unit tests (PRs 21–24) cover the protocol surface against an in-memory fake transport. They validate the codec, state machine, and function-code routing against a textbook Modbus server. That's necessary but not sufficient: real PLC populations disagree with the spec in small, device-specific ways, and a driver that passes textbook tests can still misbehave against actual equipment.
This doc is the harness-and-quirks playbook. It's what gets wired up in the
tests/Driver.Modbus.IntegrationTests project when we ship that (PR 26 candidate).
Harness
Chosen simulator: ModbusPal (Java, scriptable). Rationale:
- Scriptable enough to mimic device-specific behaviors (non-standard register layouts, custom exception codes, intentional response delays).
- Runs locally, no CI dependency. Tests skip when
localhost:502(or the configured simulator endpoint) isn't reachable. - Free + long-maintained — physical PLC bench is unavailable in most dev environments, and renting cloud PLCs isn't worth the per-test cost.
Setup pattern (not yet codified in a script — will land alongside the integration test project):
- Install ModbusPal, load the per-device
.xmppprofile fromtests/Driver.Modbus.IntegrationTests/ModbusPal/(TBD directory). - Start the simulator listening on
localhost:502(or override viaMODBUS_SIM_ENDPOINTenv var). dotnet testthe integration project — tests auto-skip when the endpoint is unreachable, so forgetting to start the simulator doesn't wedge CI.
Per-device quirk catalog
AutomationDirect DL205
First known target device. Quirks to document and cover with named tests (to be filled in when user validates each behavior in ModbusPal with a DL205 profile):
- Word order for 32-bit values: pending — confirm whether DL205 uses ABCD
(Modbus TCP standard) or CDAB (Siemens-style word-swap) for Int32/UInt32/Float32.
Test name:
DL205_Float32_word_order_is_CDAB(orABCD, whichever proves out). - Register-zero access: pending — some DL205 configurations reject FC03 at
register 0 with exception code 02 (illegal data address). If confirmed, the
integration test suite verifies
ModbusProbeOptions.ProbeAddressdefault of 0 triggers the rejection and operators must override; test name:DL205_FC03_at_register_0_returns_IllegalDataAddress. - Coil addressing base: pending — DL205 documentation sometimes uses 1-based coil addresses; verify the driver's zero-based addressing matches the physical PLC without an off-by-one adjustment.
- Maximum registers per FC03: pending — Modbus spec caps at 125; some DL205
models enforce a lower limit (e.g., 64). Test name:
DL205_FC03_beyond_max_registers_returns_IllegalDataValue. - Response framing under sustained load: pending — the driver's
single-flight semaphore assumes the server pairs requests/responses by
transaction id; at least one DL205 firmware revision is reported to drop the
TxId under load. If reproduced in ModbusPal we add a retry + log-and-continue
path to
ModbusTcpTransport. - Exception code on coil write to a protected bit: pending — some DL205
setups protect internal coils; the driver should surface the PLC's exception
PDU as
BadNotWritablerather thanBadInternalError.
User action item: as each quirk is validated in ModbusPal, replace the pending marker with the confirmed behavior and file a named test in the integration suite.
Future devices
One section per device class, same shape as DL205. Quirks that apply across multiple devices (e.g., "all AB PLCs use CDAB") can be noted in the cross-device patterns section below once we have enough data points.
Cross-device patterns
Once multiple device catalogs accumulate, quirks that recur across two or more vendors get promoted into driver defaults or opt-in options:
- (empty — filled in as catalogs grow)
Test conventions
- One named test per quirk.
DL205_word_order_is_CDAB_for_Float32is easier to diagnose on failure than a genericFloat32_roundtrip. TheDL205_prefix makes filtering by device class trivial (--filter "DisplayName~DL205"). - Skip with a clear SkipReason. Follow the pattern from
GalaxyRepositoryLiveSmokeTests: check reachability in the fixture, capture aSkipReasonstring, and have each test callAssert.Skip(SkipReason)when it's set. Don't throw — skipped tests read cleanly in CI logs. - Use the real
ModbusTcpTransport. Integration tests exercise the wire protocol end-to-end. The in-memoryFakeTransportfrom the unit test suite is deliberately not used here — its value is speed + determinism, which doesn't help reproduce device-specific issues. - Don't depend on ModbusPal state between tests. Each test resets the simulator's register bank or uses a unique address range. Avoid relying on "previous test left value at register 10" setups that flake when tests run in parallel or re-order.
Next concrete PRs
- PR 26 — Integration test project + DL205 profile scaffold: creates
tests/Driver.Modbus.IntegrationTests, imports the ModbusPal profile (or generates it from JSON), adds the fixture with skip-when-unreachable, plus one smoke test that reads a register. No DL205-specific assertions yet — that waits for the user to validate each quirk. - PR 27+: one PR per confirmed DL205 quirk, landing the named test + any driver-side adjustment (e.g., retry on dropped TxId) needed to pass it.