Merge lane Modbus: Modbus driver remediation (#741)
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
@@ -149,7 +149,11 @@ Beyond per-tag addressing, `ModbusDriverOptions` exposes (#139–#143):
|
||||
- `UseFC15ForSingleCoilWrites` — force FC15 (write multiple coils
|
||||
qty=1) for single-coil writes. Safety/audit PLCs may require this.
|
||||
- `UseFC16ForSingleRegisterWrites` — same for FC16 vs FC06.
|
||||
- `DisableFC23` — kill switch for FC23 (currently unused; reserved).
|
||||
- `UseFC23ForMultiRegisterWrites` (default `false`) — opt-in: a 2–121-register
|
||||
holding write is sent as one FC23 (write + read-back). A unit answering
|
||||
exception 01 moves to FC16 until the next re-init. Without the key the
|
||||
wire stays FC16 (#741).
|
||||
- `DisableFC23` — hard kill switch: never send FC23, even when opted in.
|
||||
|
||||
### Subscribe (#141)
|
||||
- Per-tag `Deadband` — suppress sub-threshold publishes on numeric tags.
|
||||
@@ -165,9 +169,13 @@ Beyond per-tag addressing, `ModbusDriverOptions` exposes (#139–#143):
|
||||
this tag in isolation regardless of `MaxReadGap`).
|
||||
|
||||
### Block-read coalescing (#143)
|
||||
- `MaxReadGap` (default 0 = off) — gap budget the planner is willing to
|
||||
bridge between adjacent register tags. With `MaxReadGap=10`, three tags
|
||||
at HR 100/102/110 collapse into one FC03 of quantity 11.
|
||||
- `MaxReadGap` — gap budget the planner is willing to bridge between
|
||||
adjacent tags of one (unit, region). With `MaxReadGap=10`, three tags at
|
||||
HR 100/102/110 collapse into one FC03 of quantity 11. An explicit `0` =
|
||||
coalescing off (its historical meaning); the default `8` applies only when
|
||||
the key is absent (#741). Coils / DiscreteInputs (gap in bits), `String`
|
||||
scalars and same-word `BitInRegister` tags coalesce too.
|
||||
- `DisableReadCoalescing` — turn coalescing off regardless of the gap.
|
||||
|
||||
### Coalescing auto-recovery (#148 / #150 / #151 / #152)
|
||||
- A coalesced read that fails with a Modbus exception (write-only or
|
||||
@@ -231,3 +239,43 @@ the current Kepware "Modbus Ethernet Driver Help" PDF and Ignition's
|
||||
"Modbus Addressing" user-manual page — if a critical tool's mnemonics
|
||||
have shifted, add aliases in `ModbusAddressParser.TryParseType` rather
|
||||
than asking users to rewrite spreadsheets.
|
||||
|
||||
|
||||
## Address strings, families and region/type rules (#741)
|
||||
|
||||
- **One authority: `ModbusTagAddressResolution` (Driver.Modbus.Contracts).** The
|
||||
driver's tag reader, the deploy gate (`Inspect`), the AdminUI editor model
|
||||
(load / save / `Validate` / the hint text) and the CSV import/export all ask
|
||||
it which part of a blob is the address; none restates the rule. Its
|
||||
agreement is pinned by one table-driven test that runs every persisted shape
|
||||
through every consumer (`ModbusAddressAuthorityAgreementTests`).
|
||||
- **Whenever a tag carries both `region` and `address`, those keys are its
|
||||
address** — a classic tag, an editor-pinned tag, and a pre-#741 row that
|
||||
carried an `addressString` beside its keys (the old driver never read the
|
||||
string) all load from the keys, exactly as before, and a family change never
|
||||
remaps them. Only a **string-only** blob (an `addressString` with NO
|
||||
`region`/`address` — CSV / API authoring) is resolved through
|
||||
`ModbusAddressParser` with the **driver's** `Family` / `MelsecSubFamily`; a
|
||||
family that cannot parse it refuses the tag (BadNodeIdUnknown). It is never
|
||||
served as HR[0].
|
||||
- `"addressResolved": true` is editor display / CSV state only: it never makes
|
||||
keys authoritative, and a marker on a blob WITHOUT keys is ignored (the blob
|
||||
is string-only) and dropped on the next editor save.
|
||||
- The AdminUI never writes default keys it did not resolve: the editor model
|
||||
stamps `region`/`address`/type only when the loaded blob had the keys, or when
|
||||
a field is set (hand edit, CSV address cell, Resolve). A string-only,
|
||||
non-string-`addressString` or address-less blob keeps its keys unwritten, and
|
||||
its CSV export leaves the address cells blank.
|
||||
- The editor's "Resolve with driver family" persists the resolved
|
||||
`region`/`address`/type fields plus `"addressResolved": true` (the pinned
|
||||
state). Editing a resolved field by hand in the editor drops the string and
|
||||
the marker. A typed CSV address cell keeps them only while the string still
|
||||
describes the cells under some family (so an unchanged export → import of a
|
||||
pinned row is lossless); a contradicting cell drops them, and the import
|
||||
review grid reports that column as an override.
|
||||
- Coils / DiscreteInputs carry only Bool. A bit-region tag persisted with a
|
||||
register `dataType` (the pre-#741 editor saved `Int16`) is coerced to Bool,
|
||||
never refused. A register region with `Bool` is refused (use
|
||||
`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.
|
||||
|
||||
Reference in New Issue
Block a user