Phase 3 PR 42 — ModbusPal simulator profiles for Standard Modbus + DL205/DL260 quirks. Two hand-authored .xmpp profiles in tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/ModbusPal/ that integration tests load via the GUI to drive the suite without a real PLC. Both well-formed XML (verified via PowerShell [xml] cast); both copied to test-output as PreserveNewest content per the existing csproj rule.
Standard.xmpp — generic Modbus TCP server on port 502, slave id 1. HR[0..31] seeded with address-as-value (HR[5]=5 — easy mental map for diagnostics), HR[100] auto-incrementing via a 1Hz LinearGenerator binding (drives subscribe-and-receive integration tests so they have a register that actually changes without a write), HR[200..209] scratch range for write-roundtrip tests, coils 0..31 alternating on/off, coils 100..109 scratch. The Tick automation runs 0..65535 over 60s looping; bound to HR[100] via Binding_SINT16 — slow enough that a 250ms-poll integration test sees discrete jumps, fast enough that a 5s subscribe test sees several change notifications. DL205.xmpp — AutomationDirect DirectLOGIC DL205/DL260 quirk simulator on port 502, slave id 1, modeling the behaviors documented in docs/v2/dl205.md as concrete register values so DL205 integration tests can assert each quirk WITHOUT a live PLC. Per-quirk encoding: V0 marker at HR[0]=0xCAFE proves register 0 is valid (rejects-register-0 rumour disproved); V2000 marker at HR[1024]=0x2000 proves V-memory octal-to-decimal mapping; V40400 marker at HR[8448]=0x4040 proves V40400→PDU 0x2100 (NOT register 0, contrary to the widespread shorthand); 'Hello' string at HR[1040..1042] packed first-char-low-byte (HR[1040]=0x6548 = 'H' lo + 'e' hi, HR[1041]=0x6C6C, HR[1042]=0x006F) — the headline string-byte-order quirk the user flagged; Float32 1.5f at HR[1056..1057] in CDAB word order (low word first: 0, then 0x3FC0); BCD register at HR[1072]=0x1234 representing decimal 1234 in BCD nibbles (NOT binary 0x04D2); 128-register block at HR[1280..1407] for FC03-128-cap testing; Y0 marker at coil 2048, C0 marker at coil 3072, scratch C-coils at 4000..4007 for write tests. Critical limitation flagged inline + in README: ModbusPal 1.6b CANNOT represent the DL205 quirks semantically — it has no string binding, no BCD binding, no arbitrary-byte-layout binding (only SINT16/SINT32/FLOAT32 with word-order). So every DL205 quirk is encoded as a pre-computed raw 16-bit integer with the math worked out in inline comments above each register. Becomes unreadable past ~50 quirky registers; the README's 'alternatives' section recommends switching to pymodbus when that threshold approaches (pymodbus's ModbusSimulatorServer has first-class headless + scriptable callbacks for byte-level layouts). Other ModbusPal 1.6b limitations called out in README: only holding_registers + coils sections in the official build (no input_registers / discrete_inputs — DL260 X-input markers can't be encoded faithfully here, FC02/FC04 tests wait for a fork or pymodbus); abandoned project (last release 1.6b, active forks at SCADA-LTS/ModbusPal, ControlThings-io/modbuspal, mrhenrike/ModbusPalEnhanced); no headless mode in the official JAR (-loadFile / -hide flags only in source-built forks); CVE-2018-10832 XXE on .xmpp import (don't import untrusted profiles — the in-repo ones are author-controlled). README.md updated with: per-profile description tables, getting-started (download jar + java -jar + GUI File>Load>Run), MODBUS_SIM_ENDPOINT env-var override doc, two reference tables documenting which HR / coil address encodes which DL205 quirk + which test name asserts it (the same DL205_<behavior> naming convention from docs/v2/modbus-test-plan.md), 4-row alternatives comparison (pymodbus / diagslave / ModbusMechanic / ModRSsim2) for when ModbusPal can no longer carry the load, and a quick-reference XML format table at the bottom for future-me hand-authoring more profiles. Pure documentation + test-asset PR — no code changes. The integration tests that consume these profiles (the actual DL205_<behavior> facts) land one at a time in PR 43+ as user validates each quirk via ModbusPal on the bench. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
`<modbuspal_project>` with three children:
|
||||
- `<idgen value="N"/>` — internal id counter (start at 100+)
|
||||
- `<links selected="TCP/IP">` — `<tcpip port="502"/>` for TCP listen, plus a `<serial>` placeholder
|
||||
- One or more `<slave id="..." enabled="true" name="..." implementation="modbus">` containing `<holding_registers>` (`<register address="N" value="V"/>`), `<coils>` (`<coil address="N" value="0|1"/>`), `<tuning>`
|
||||
|
||||
Per-register `<binding automation="..." class="Binding_SINT16|SINT32|FLOAT32" order="0|1"/>` 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.
|
||||
|
||||
Reference in New Issue
Block a user