diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/ModbusPal/DL205.xmpp b/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/ModbusPal/DL205.xmpp new file mode 100644 index 0000000..46ac26f --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/ModbusPal/DL205.xmpp @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/ModbusPal/README.md b/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/ModbusPal/README.md index c2fcdbd..6d062eb 100644 --- a/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/ModbusPal/README.md +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/ModbusPal/README.md @@ -1,30 +1,105 @@ # ModbusPal simulator profiles -Drop device-specific `.xmpp` profiles here. The integration tests connect to the -endpoint in `MODBUS_SIM_ENDPOINT` (default `localhost:502`) and expect the -simulator to already be running — tests do not launch ModbusPal themselves, -because its Java GUI + JRE requirement is heavier than the harness is worth. +Two hand-authored `.xmpp` profiles you load into ModbusPal to drive the +integration-test suite without a real PLC: + +| File | What it simulates | Test category | +|---|---|---| +| [`Standard.xmpp`](Standard.xmpp) | Generic Modbus TCP server — HR[0..31] = address-as-value, alternating coils, one auto-incrementing register at HR[100] for subscribe tests, scratch ranges for write-roundtrip tests. | `Trait=Standard` | +| [`DL205.xmpp`](DL205.xmpp) | AutomationDirect DirectLOGIC DL205 / DL260 quirks per [`docs/v2/dl205.md`](../../../docs/v2/dl205.md): low-byte-first string packing, CDAB Float32, BCD numerics, V-memory address markers, Y/C coil mappings. | `Trait=DL205` | + +Both listen on TCP **port 502** (the standard Modbus port — change in the +ModbusPal GUI if a port conflict). Run **only one at a time** since they +share the port. ## Getting started -1. Download ModbusPal from SourceForge (`modbuspal.jar`). +1. Download ModbusPal 1.6b from + [SourceForge](https://sourceforge.net/projects/modbuspal/) — `modbuspal.jar`. + Requires Java 8+ (Java 17/21 work but emit Swing deprecation warnings). 2. `java -jar modbuspal.jar` to launch the GUI. -3. Load a profile from this directory (or configure one manually) and start the - simulator on TCP port 502. -4. `dotnet test tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests` — tests - auto-skip with a clear `SkipReason` if the TCP probe at the configured - endpoint fails within 2 seconds. +3. **File > Load** → pick `Standard.xmpp` (or `DL205.xmpp`). +4. Click the **Run** button (top-right of the toolbar) to start serving on TCP 502. +5. `dotnet test tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests` — + tests auto-skip with a clear `SkipReason` if the TCP probe at the + configured endpoint fails within 2 seconds (`ModbusSimulatorFixture`). -## Profile files +## Switching between Standard and DL205 -- `DL205.xmpp` — _to be added_ — register map reflecting the AutomationDirect - DL205 quirks tracked in `docs/v2/modbus-test-plan.md`. The scaffolded smoke - test in `DL205/DL205SmokeTests.cs` needs holding register 100 writable and - present; a minimal ModbusPal profile with a single holding-register bank at - address 100 is sufficient. +Stop the running simulator (toolbar's **Stop** button), **File > Load** +the other profile, **Run**. ## Environment variables -- `MODBUS_SIM_ENDPOINT` — override the simulator endpoint. Accepts `host:port`; - defaults to `localhost:502`. Useful when pointing the suite at a real PLC on - the bench. +- `MODBUS_SIM_ENDPOINT` — override the simulator endpoint + (`host:port`). Defaults to `localhost:502`. Useful when pointing the suite + at a real PLC on the bench, or running ModbusPal on a non-default port. + +## What's encoded in each profile + +### Standard + +- HR[0..31]: each register's value equals its address. +- HR[100]: bound to a `LinearGenerator` (0..65535 over 60s, looping) — drives + subscribe-and-receive tests. +- HR[200..209]: scratch range for write-roundtrip tests. +- Coils[0..31]: alternating on/off (even=on). +- Coils[100..109]: scratch range. + +### DL205 (per `docs/v2/dl205.md`) + +| HR address | Quirk demonstrated | Raw value | Decoded value | +|---|---|---|---| +| `0` | Register zero is valid (rejects-register-0 rumour disproved) | `-13570` (0xCAFE) | marker | +| `1024` (= V2000 octal) | V-memory octal-to-decimal mapping | `8192` (0x2000) | marker | +| `8448` (= V40400 octal) | V40400 → PDU 0x2100 (NOT register 0) | `16448` (0x4040) | marker | +| `1040..1042` | String "Hello" packed first-char-low-byte | `25928, 27756, 111` | `"Hello"` | +| `1056..1057` | Float32 1.5f in CDAB word order | `0, 16320` | `1.5f` | +| `1072` | Decimal 1234 in BCD encoding | `4660` (0x1234) | `1234` | +| `1280..1407` | 128-register block (FC03 cap = 128 above spec's 125) | address − 1280 | for FC03 cap test | + +| Coil address | Quirk demonstrated | +|---|---| +| `2048` | Y0 maps to coil 2048 (DL260 layout) | +| `3072` | C0 maps to coil 3072 (DL260 layout) | +| `4000..4007` | Scratch C-relay range for write-roundtrip tests | + +## Limitations of ModbusPal 1.6b + +- **Only `holding_registers` + `coils`** sections in the official build — + no `input_registers` (FC04) and no `discrete_inputs` (FC02). DL205's + X-input markers can't be encoded faithfully here. Tests for FC02 / FC04 + wait for a fork (e.g. `SCADA-LTS/ModbusPal`) or a pymodbus rewrite. +- **No semantic bindings** for strings / BCD / arbitrary byte layouts. The + DL205 profile encodes everything as pre-computed raw 16-bit integers + with the math worked out in inline comments. Anything fancier becomes + unreadable above ~50 quirky registers — switch to pymodbus when that + threshold approaches. +- **Project is abandoned** since 1.6b on the official SourceForge listing. + Active forks: `SCADA-LTS/ModbusPal`, `ControlThings-io/modbuspal`, + `mrhenrike/ModbusPalEnhanced`. +- **No headless mode** in the official 1.6b JAR (`-loadFile` / `-hide` + flags exist only in source-built forks). For CI use, plan to switch to + pymodbus's `ModbusSimulatorServer` (JSON config, scriptable callbacks, + first-class headless). +- **CVE-2018-10832** XXE in `.xmpp` import. Don't import `.xmpp` files from + untrusted sources. Profiles in this repo are author-controlled; safe. + +## Alternatives if ModbusPal stops working + +| Tool | Pros | Cons | +|---|---|---| +| **pymodbus `ModbusSimulatorServer`** | Headless-first, JSON config, per-register seeding, custom callbacks for byte-level layouts. Best CI fit. | Python dependency. | +| **diagslave** | Simple, headless, fast. | Flat register banks; no per-address seeding from config; no scripting. | +| **ModbusMechanic** | Headless config-file mode. | Lightly documented. | +| **ModRSsim2** | Windows GUI, CSV import, scripting. | GUI-centric. | + +## File format reference + +ModbusPal `.xmpp` is XML with a DTD reference (`modbuspal.dtd`). Root element +`` with three children: +- `` — internal id counter (start at 100+) +- `` — `` for TCP listen, plus a `` placeholder +- One or more `` containing `` (``), `` (``), `` + +Per-register `` ties a register to a `LinearGenerator` / `RandomGenerator` / `SineGenerator` automation declared at the project level. `order="0"` = LSW, `order="1"` = MSW for 32-bit types. There is **no string binding** and **no byte-swap-within-word** binding. diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/ModbusPal/Standard.xmpp b/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/ModbusPal/Standard.xmpp new file mode 100644 index 0000000..ec89678 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/ModbusPal/Standard.xmpp @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +