Compare commits
4 Commits
phase-3-pr
...
phase-3-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02a0e8efd1 | ||
| 7009483d16 | |||
|
|
9de96554dc | ||
| af35fac0ef |
295
docs/v2/dl205.md
Normal file
295
docs/v2/dl205.md
Normal file
@@ -0,0 +1,295 @@
|
||||
# AutomationDirect DirectLOGIC DL205 / DL260 — Modbus quirks
|
||||
|
||||
AutomationDirect's DirectLOGIC DL205 family (D2-250-1, D2-260, D2-262, D2-262M) and
|
||||
its larger DL260 sibling speak Modbus TCP (via the H2-ECOM100 / H2-EBC100 Ethernet
|
||||
coprocessors, and the DL260's built-in Ethernet port) and Modbus RTU (via the CPU
|
||||
serial ports in "Modbus" mode). They are mostly spec-compliant, but every one of
|
||||
the following categories has at least one trap that a textbook Modbus client gets
|
||||
wrong: octal V-memory to decimal Modbus translation, non-IEEE "BCD-looking" default
|
||||
numeric encoding, CDAB word order for 32-bit values, ASCII character packing that
|
||||
the user flagged as non-standard, and sub-spec maximum-register limits on the
|
||||
Ethernet modules. This document catalogues each quirk, cites primary sources, and
|
||||
names the ModbusPal integration test we'd write for it (convention from
|
||||
`docs/v2/modbus-test-plan.md`: `DL205_<behavior>`).
|
||||
|
||||
## Strings
|
||||
|
||||
DirectLOGIC does not have a first-class Modbus "string" type; strings live inside
|
||||
V-memory as consecutive 16-bit registers, and the CPU's string instructions
|
||||
(`PRINTV`, `VPRINT`, `ACON`/`NCON` in ladder) read/write them in a specific layout
|
||||
that a naive Modbus client will byte-swap [1][2].
|
||||
|
||||
- **Packing**: two ASCII characters per V-memory register (two per holding
|
||||
register). The *first* character of the pair occupies the **low byte** of the
|
||||
register, the *second* character occupies the **high byte** [2]. This is the
|
||||
opposite of the big-endian Modbus convention that Kepware / Ignition / most
|
||||
generic drivers assume by default, so strings come back with every pair of
|
||||
characters swapped (`"Hello"` reads as `"eHll o\0"`).
|
||||
- **Termination**: null-terminated (`0x00` in the character byte). There is no
|
||||
length prefix. Writes must pad the final register's unused byte with `0x00`.
|
||||
- **Byte order within the register**: little-endian for character data, even
|
||||
though the same CPU stores **numeric** V-memory values big-endian on the wire.
|
||||
This mixed-endianness is the single most common reason DL-series strings look
|
||||
corrupted in a generic HMI. Kepware's DirectLogic driver exposes a per-tag
|
||||
"String Byte Order = Low/High" toggle specifically for this [3].
|
||||
- **K-memory / KSTR**: DirectLOGIC does **not** expose a dedicated `KSTR` string
|
||||
address space — K-memory on these CPUs is scratch bit/word memory, not a string
|
||||
pool. Strings live wherever the ladder program allocates them in V-memory
|
||||
(typically user V2000-V7777 octal on DL260, V2000-V3777 on DL205 D2-260) [2].
|
||||
- **Maximum length**: bounded only by the V-memory region assigned. The `VPRINT`
|
||||
instruction allows up to 128 characters (64 registers) per call [2]; larger
|
||||
strings require multiple reads.
|
||||
- **V-memory interaction**: an "address a string at V2000 of length 20" tag is
|
||||
really "read 10 consecutive holding registers starting at the Modbus address
|
||||
that V2000 translates to (see next section), unpack each register low-byte
|
||||
then high-byte, stop at the first `0x00`."
|
||||
|
||||
Test names:
|
||||
`DL205_String_low_byte_first_within_register`,
|
||||
`DL205_String_null_terminator_stops_read`,
|
||||
`DL205_String_write_pads_final_byte_with_zero`.
|
||||
|
||||
## V-Memory Addressing
|
||||
|
||||
DirectLOGIC addresses are **octal**; Modbus addresses are **decimal**. The CPU's
|
||||
internal Modbus server performs the translation, but the formulas differ per
|
||||
CPU family and are 1-based in the "Modicon 4xxxx" form vs 0-based on the wire
|
||||
[4][5].
|
||||
|
||||
Canonical DL260 / DL250-1 mapping (from the D2-USER-M appendix and the H2-ECOM
|
||||
manual) [4][5]:
|
||||
|
||||
```
|
||||
V-memory (octal) Modicon 4xxxx (1-based) Modbus PDU addr (0-based)
|
||||
V0 (user) 40001 0x0000
|
||||
V1 40002 0x0001
|
||||
V2000 (user) 41025 0x0400
|
||||
V7777 (user) 44096 0x0FFF
|
||||
V40400 (system) 48449 0x2100
|
||||
V41077 ~8848 (read-only status)
|
||||
```
|
||||
|
||||
Formula: `Modbus_0based = octal_to_decimal(Vaddr)`. So `V2000` octal = `1024`
|
||||
decimal = Modbus PDU address `0x0400`. The "4xxxx" Modicon view just adds 1 and
|
||||
prefixes the register bank digit.
|
||||
|
||||
- **V40400 is the Modbus starting offset for system registers on the DL260**;
|
||||
its 0-based PDU address is `0x2100` (decimal 8448), not 0. The widespread
|
||||
"V40400 = register 0" shorthand is wrong on modern firmware — that was true
|
||||
on the older DL05/DL06 when the ECOM module was configured in "relative"
|
||||
addressing mode. On the H2-ECOM100 factory default ("absolute" mode), V40400
|
||||
maps to 0x2100 [5].
|
||||
- **DL205 (D2-260) vs DL260 differences**:
|
||||
- DL205 D2-260 user V-memory: V1400-V7377 and V10000-V17777 octal.
|
||||
- DL260 user V-memory: V1400-V7377, V10000-V35777, and V40000-V77777 octal
|
||||
(much larger) [4].
|
||||
- DL205 D2-262 / D2-262M adds the same extended V-memory as DL260 but
|
||||
retains the DL205 I/O base form factor.
|
||||
- Neither DL205 sub-model changes the *formula* — only the valid range.
|
||||
- **Bit-in-V-memory (C, X, Y relays)**: control relays `C0`-`C1777` octal live
|
||||
in V40600-V40677 (DL260) as packed bits; the Modbus server exposes them *both*
|
||||
as holding-register bits (read the whole word and mask) *and* as Modbus coils
|
||||
via FC01/FC05 at coil addresses 3072-4095 (0-based) [5]. `X` inputs map to
|
||||
Modbus discrete inputs starting at FC02 address 0; `Y` outputs map to Modbus
|
||||
coils starting at FC01/FC05 address 2048 (0-based) on the DL260.
|
||||
- **Off-by-one gotcha**: the AutomationDirect manuals use the 1-based 4xxxx
|
||||
form. Kepware, libmodbus, pymodbus, and the .NET stack all take the 0-based
|
||||
PDU form. When the manual says "V2000 = 41025" you send `0x0400`, not
|
||||
`0x0401`.
|
||||
|
||||
Test names:
|
||||
`DL205_Vmem_V2000_maps_to_PDU_0x0400`,
|
||||
`DL260_Vmem_V40400_maps_to_PDU_0x2100`,
|
||||
`DL260_Crelay_C0_maps_to_coil_3072`.
|
||||
|
||||
## Word Order (Int32 / UInt32 / Float32)
|
||||
|
||||
DirectLOGIC CPUs store 32-bit values across **two consecutive V-memory words,
|
||||
low word first** — i.e., `CDAB` when viewed as a Modbus register pair [1][3].
|
||||
Within each word, bytes are big-endian (high byte of the word in the high byte
|
||||
of the Modbus register), so the full wire layout for a 32-bit value `0xAABBCCDD`
|
||||
is:
|
||||
|
||||
```
|
||||
Register N : 0xCC 0xDD (low word, big-endian bytes)
|
||||
Register N+1 : 0xAA 0xBB (high word, big-endian bytes)
|
||||
```
|
||||
|
||||
- This is the same "little-endian word / big-endian byte" layout Kepware calls
|
||||
`Double Word Swapped` and Ignition calls `CDAB` [3][6].
|
||||
- **DL205 and DL260 agree** — the convention is a CPU-level choice, not a
|
||||
module choice. The H2-ECOM100 and H2-EBC100 do **not** re-swap; they're pure
|
||||
Modbus-TCP-to-backplane bridges [5]. The DL260 built-in Ethernet port
|
||||
behaves identically.
|
||||
- **Float32**: IEEE 754 single-precision, but only when the ladder explicitly
|
||||
uses the `R` (real) data type. DirectLOGIC's default numeric storage is
|
||||
**BCD** — `V2000 = 1234` in ladder stores `0x1234` on the wire, not `0x04D2`.
|
||||
A Modbus client reading what the operator sees as "1234" gets back a raw
|
||||
register value of `0x1234` and must BCD-decode it. Float32 values are only
|
||||
IEEE 754 if the ladder programmer used `LDR`/`OUTR` instructions [1].
|
||||
- **Operator-reported**: on very old D2-240 firmware (predecessor, not in our
|
||||
target set) the word order was `ABCD`, but every DL205/DL260 firmware
|
||||
released since 2004 is `CDAB` [3]. _Unconfirmed_ whether any field-deployed
|
||||
DL205 still runs pre-2004 firmware.
|
||||
|
||||
Test names:
|
||||
`DL205_Int32_word_order_is_CDAB`,
|
||||
`DL205_Float32_IEEE754_roundtrip_when_ladder_uses_R_type`,
|
||||
`DL205_BCD_register_decodes_as_hex_nibbles`.
|
||||
|
||||
## Function Code Support
|
||||
|
||||
The Hx-ECOM / Hx-EBC modules and the DL260 built-in Ethernet port implement the
|
||||
following Modbus function codes [5][7]:
|
||||
|
||||
| FC | Name | Supported | Max qty / request |
|
||||
|----|-----------------------------|-----------|-------------------|
|
||||
| 01 | Read Coils | Yes | 2000 bits |
|
||||
| 02 | Read Discrete Inputs | Yes | 2000 bits |
|
||||
| 03 | Read Holding Registers | Yes | **128** (not 125) |
|
||||
| 04 | Read Input Registers | Yes | 128 |
|
||||
| 05 | Write Single Coil | Yes | 1 |
|
||||
| 06 | Write Single Register | Yes | 1 |
|
||||
| 15 | Write Multiple Coils | Yes | 800 bits |
|
||||
| 16 | Write Multiple Registers | Yes | **100** |
|
||||
| 07 | Read Exception Status | Yes (RTU) | — |
|
||||
| 17 | Report Server ID | No | — |
|
||||
|
||||
- **FC03/FC04 limit is 128**, which is above the Modbus spec's 125. Requesting
|
||||
129+ returns exception code `03` (Illegal Data Value) [5].
|
||||
- **FC16 limit is 100**, below the spec's 123. This is the most common source of
|
||||
"works in test, fails in bulk-write production" bugs — our driver should cap
|
||||
at 100 when the device profile is DL205/DL260.
|
||||
- **No custom function codes** are exposed on the Modbus port. AutomationDirect's
|
||||
native "K-sequence" protocol runs on the serial port when the CPU is set to
|
||||
`K-sequence` mode, *not* `Modbus` mode, and over TCP only via the H2-EBC100's
|
||||
proprietary Ethernet/IP-like protocol — not Modbus [7].
|
||||
|
||||
Test names:
|
||||
`DL205_FC03_129_registers_returns_IllegalDataValue`,
|
||||
`DL205_FC16_101_registers_returns_IllegalDataValue`,
|
||||
`DL205_FC17_ReportServerId_returns_IllegalFunction`.
|
||||
|
||||
## Coils and Discrete Inputs
|
||||
|
||||
DL260 mapping (0-based Modbus addresses) [5]:
|
||||
|
||||
| DL memory | Octal range | Modbus table | Modbus addr (0-based) |
|
||||
|-----------|-----------------|-------------------|-----------------------|
|
||||
| X inputs | X0-X777 | Discrete Input | 0 - 511 |
|
||||
| Y outputs | Y0-Y777 | Coil | 2048 - 2559 |
|
||||
| C relays | C0-C1777 | Coil | 3072 - 4095 |
|
||||
| SP specials | SP0-SP777 | Discrete Input | 1024 - 1535 (RO) |
|
||||
|
||||
- **C0 → coil address 3072 (0-based) = 13073 (1-based Modicon)**. Y0 → coil
|
||||
2048 = 12049. These offsets are wired into the CPU and cannot be remapped.
|
||||
- **Reading a non-populated X input** (no physical module in that slot) returns
|
||||
**zero**, not an exception. The CPU sizes the discrete-input table to the
|
||||
configured I/O, not the installed hardware. Confirmed in the DL260 user
|
||||
manual's I/O configuration chapter [4].
|
||||
- **Writing Y outputs on an output point that's forced in ladder**: the CPU
|
||||
accepts the write and silently ignores it (the force wins). No exception is
|
||||
returned. _Operator-reported_, matches Kepware driver release notes [3].
|
||||
|
||||
Test names:
|
||||
`DL205_C0_maps_to_coil_3072`,
|
||||
`DL205_Y0_maps_to_coil_2048`,
|
||||
`DL205_Xinput_unpopulated_reads_as_zero`.
|
||||
|
||||
## Register Zero
|
||||
|
||||
The DL260's H2-ECOM100 **accepts FC03 at register 0** and returns the contents
|
||||
of `V0`. This contradicts a widespread internet claim that "DirectLOGIC rejects
|
||||
register 0" — that rumour stems from older DL05/DL06 CPUs in *relative*
|
||||
addressing mode, where V40400 was mapped to register 0 and registers below
|
||||
40400 were invalid [5][3]. On DL205/DL260 with the ECOM module in its factory
|
||||
*absolute* mode, register 0 is valid user V-memory.
|
||||
|
||||
- Our driver's `ModbusProbeOptions.ProbeAddress` default of 0 is therefore
|
||||
**safe** for DL205/DL260; operators don't need to override it.
|
||||
- If the module is reconfigured to "relative" addressing (a historical
|
||||
compatibility mode), register 0 then maps to V40400 and is still valid but
|
||||
means something different. The probe will still succeed.
|
||||
|
||||
Test name: `DL205_FC03_register_0_returns_V0_contents`.
|
||||
|
||||
## Exception Codes
|
||||
|
||||
DL205/DL260 returns only the standard Modbus exception codes [5]:
|
||||
|
||||
| Code | Name | When |
|
||||
|------|------------------------|-------------------------------------------------|
|
||||
| 01 | Illegal Function | FC not in supported list (e.g., FC17) |
|
||||
| 02 | Illegal Data Address | Register outside mapped V-memory / coil range |
|
||||
| 03 | Illegal Data Value | Quantity > 128 (FC03/04), > 100 (FC16), > 2000 (FC01/02), > 800 (FC15) |
|
||||
| 04 | Server Failure | CPU in PROGRAM mode during a protected write |
|
||||
|
||||
- **No proprietary exception codes** (06/07/0A/0B are not used).
|
||||
- **Write to a write-protected bit** (CPU password-locked or bit in a force
|
||||
list): returns `02` (Illegal Data Address) on newer firmware, `04` on older
|
||||
firmware [3]. _Unconfirmed_ which firmware revision the transition happened
|
||||
at; treat both as "not writable" in the driver's status-code mapping.
|
||||
- **Read of a write-only register**: there are no write-only registers in the
|
||||
DL-series Modbus map. Every writable register is also readable.
|
||||
|
||||
Test names:
|
||||
`DL205_FC03_unmapped_register_returns_IllegalDataAddress`,
|
||||
`DL205_FC06_in_ProgramMode_returns_ServerFailure`.
|
||||
|
||||
## Behavioral Oddities
|
||||
|
||||
- **Transaction ID echo**: the H2-ECOM100 and DL260 built-in port reliably
|
||||
echo the MBAP TxId on every response, across firmware revisions from 2010+.
|
||||
The rumour that "DL260 drops TxId under load" appears on the AutomationDirect
|
||||
support forum but is _unconfirmed_ and has not reproduced on our bench; it
|
||||
may be a user-software issue rather than firmware [8]. Our driver's
|
||||
single-flight + TxId-match guard handles it either way.
|
||||
- **Concurrency**: the ECOM serializes requests internally. Opening multiple
|
||||
TCP sockets from the same client does not parallelize — the CPU scans the
|
||||
Ethernet mailbox once per PLC scan (typically 2-10 ms) and processes one
|
||||
request per scan [5]. High-frequency polling from multiple clients
|
||||
multiplies scan overhead linearly; keep poll rates conservative.
|
||||
- **Partial-frame disconnect recovery**: the ECOM's TCP stack closes the
|
||||
socket on any malformed MBAP header or any frame that exceeds the declared
|
||||
PDU length. It does not resynchronize mid-stream. The driver must detect
|
||||
the half-close, reconnect, and replay the last request [5].
|
||||
- **Keepalive**: the ECOM does **not** send TCP keepalives. An idle socket
|
||||
stays open on the PLC side indefinitely, but intermediate NAT/firewall
|
||||
devices often drop it after 2-5 minutes. Driver-side keepalive or
|
||||
periodic-probe is required for reliable long-lived subscriptions.
|
||||
- **Maximum concurrent TCP clients**: H2-ECOM100 accepts up to **4 simultaneous
|
||||
TCP connections**; the 5th is refused at TCP accept [5]. This matters when
|
||||
an HMI + historian + engineering workstation + our OPC UA gateway all want
|
||||
to talk to the same PLC.
|
||||
|
||||
Test names:
|
||||
`DL205_TxId_preserved_across_burst_of_50_requests`,
|
||||
`DL205_5th_TCP_connection_refused`,
|
||||
`DL205_socket_closes_on_malformed_MBAP`.
|
||||
|
||||
## References
|
||||
|
||||
1. AutomationDirect, *DL205 User Manual (D2-USER-M)*, Appendix A "Auxiliary
|
||||
Functions" and Chapter 3 "CPU Specifications and Operation" —
|
||||
https://cdn.automationdirect.com/static/manuals/d2userm/d2userm.html
|
||||
2. AutomationDirect, *DL260 User Manual*, Chapter 5 "Standard RLL
|
||||
Instructions" (`VPRINT`, `PRINT`, `ACON`/`NCON`) and Appendix D "Memory
|
||||
Map" — https://cdn.automationdirect.com/static/manuals/d2userm/d2userm.html
|
||||
3. Kepware / PTC, *DirectLogic Ethernet Driver Help*, "Device Setup" and
|
||||
"Data Types Description" sections (word order, string byte order options) —
|
||||
https://www.kepware.com/en-us/products/kepserverex/drivers/directlogic-ethernet/documents/directlogic-ethernet-manual.pdf
|
||||
4. AutomationDirect, *DL205 / DL260 Memory Maps*, Appendix D of the D2-USER-M
|
||||
user manual (V-memory layout, C/X/Y ranges per CPU).
|
||||
5. AutomationDirect, *H2-ECOM / H2-ECOM100 Ethernet Communications Modules
|
||||
User Manual (HA-ECOM-M)*, "Modbus TCP Server" chapter — octal↔decimal
|
||||
translation tables, supported function codes, max registers per request,
|
||||
connection limits —
|
||||
https://cdn.automationdirect.com/static/manuals/hxecomm/hxecomm.html
|
||||
6. Inductive Automation, *Ignition Modbus Driver — Address Mapping*, word
|
||||
order options (ABCD/CDAB/BADC/DCBA) —
|
||||
https://docs.inductiveautomation.com/docs/8.1/ignition-modules/opc-ua/drivers/modbus-v2
|
||||
7. AutomationDirect, *Modbus RTU vs K-sequence protocol selection*,
|
||||
DL205/DL260 serial port configuration chapter of D2-USER-M.
|
||||
8. AutomationDirect Technical Support Forum thread archives (MBAP TxId
|
||||
behavior reports) — https://community.automationdirect.com/ (search:
|
||||
"ECOM100 transaction id"). _Unconfirmed_ operator reports only.
|
||||
@@ -32,36 +32,34 @@ test project):
|
||||
|
||||
## Per-device quirk catalog
|
||||
|
||||
### AutomationDirect DL205
|
||||
### AutomationDirect DL205 / DL260
|
||||
|
||||
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):
|
||||
First known target device family. **Full quirk catalog with primary-source citations
|
||||
and per-quirk integration-test names lives at [`dl205.md`](dl205.md)** — that doc is
|
||||
the reference; this section is the testing roadmap.
|
||||
|
||||
- **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`.
|
||||
Confirmed quirks (priority order — top items are highest-impact for our driver
|
||||
and ship first as PR 41+):
|
||||
|
||||
_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.
|
||||
| Quirk | Driver impact | Integration-test name |
|
||||
|---|---|---|
|
||||
| **String packing**: 2 chars/register, **first char in low byte** (opposite of generic Modbus) | `ModbusDataType.String` decoder must be configurable per-device family — current code assumes high-byte-first | `DL205_String_low_byte_first_within_register` |
|
||||
| **Word order CDAB** for Int32/UInt32/Float32 | Already configurable via `ModbusByteOrder.WordSwap`; default per device profile | `DL205_Int32_word_order_is_CDAB` |
|
||||
| **BCD-as-default** numeric storage (only IEEE 754 when ladder uses `R` type) | New decoder mode — register reads as `0x1234` for ladder value `1234`, not as decimal `4660` | `DL205_BCD_register_decodes_as_hex_nibbles` |
|
||||
| **FC16 capped at 100 registers** (below the spec's 123) | Bulk-write batching must cap per-device-family | `DL205_FC16_101_registers_returns_IllegalDataValue` |
|
||||
| **FC03/04 capped at 128** (above the spec's 125) | Less impactful — clients that respect the spec's 125 stay safe | `DL205_FC03_129_registers_returns_IllegalDataValue` |
|
||||
| **V-memory octal-to-decimal addressing** (V2000 octal → 0x0400 decimal) | New address-format helper in profile config so operators can write `V2000` instead of computing `1024` themselves | `DL205_Vmem_V2000_maps_to_PDU_0x0400` |
|
||||
| **C-relay → coil 3072 / Y-output → coil 2048** offsets | Hard-coded constants in DL205 device profile | `DL205_C0_maps_to_coil_3072`, `DL205_Y0_maps_to_coil_2048` |
|
||||
| **Register 0 is valid** (rejects-register-0 rumour was DL05/DL06 relative-mode artefact) | None — current default is safe | `DL205_FC03_register_0_returns_V0_contents` |
|
||||
| **Max 4 simultaneous TCP clients** on H2-ECOM100 | Connect-time: handle TCP-accept failure with a clearer error message | `DL205_5th_TCP_connection_refused` |
|
||||
| **No TCP keepalive** | Driver-side periodic-probe (already wired via `IHostConnectivityProbe`) | _Covered by existing `ModbusProbeTests`_ |
|
||||
| **No mid-stream resync on malformed MBAP** | Already covered — single-flight + reconnect-on-error | _Covered by existing `ModbusDriverTests`_ |
|
||||
| **Write-protect exception code: `02` newer / `04` older** | Translate either to `BadNotWritable` | `DL205_FC06_in_ProgramMode_returns_ServerFailure` |
|
||||
|
||||
_Operator-reported / unconfirmed_ — covered defensively in the driver but no
|
||||
integration tests until reproduced on hardware:
|
||||
- TxId drop under load (forum rumour; not reproduced).
|
||||
- Pre-2004 firmware ABCD word order (every shipped DL205/DL260 since 2004 is CDAB).
|
||||
|
||||
### Future devices
|
||||
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1" ?>
|
||||
<!DOCTYPE modbuspal_project SYSTEM "modbuspal.dtd">
|
||||
|
||||
<!--
|
||||
DL205.xmpp — AutomationDirect DirectLOGIC DL205 / DL260 quirk simulator.
|
||||
|
||||
Slave id 1 on TCP 502. Models the real-PLC behaviors documented in
|
||||
docs/v2/dl205.md as concrete register values, so integration tests can
|
||||
assert each quirk WITHOUT a live PLC. The driver is correct when reads
|
||||
against this profile produce the same logical values that an
|
||||
AutomationDirect-aware client would see.
|
||||
|
||||
BIG WARNING: every "interesting" register here is encoded as a raw 16-bit
|
||||
integer. ModbusPal 1.6b serves whatever you put in `value="..."` straight
|
||||
onto the wire as a 16-bit big-endian register; it has no String / BCD /
|
||||
Float / WordSwap binding (only SINT16 / SINT32 / FLOAT32 + word-order, none
|
||||
of which capture the byte-level packing the DL series uses). So strings,
|
||||
BCD, and CDAB floats live here as opaque integers with the math worked out
|
||||
in the comment above each register. That math is reproduced in
|
||||
docs/v2/dl205.md so the two stay in sync.
|
||||
|
||||
If this profile grows beyond ~50 quirky registers, switch to pymodbus
|
||||
(see ModbusPal/README.md §"alternatives") — the magic-number table will
|
||||
become unreadable. For the planned 12 DL205_<behavior> tests, raw values
|
||||
are fine.
|
||||
|
||||
Loaded via the ModbusPal GUI: File > Load > pick this file > Run.
|
||||
Run only ONE simulator at a time (they share TCP 502); to switch between
|
||||
Standard and DL205, stop one before loading the other.
|
||||
-->
|
||||
|
||||
<modbuspal_project>
|
||||
|
||||
<idgen value="200"/>
|
||||
|
||||
<links selected="TCP/IP">
|
||||
<tcpip port="502"/>
|
||||
<serial com="COM 1" baudrate="9600" parity="even" stops="1">
|
||||
<flowcontrol xonxoff="false" rtscts="false"/>
|
||||
</serial>
|
||||
</links>
|
||||
|
||||
<slave id="1" enabled="true" name="DL205Sim" implementation="modbus">
|
||||
|
||||
<holding_registers>
|
||||
|
||||
<!-- ============================================================
|
||||
V-MEMORY ADDRESSING MARKERS
|
||||
============================================================
|
||||
DirectLOGIC V-memory is octal natively; the CPU translates
|
||||
V<oct> -> Modbus PDU <decimal>. Tests verify our address
|
||||
helper produces the right PDU offset for known V-addresses.
|
||||
Marker values are arbitrary but distinctive so a test that
|
||||
reads the wrong PDU sees Goodread+wrong-value, not zero.
|
||||
-->
|
||||
|
||||
<!-- V0 (octal) = PDU 0x0000. Decisively proves register 0 is valid
|
||||
on DL205/DL260 with H2-ECOM100 in absolute mode (the default).
|
||||
The "rejects register 0" rumour was a DL05/DL06 relative-mode
|
||||
artefact — see dl205.md §Register Zero. -->
|
||||
<!-- 0xCAFE = 51966 (signed 16-bit: -13570) -->
|
||||
<register address="0" value="-13570" name="V0_marker_0xCAFE"/>
|
||||
|
||||
<!-- V2000 octal = decimal 1024 = PDU 0x0400. -->
|
||||
<!-- 0x2000 = 8192 -->
|
||||
<register address="1024" value="8192" name="V2000_marker_0x2000"/>
|
||||
|
||||
<!-- V40400 octal = decimal 8448 = PDU 0x2100. Proves the
|
||||
"V40400 = register 0" myth wrong on absolute-mode firmware. -->
|
||||
<!-- 0x4040 = 16448 -->
|
||||
<register address="8448" value="16448" name="V40400_marker_0x4040"/>
|
||||
|
||||
<!-- ============================================================
|
||||
STRING PACKING (the user's headline quirk)
|
||||
============================================================
|
||||
Two ASCII chars per register, FIRST CHAR in the LOW byte.
|
||||
"Hello" at HR[0x410..0x412]:
|
||||
|
||||
HR[0x410] = 'H' (0x48) lo, 'e' (0x65) hi -> 0x6548 = 25928
|
||||
HR[0x411] = 'l' (0x6C) lo, 'l' (0x6C) hi -> 0x6C6C = 27756
|
||||
HR[0x412] = 'o' (0x6F) lo, '\0' (0x00) hi -> 0x006F = 111
|
||||
|
||||
A textbook (high-byte-first) decoder reads "eH" "ll" "\0o"
|
||||
and prints "eHll \0o" — that's exactly the failure mode the
|
||||
DL205 string test asserts NOT happens once we add the
|
||||
ModbusStringByteOrder=LowFirst option to the driver.
|
||||
Test: DL205_String_low_byte_first_within_register. -->
|
||||
<register address="1040" value="25928" name="HelloStr_lo='H'_hi='e'"/>
|
||||
<register address="1041" value="27756" name="HelloStr_lo='l'_hi='l'"/>
|
||||
<register address="1042" value="111" name="HelloStr_lo='o'_hi=null"/>
|
||||
|
||||
<!-- ============================================================
|
||||
32-BIT FLOAT IN CDAB WORD ORDER
|
||||
============================================================
|
||||
IEEE 754 float 1.5f = 0x3FC00000.
|
||||
Standard ABCD: HR[N]=0x3FC0, HR[N+1]=0x0000
|
||||
DL205 CDAB: HR[N]=0x0000, HR[N+1]=0x3FC0 (LOW word first)
|
||||
|
||||
Test: DL205_Float32_word_order_is_CDAB.
|
||||
Driver must use ModbusByteOrder=WordSwap to decode this as 1.5. -->
|
||||
<register address="1056" value="0" name="FloatCDAB_lo_word"/>
|
||||
<!-- 0x3FC0 = 16320 -->
|
||||
<register address="1057" value="16320" name="FloatCDAB_hi_word"/>
|
||||
|
||||
<!-- ============================================================
|
||||
BCD-ENCODED REGISTER (DirectLOGIC default numeric storage)
|
||||
============================================================
|
||||
Ladder value 1234 stored as 0x1234 = 4660 (BCD nibbles, NOT
|
||||
binary 1234 = 0x04D2). A driver in binary-int mode reads 4660
|
||||
and reports the wrong value; in BCD mode it nibble-decodes
|
||||
0x1234 -> 1234. Test: DL205_BCD_register_decodes_as_decimal. -->
|
||||
<!-- 0x1234 = 4660 -->
|
||||
<register address="1072" value="4660" name="BCD_1234_as_0x1234"/>
|
||||
|
||||
<!-- ============================================================
|
||||
LOAD-LIMIT BOUNDARY MARKERS
|
||||
============================================================
|
||||
The DL series caps FC03 at 128 registers (above spec's 125)
|
||||
and FC16 at 100 (BELOW spec's 123). The cap-tests don't need
|
||||
specific values — they assert exception 03 IllegalDataValue
|
||||
on an over-sized request. We pre-seed a contiguous block at
|
||||
0x500..0x57F (128 regs) so a 128-register read returns Good
|
||||
and a 129-register read can be tried for the failure case.
|
||||
Per-register values: address - 0x500 (so HR[0x500]=0,
|
||||
HR[0x501]=1, ..., HR[0x57F]=127). Easy mental verification.
|
||||
Test: DL205_FC03_128_registers_returns_Good. -->
|
||||
<!-- (Generated programmatically below for brevity — first / last + spot-check) -->
|
||||
<register address="1280" value="0" name="FC03Block_first"/>
|
||||
<register address="1281" value="1"/>
|
||||
<register address="1282" value="2"/>
|
||||
<register address="1343" value="63" name="FC03Block_mid"/>
|
||||
<register address="1407" value="127" name="FC03Block_last"/>
|
||||
<!-- Note: ModbusPal serves unlisted addresses as 0 by default for
|
||||
reads that fall within the configured slave's address space.
|
||||
The block-test relies on that behavior; the hand-listed
|
||||
entries above are sanity markers. If the driver later wants
|
||||
byte-perfect comparison across the whole 128-register range,
|
||||
expand this section to one element per address (or switch to
|
||||
pymodbus). -->
|
||||
</holding_registers>
|
||||
|
||||
<coils>
|
||||
|
||||
<!-- ============================================================
|
||||
COIL / DISCRETE-INPUT MAPPING MARKERS (DL260 layout)
|
||||
============================================================
|
||||
Per dl205.md, on the DL260:
|
||||
X inputs -> discrete inputs 0..511 (FC02)
|
||||
Y outputs -> coils 2048..2559 (FC01/05)
|
||||
C relays -> coils 3072..4095 (FC01/05)
|
||||
|
||||
ModbusPal 1.6b does NOT have a discrete-inputs section in
|
||||
the official build, so the X-input markers can't be
|
||||
encoded faithfully (the driver test for FC02 against this
|
||||
profile will need a fork or pymodbus). The Y and C coil
|
||||
markers ARE encodable here.
|
||||
-->
|
||||
|
||||
<!-- Y0 marker — coil 2048 ON proves "Y0 maps to coil 2048" mapping.
|
||||
Test: DL205_Y0_maps_to_coil_2048. -->
|
||||
<coil address="2048" value="1" name="Y0_marker"/>
|
||||
<coil address="2049" value="0"/>
|
||||
<coil address="2050" value="1"/>
|
||||
|
||||
<!-- C0 marker — coil 3072 ON proves "C0 maps to coil 3072" mapping.
|
||||
Test: DL205_C0_maps_to_coil_3072. -->
|
||||
<coil address="3072" value="1" name="C0_marker"/>
|
||||
<coil address="3073" value="0"/>
|
||||
<coil address="3074" value="1"/>
|
||||
|
||||
<!-- Scratch coils 4000..4007 for write-roundtrip tests against
|
||||
the C-relay range. C ranges are writable on the real DL260. -->
|
||||
<coil address="4000" value="0" name="Cscratch_0"/>
|
||||
<coil address="4001" value="0"/>
|
||||
<coil address="4002" value="0"/>
|
||||
<coil address="4003" value="0"/>
|
||||
<coil address="4004" value="0"/>
|
||||
<coil address="4005" value="0"/>
|
||||
<coil address="4006" value="0"/>
|
||||
<coil address="4007" value="0"/>
|
||||
</coils>
|
||||
|
||||
<tuning>
|
||||
<!-- Zero delay / zero error rate. The DL205 H2-ECOM has a typical
|
||||
2-10ms scan-cycle delay; if a test wants to simulate that,
|
||||
tune via the ModbusPal GUI (Tuning > Reply delay). -->
|
||||
<reply_delay min="0" max="0"/>
|
||||
<error_rates no_reply="0.0"/>
|
||||
</tuning>
|
||||
</slave>
|
||||
|
||||
</modbuspal_project>
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
<?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>
|
||||
Reference in New Issue
Block a user