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>
ModbusPal simulator profiles
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 |
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 |
AutomationDirect DirectLOGIC DL205 / DL260 quirks per 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
- Download ModbusPal 1.6b from
SourceForge —
modbuspal.jar. Requires Java 8+ (Java 17/21 work but emit Swing deprecation warnings). java -jar modbuspal.jarto launch the GUI.- File > Load → pick
Standard.xmpp(orDL205.xmpp). - Click the Run button (top-right of the toolbar) to start serving on TCP 502.
dotnet test tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests— tests auto-skip with a clearSkipReasonif the TCP probe at the configured endpoint fails within 2 seconds (ModbusSimulatorFixture).
Switching between Standard and DL205
Stop the running simulator (toolbar's Stop button), File > Load the other profile, Run.
Environment variables
MODBUS_SIM_ENDPOINT— override the simulator endpoint (host:port). Defaults tolocalhost: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+coilssections in the official build — noinput_registers(FC04) and nodiscrete_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/-hideflags exist only in source-built forks). For CI use, plan to switch to pymodbus'sModbusSimulatorServer(JSON config, scriptable callbacks, first-class headless). - CVE-2018-10832 XXE in
.xmppimport. Don't import.xmppfiles 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.