Files
lmxopcua/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/ModbusPal/Standard.xmpp
Joseph Doherty 02a0e8efd1 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>
2026-04-18 20:05:20 -04:00

167 lines
7.3 KiB
XML

<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE modbuspal_project SYSTEM "modbuspal.dtd">
<!--
Standard.xmpp — generic Modbus TCP server.
Slave id 1 on TCP port 502. Holding registers 0..31 seeded with their own
address as value (so HR[0]=0, HR[5]=5, easy mental map for diagnostics).
Coils 0..31 alternate true/false. One auto-incrementing register at HR[100]
bound to the "Tick" automation (1 Hz, wraps 0..65535) so subscribe-and-receive
integration tests have something that actually changes without a write.
Loaded via the ModbusPal GUI: File > Load > pick this file > Run.
The integration test fixture (MODBUS_SIM_ENDPOINT, default localhost:502)
connects on TCP. Tests filter by Trait=Standard.
Limitations of ModbusPal 1.6b that shape this profile:
- Only holding_registers + coils sections (no input_registers, no
discrete_inputs in the official 1.6b build). Tests for FC04 / FC02
wait until we switch to a fork or pymodbus.
- Per-register elements only — sparse maps fine, range form not
supported in the serialized format (the GUI lets you Add range,
but the .xmpp file expands them).
- Listens on all interfaces (no bind-address attribute).
-->
<modbuspal_project>
<!-- Monotonic id generator ModbusPal uses to internally name automations / slaves. -->
<idgen value="100"/>
<!-- TCP listen on 502 (standard Modbus port). Override via ModbusPal GUI if conflicting. -->
<links selected="TCP/IP">
<tcpip port="502"/>
<serial com="COM 1" baudrate="9600" parity="even" stops="1">
<flowcontrol xonxoff="false" rtscts="false"/>
</serial>
</links>
<!--
Tick automation: 0..65535 over 60 seconds, looping. Bound to HR[100]
below so each second the register climbs by ~1092. Slow enough that
a 250ms-poll integration test sees discrete jumps; fast enough that
a 5s subscribe test sees several change notifications.
-->
<automation name="Tick" step="1.0" loop="true" init="0.0">
<generator class="LinearGenerator" duration="60.0">
<start value="0.0" relative="false"/>
<end value="65535.0" relative="false"/>
</generator>
</automation>
<slave id="1" enabled="true" name="StandardSim" implementation="modbus">
<holding_registers>
<!-- HR[0..31] = address-as-value. Easy mental map for diagnostics + read tests. -->
<register address="0" value="0"/>
<register address="1" value="1"/>
<register address="2" value="2"/>
<register address="3" value="3"/>
<register address="4" value="4"/>
<register address="5" value="5"/>
<register address="6" value="6"/>
<register address="7" value="7"/>
<register address="8" value="8"/>
<register address="9" value="9"/>
<register address="10" value="10"/>
<register address="11" value="11"/>
<register address="12" value="12"/>
<register address="13" value="13"/>
<register address="14" value="14"/>
<register address="15" value="15"/>
<register address="16" value="16"/>
<register address="17" value="17"/>
<register address="18" value="18"/>
<register address="19" value="19"/>
<register address="20" value="20"/>
<register address="21" value="21"/>
<register address="22" value="22"/>
<register address="23" value="23"/>
<register address="24" value="24"/>
<register address="25" value="25"/>
<register address="26" value="26"/>
<register address="27" value="27"/>
<register address="28" value="28"/>
<register address="29" value="29"/>
<register address="30" value="30"/>
<register address="31" value="31"/>
<!-- HR[100] auto-increments via the Tick automation. Subscribe tests
read this and expect to see at least 2 change notifications in 5s. -->
<register address="100" value="0" name="AutoIncrement">
<binding automation="Tick" class="Binding_SINT16" order="0"/>
</register>
<!-- HR[200..209] — scratch range left at 0 for write-roundtrip tests
to mutate freely without touching the address-as-value set above. -->
<register address="200" value="0" name="Scratch0"/>
<register address="201" value="0" name="Scratch1"/>
<register address="202" value="0" name="Scratch2"/>
<register address="203" value="0" name="Scratch3"/>
<register address="204" value="0" name="Scratch4"/>
<register address="205" value="0" name="Scratch5"/>
<register address="206" value="0" name="Scratch6"/>
<register address="207" value="0" name="Scratch7"/>
<register address="208" value="0" name="Scratch8"/>
<register address="209" value="0" name="Scratch9"/>
</holding_registers>
<coils>
<!-- Coils 0..31 alternating. Even = on, odd = off. -->
<coil address="0" value="1"/>
<coil address="1" value="0"/>
<coil address="2" value="1"/>
<coil address="3" value="0"/>
<coil address="4" value="1"/>
<coil address="5" value="0"/>
<coil address="6" value="1"/>
<coil address="7" value="0"/>
<coil address="8" value="1"/>
<coil address="9" value="0"/>
<coil address="10" value="1"/>
<coil address="11" value="0"/>
<coil address="12" value="1"/>
<coil address="13" value="0"/>
<coil address="14" value="1"/>
<coil address="15" value="0"/>
<coil address="16" value="1"/>
<coil address="17" value="0"/>
<coil address="18" value="1"/>
<coil address="19" value="0"/>
<coil address="20" value="1"/>
<coil address="21" value="0"/>
<coil address="22" value="1"/>
<coil address="23" value="0"/>
<coil address="24" value="1"/>
<coil address="25" value="0"/>
<coil address="26" value="1"/>
<coil address="27" value="0"/>
<coil address="28" value="1"/>
<coil address="29" value="0"/>
<coil address="30" value="1"/>
<coil address="31" value="0"/>
<!-- Coils 100..109 — scratch range for write-roundtrip tests. -->
<coil address="100" value="0"/>
<coil address="101" value="0"/>
<coil address="102" value="0"/>
<coil address="103" value="0"/>
<coil address="104" value="0"/>
<coil address="105" value="0"/>
<coil address="106" value="0"/>
<coil address="107" value="0"/>
<coil address="108" value="0"/>
<coil address="109" value="0"/>
</coils>
<tuning>
<!-- Zero artificial reply delay or error rate. Set non-zero in the GUI to
simulate a slow / lossy link without re-authoring the file. -->
<reply_delay min="0" max="0"/>
<error_rates no_reply="0.0"/>
</tuning>
</slave>
</modbuspal_project>