fix(arrays): Modbus row honours the FC16/FC03 protocol ceilings, the RMW read cap and the address-space end
Review r0 of Integration-W3: - I1: the effective FC16 cap is min(configured maxRegistersPerWrite, 123). The driver trusts a larger configured cap and sends an over-ceiling FC16 (past 127 registers its byte-count byte wraps), which every conformant device refuses, so such an array is never advertised writable. - I2: a BitInRegister[] write reads its span first in one unchunked FC03, so it is also bounded by min(configured maxRegistersPerRead, 125) (absent/0 = 125). - m2: every span (registers and coils) must end at or before address 65535. - m1: the String-node wording says it converts only when the text parses, which the server cannot promise (DateTime never converts). - m4: the parity device now enforces FC01/02 2000, FC03/04 125 (or a smaller device read limit), FC15 1968, FC16 123, FC23 121/125, the FC15/FC16 byte-count field (exception 03) and the address-space end (exception 02). Tests: parity rows for each ceiling/cap/span boundary against the real write path, and Core.Abstractions row tests. The configured-cap test that asserted a cap of 200 lifts the ceiling now asserts the opposite. Refs #741 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
@@ -301,16 +301,22 @@ only for what `ModbusDriver.WriteAsync` encodes end to end
|
||||
not chunk). Any `dataType` — a bit region is coerced to Bool.
|
||||
- **HoldingRegisters:** one FC16 whose register count fits
|
||||
`maxRegistersPerWrite` (default 123; the smallest value on the driver or the
|
||||
tag's device wins). Registers per element: 1 / 2 / 4 for the numeric types,
|
||||
tag's device wins), and never more than FC16's protocol ceiling of 123 — a
|
||||
larger configured cap does not lift it, because a conformant device refuses
|
||||
the longer FC16. Registers per element: 1 / 2 / 4 for the numeric types,
|
||||
`(stringLength+1)/2` for `String` (each element nul-padded to that width), 1
|
||||
for `BitInRegister` (the span is read with one FC03, the addressed bit of each
|
||||
register is set or cleared, and the span is written back with one FC16 —
|
||||
the other 15 bits are preserved; `bitIndex` 0–15, the span must end at or
|
||||
before register 65535). FC23 (`useFC23ForMultiRegisterWrites`) is opt-in,
|
||||
the other 15 bits are preserved; `bitIndex` 0–15). Because that FC03 is one
|
||||
unchunked read of the whole span, a BitInRegister array must also fit
|
||||
`maxRegistersPerRead` (default and protocol ceiling 125).
|
||||
- **Every span** — registers or coils — must end at or before address 65535
|
||||
(a conformant device answers exception 02 past it). FC23 (`useFC23ForMultiRegisterWrites`) is opt-in,
|
||||
capped at 121 registers and falls back to FC16 above that, so it never
|
||||
changes which arrays are writable.
|
||||
- **Never writable:** DiscreteInputs / InputRegisters, Bool in a register
|
||||
region, a `String` with no `stringLength`, a `bitIndex` above 15, a missing
|
||||
or zero `arrayLength`, and a `String` / `DateTime` node over a numeric or bit
|
||||
element (such an element never converts). Elements otherwise convert, so a
|
||||
element (a DateTime never converts; a String converts only when its text
|
||||
happens to parse, which the server cannot promise). Elements otherwise convert, so a
|
||||
ScadaBridge `int[]` to an `Int16` node is writable.
|
||||
|
||||
Reference in New Issue
Block a user