feat(arrays): Modbus String[]/BitInRegister[] array writes reachable; table follows the resolver
The Modbus lane (#741 U3) gave the driver String[] and BitInRegister[] holding-register array encoders, and moved address resolution to ModbusTagAddressResolution (keys first, a bit region coerced to Bool, strict key reads), but DriverArrayWriteSupport still listed both array shapes read-only (follow-up F1), so a client write was BadNotWritable before it reached the driver. Modbus row now mirrors the driver end to end: - Keys shape only (region + address both present). A string-only addressString tag is resolved by the driver with its own family through a parser Core.Abstractions cannot reference, so it stays read-only (pinned: the conservative direction). - Strict reads as the resolver: address an integer 0-65535; a present region / dataType / byteOrder / stringByteOrder string must name a member. - Coils: any dataType (coerced to Bool), <= 1968 coils (FC15). - HoldingRegisters (FC16, never Bool): numeric, String ((stringLength+1)/2 registers, stringLength >= 1), BitInRegister (1 register, bitIndex 0-15, span ending <= 65535), all within maxRegistersPerWrite. FC23 is opt-in and never changes the answer. - Elements convert (ScadaBridge int[] -> Int16 stays writable); only a String / DateTime node over a numeric or bit element is left out, since such an element never converts. Tests: the parity test's PendingF1 exemption and its guard are gone; the parity matrix is now region x element type x node type against the real WriteAsync with a spec-conformant device (FC15 > 1968 refused), plus shape edges at every cap boundary (numeric, String, BitInRegister, coil), strict key reads, FC23 on/off, string-only, the ScadaBridge int[] shape and U3 wire bytes. Core.Abstractions row tests and a composer test (String[] and BitInRegister[] writable; over-cap, no-stringLength and string-only read-only). Docs: table XML doc, Uns.md, modbus-addressing.md, drivers/Modbus.md, data-plane rules. Refs #741 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
@@ -89,6 +89,13 @@ For 8-byte values (Int64 / Float64) the same labels apply pairwise.
|
||||
`40001:F:5` = `Float32[5]` (consumes HR[0..9]). Array + bit suffix is
|
||||
rejected. Strings are not arrays.
|
||||
|
||||
An array written through the grammar string alone (a **string-only** tag —
|
||||
`addressString` with no `region`/`address` keys) is served and written by the
|
||||
driver, but its OPC UA node stays **read-only**: whether a write fits is
|
||||
decided at compose time, where the driver's family is not known. Pin the keys
|
||||
(the editor's "Resolve with driver family") to make the array writable — see
|
||||
"Array writes" below.
|
||||
|
||||
### Composition
|
||||
|
||||
The 3-field shorthand `40001:F:5` is parsed as `(type=F, count=5)` because
|
||||
@@ -279,3 +286,31 @@ than asking users to rewrite spreadsheets.
|
||||
`BitInRegister`).
|
||||
- Exception 01/02/03 are device refusals: per-tag Bad, one Warning per tag,
|
||||
no driver Degraded; reads and writes share one comms-fault health slot.
|
||||
|
||||
## Array writes (#610, #741)
|
||||
|
||||
Whether an array node is client-writable is decided by
|
||||
`DriverArrayWriteSupport` (Core.Abstractions) at compose time, and it says yes
|
||||
only for what `ModbusDriver.WriteAsync` encodes end to end
|
||||
(`ModbusArrayWriteSupportParityTests` pins the two against each other):
|
||||
|
||||
- **Keys only.** The tag must carry `region` + `address` (read as strictly as
|
||||
the driver reads them). A string-only tag stays read-only — the composer
|
||||
cannot resolve the grammar under the driver's family.
|
||||
- **Coils:** one FC15 of at most 1968 coils (the spec ceiling; the driver does
|
||||
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,
|
||||
`(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,
|
||||
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
|
||||
ScadaBridge `int[]` to an `Int16` node is writable.
|
||||
|
||||
Reference in New Issue
Block a user