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:
+4
-4
@@ -323,7 +323,7 @@ carried on the plan records (`RawTagPlan.ArrayWritable`, mirrored onto every UNS
|
||||
|
||||
| Driver | Array writes | Element types |
|
||||
|---|---|---|
|
||||
| **Modbus** | Yes (String / BitInRegister arrays since #741) | Tags addressed by `region` + `address` keys (strict reads: an integer address 0–65535, enum strings that name a member). `region: Coils` (FC15, ≤ 1968 coils) with **any** `dataType` — a bit region is coerced to Bool, never refused. `region: HoldingRegisters` (FC16) with a fixed-width numeric type (`Int16`/`UInt16`/`Bcd16`/`Int32`/`UInt32`/`Bcd32`/`Float32`/`Int64`/`UInt64`/`Float64`), `String` (`(stringLength+1)/2` registers per element, `stringLength >= 1`) or `BitInRegister` (one register per element, read-modify-written in one FC03 + one FC16, `bitIndex` 0–15, span ending at or before register 65535), whose `arrayLength × registers-per-element` fits `maxRegistersPerWrite` (default 123; the smallest value set on the driver or the tag's device wins) — the driver refuses rather than splits an over-cap FC16, so e.g. a 62-element `Int32` array (124 registers) stays read-only. FC23 (opt-in) never changes the answer. Not Bool-in-register; input regions are read-only. A **string-only** tag (`addressString`, no keys) stays read-only: the driver resolves it with its own family, which the composer cannot know — use the editor's Resolve to pin the keys |
|
||||
| **Modbus** | Yes (String / BitInRegister arrays since #741) | Tags addressed by `region` + `address` keys (strict reads: an integer address 0–65535, enum strings that name a member). `region: Coils` (FC15, ≤ 1968 coils) with **any** `dataType` — a bit region is coerced to Bool, never refused. `region: HoldingRegisters` (FC16) with a fixed-width numeric type (`Int16`/`UInt16`/`Bcd16`/`Int32`/`UInt32`/`Bcd32`/`Float32`/`Int64`/`UInt64`/`Float64`), `String` (`(stringLength+1)/2` registers per element, `stringLength >= 1`) or `BitInRegister` (one register per element, read-modify-written in one FC03 + one FC16, `bitIndex` 0–15, and — because of that unchunked FC03 — also within `maxRegistersPerRead`, default and protocol ceiling 125), whose `arrayLength × registers-per-element` fits `maxRegistersPerWrite` (default 123; the smallest value set on the driver or the tag's device wins; a value above FC16's protocol ceiling of 123 never lifts it, since a conformant device refuses a longer FC16) — the driver refuses rather than splits an over-cap FC16, so e.g. a 62-element `Int32` array (124 registers) stays read-only. Every span (registers or coils) must end at or before address 65535. FC23 (opt-in) never changes the answer. Not Bool-in-register; input regions are read-only. A **string-only** tag (`addressString`, no keys) stays read-only: the driver resolves it with its own family, which the composer cannot know — use the editor's Resolve to pin the keys |
|
||||
| **S7** | Yes | `Byte`/`Int16`/`UInt16`/`Int32`/`UInt32`/`Float32` (one contiguous block). Not `Bool` (would clobber neighbouring bits) nor the wide / String / DateTime types |
|
||||
| **OpcUaClient** | Yes | All — forwarded upstream unchanged; the upstream server decides (shape included) |
|
||||
| **Galaxy** | Yes | OPC UA `Boolean`/`Int32`/`Int64`/`Float`/`Double`/`String`/`DateTime` (the `MxValueEncoder` array variants) — **only with a declared `arrayLength`**: an undeclared length materialises an unbounded node, and a short whole-array write would *resize* the Galaxy attribute (mxaccessgw contract), so such a node stays read-only |
|
||||
@@ -341,9 +341,9 @@ stays read-only.
|
||||
nodes: the node manager converts a numeric array to the node's element type, and the AB CIP, AB Legacy
|
||||
and TwinCAT encoders convert each element again to the controller's type (an element that does not fit
|
||||
is `BadOutOfRange`), as does Modbus to the register / coil type. Those rows therefore do not key on an
|
||||
exact node `DataType`; they leave out only node types whose elements never convert — a `DateTime` or
|
||||
`String` node over a numeric element (on Modbus, over a numeric or bit element; a Modbus `String`
|
||||
element takes any node type), and a `Boolean` node on AB Legacy. An array tag deployed read-only before its driver gained an encoder turns
|
||||
exact node `DataType`; they leave out only node types whose elements the server cannot promise to convert — a `DateTime` (never converts) or
|
||||
`String` node over a numeric element (converts only when the text happens to parse, `"1"` / `"true"`;
|
||||
on Modbus, over a numeric or bit element; a Modbus `String` element takes any node type), and a `Boolean` node on AB Legacy. An array tag deployed read-only before its driver gained an encoder turns
|
||||
writable on the next deploy (a surgical `ArrayWritable` update, no rebuild).
|
||||
|
||||
Each "Yes" row is pinned against the driver's real encoder by a parity test in that driver's
|
||||
|
||||
Reference in New Issue
Block a user