104 lines
5.5 KiB
Markdown
104 lines
5.5 KiB
Markdown
# 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):
|
||
1. Install ModbusPal, load the per-device `.xmpp` profile from
|
||
`tests/Driver.Modbus.IntegrationTests/ModbusPal/` (TBD directory).
|
||
2. Start the simulator listening on `localhost:502` (or override via
|
||
`MODBUS_SIM_ENDPOINT` env var).
|
||
3. `dotnet test` the 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` (or `ABCD`, 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.ProbeAddress` default 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 `BadNotWritable` rather than `BadInternalError`.
|
||
|
||
_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_Float32` is easier to
|
||
diagnose on failure than a generic `Float32_roundtrip`. The `DL205_` 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
|
||
a `SkipReason` string, and have each test call `Assert.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-memory `FakeTransport` from 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.
|