feat(abcip): final review I1 — node-level Drivers:AbCip:DefaultTransport
The old AdminUI cannot author the per-driver transport key (#576), so the 'pin before upgrading' recipe had no working path. A node-level appsetting (env Drivers__AbCip__DefaultTransport) now supplies the default for AbCip drivers with no key: explicit key > node default > compiled Native. Bound and validated unconditionally (bad value fails host start); the driver factory and the Test Connect probe both read it so they agree. AbCip.md upgrade recipe rewritten around the key; Configuration.md documents it. Also documents first-wins for AllowPacking/probe and host-alias throttles (final review M6) and moves the status-bit note out of AbCip.md (M5). Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
+38
-18
@@ -89,23 +89,28 @@ The driver has two interchangeable wire transports, selected by one `DriverConfi
|
||||
|
||||
| Key | Values | Default | Serialization |
|
||||
|---|---|---|---|
|
||||
| `transport` | absent \| `Native` \| `Libplctag` | absent ⇒ `Native` | string, case-insensitive on read, written as `"Libplctag"` / `"Native"`; "use the default" is encoded by OMITTING the key |
|
||||
| `transport` | absent \| `Native` \| `Libplctag` | absent ⇒ the node's `Drivers:AbCip:DefaultTransport` (itself default `Native`) | string, case-insensitive on read, written as `"Libplctag"` / `"Native"`; "use the default" is encoded by OMITTING the key |
|
||||
|
||||
- **`Native`** (the default since Task 22) is a managed in-process EtherNet/IP + CIP client
|
||||
(`Driver.Ab.Transport`).
|
||||
- **`Libplctag`** (fallback) drives the libplctag native library, as before.
|
||||
- An absent key means "use the default". It is resolved in one place, `AbCipTransportResolver`, which the
|
||||
driver, the Test Connect probe and the CLI all go through. An explicit value always wins.
|
||||
driver, the Test Connect probe and the CLI all go through. **Precedence:** an explicit per-driver key →
|
||||
the node-level appsetting **`Drivers:AbCip:DefaultTransport`** (`Native` | `Libplctag`, env
|
||||
`Drivers__AbCip__DefaultTransport`; a bad value fails host start; see
|
||||
[Configuration.md](../Configuration.md#driversabcip-node-level-abcip-defaults)) → compiled `Native`. The
|
||||
Test Connect probe uses the same node value, so it probes the stack the driver would run. The CLI has no
|
||||
node config and uses compiled `Native`.
|
||||
- A bad value fails like a bad `PlcFamily`; the Test Connect probe reports "Config JSON is invalid".
|
||||
- A change takes effect when the driver instance is **constructed**: on every process start (so the upgrade
|
||||
restart moves no-key drivers to Native — see the release note) and when a deploy that changes `DriverConfig`
|
||||
does this (`DriverHostActor` respawns the child, #516). `ReinitializeAsync` alone keeps the transport
|
||||
it was constructed with.
|
||||
- AdminUI: the **Transport** dropdown in the advanced section of the AbCip driver form ("Default (Native)" /
|
||||
"Native (managed)" / "libplctag (fallback)"). **Default omits the key on save**, so a defaulted driver stays
|
||||
- AdminUI: the **Transport** dropdown in the advanced section of the AbCip driver form ("Default (node
|
||||
setting; Native unless pinned)" / "Native (managed)" / "libplctag (fallback)"). **Default omits the key on save**, so a defaulted driver stays
|
||||
defaulted; saving another field preserves an explicit value and preserves absence.
|
||||
- The driver logs `transport=<effective> (default|configured)` at Information on every init, e.g.
|
||||
`transport=Native (default)`, including reconnect re-inits. AbCip
|
||||
`transport=Native (default)` or `transport=Libplctag (node default)`, including reconnect re-inits. AbCip
|
||||
drivers now receive the host logger (they previously ran on a null logger, so none of their warnings
|
||||
reached the node log); expect previously invisible warnings, e.g. a probe enabled with no probe tag path.
|
||||
- CLI: `--transport default|native|libplctag` (default `default`, which goes through the same resolver).
|
||||
@@ -119,19 +124,32 @@ The driver has two interchangeable wire transports, selected by one `DriverConfi
|
||||
- ⚠️ **Client-visible:** native reports **`BadTypeMismatch`** for a mis-authored same-width type (for example a
|
||||
tag authored `DINT` where the controller holds a `REAL`), where libplctag returned Good garbage. Correct the
|
||||
authored type; see [Native transport behaviour](#native-transport-behaviour).
|
||||
- **To fall back**, set `"transport": "Libplctag"` in the driver config (or pick "libplctag (fallback)" in
|
||||
the AdminUI dropdown).
|
||||
- **To fall back** one driver, set `"transport": "Libplctag"` in its driver config (or pick "libplctag
|
||||
(fallback)" in the AdminUI dropdown). To fall back a whole node, set `Drivers:AbCip:DefaultTransport` to
|
||||
`Libplctag` (below).
|
||||
- ⚠️ **When it takes effect: the upgrade restart itself.** Every process start constructs the drivers
|
||||
(including boot-from-cache on a driver-only node), so **every driver with no `transport` key moves to Native
|
||||
the moment the upgraded node starts** — no deploy is needed. To keep a driver on libplctag, add
|
||||
`"transport": "Libplctag"` and deploy it **before upgrading**: older builds ignore the unknown key. After the
|
||||
upgrade, a transport change takes effect when a deploy recreates the driver (not on `ReinitializeAsync`).
|
||||
(including boot-from-cache on a driver-only node), so **every driver with no `transport` key moves to the
|
||||
node default — `Native` unless pinned — the moment the upgraded node starts**; no deploy is needed. After the
|
||||
upgrade, a per-driver transport change takes effect when a deploy recreates the driver (not on
|
||||
`ReinitializeAsync`).
|
||||
- **To stay on libplctag across the upgrade (pin before upgrading):** set the node-level key on each node
|
||||
you upgrade, in the same change that installs the new build — `appsettings.json`
|
||||
`"Drivers": { "AbCip": { "DefaultTransport": "Libplctag" } }`, or the environment variable
|
||||
`Drivers__AbCip__DefaultTransport=Libplctag` on the service. Older builds ignore the section, so it can be
|
||||
staged ahead of the binaries. Every AbCip driver with no `transport` key then starts on libplctag; the init
|
||||
log shows `transport=Libplctag (node default)`. Move to Native later per driver (explicit
|
||||
`"transport": "Native"` in the new AdminUI) or for the node (remove the key / set `Native`, then restart).
|
||||
⚠️ Do **not** rely on adding `"transport": "Libplctag"` to driver configs through the **old** AdminUI before
|
||||
upgrading: its typed AbCip form has no Transport field and drops unknown keys on save (#576), and on a fused
|
||||
`admin,driver` node the AdminUI and drivers upgrade together, so there is no window. The node key works on
|
||||
every node shape, including fused nodes and driver-only nodes booting from cache.
|
||||
- **Evidence, and its limits:** AbCip **reads** (plus browse and templates) were proven on a production
|
||||
CompactLogix 5069-L320ERS2 (**Z-35162**, read-only, 2026-10-08; see
|
||||
[gate results](../plans/2026-10-07-native-ab-transport-gate-results.md) §Real press). AbCip **writes** are
|
||||
simulator-proven only (`ab_server`), as is all of AbLegacy.
|
||||
- AbLegacy has its own exclusion: a defaulted AbLegacy driver with any PLC-5 or `logixpccc` device stays on
|
||||
libplctag (see [AbLegacy.md §Transport](AbLegacy.md#transport-transport-option)).
|
||||
simulator-proven only (`ab_server`).
|
||||
- **AbLegacy is different: its default stays libplctag** and Native is opt-in per driver; the native PCCC
|
||||
path has wire evidence (on `ab_server`) only for N-file reads/writes and one F8 read (see
|
||||
[AbLegacy.md §Transport](AbLegacy.md#transport-transport-option)).
|
||||
|
||||
## Native transport behaviour
|
||||
|
||||
@@ -140,8 +158,12 @@ The driver has two interchangeable wire transports, selected by one `DriverConfi
|
||||
request in flight; large reads (listing pages, Read Template) are sent unpacked and exclusive.
|
||||
- **Forward Open throttle:** at most one attempt in flight, at least 1 s between starts, bounded by the
|
||||
open budget.
|
||||
- `ConnectionSize` / `Timeout` are honoured per pooled connection, and the **first** driver/device to open
|
||||
a given device key wins. ControlLogix, GuardLogix and (since T21) CompactLogix default to 4002, a Large
|
||||
- `ConnectionSize` / `Timeout` / `AllowPacking` are honoured per pooled connection, and the **first**
|
||||
driver/device to open a given device key wins — including a Test Connect probe on a fused `admin,driver`
|
||||
node (its connection carries the probe timeout). A later opener with different options is ignored and
|
||||
logged once as a Warning (`DeviceSessionPool`). ⚠️ The device key compares the **host string**, so the
|
||||
same controller addressed by DNS name in one driver and by IP in another gets two connections and two
|
||||
Forward Open throttles (up to two opens per second); address a controller the same way everywhere. ControlLogix, GuardLogix and (since T21) CompactLogix default to 4002, a Large
|
||||
Forward Open (0x5B), the same as libplctag. A target that refuses Large gets one regular open at
|
||||
min(size, 504) inside the same throttled attempt. An explicit `ConnectionSize` in the options or on the
|
||||
device overrides the family default.
|
||||
@@ -157,8 +179,6 @@ The driver has two interchangeable wire transports, selected by one `DriverConfi
|
||||
writes are gated on a mismatch too.
|
||||
- Malformed tag names put the device into backoff, as under libplctag (#570). STRING capacity includes the
|
||||
alignment pad (84, not 82) (#570).
|
||||
- Status-bit sub-element writes (e.g. `T4:0.DN`) are Unsupported natively (they would clobber the control
|
||||
word); nothing goes on the wire. Sub-element arrays are Unsupported natively.
|
||||
- **A refused operation is not a comms fault.** Any operation a runtime refuses as Unsupported (in AbCip, e.g.
|
||||
a native write whose type cannot be determined) reaches the client as `BadNotSupported`, keeps the tag
|
||||
handle, and leaves driver health unchanged; only real transport failures evict and degrade.
|
||||
|
||||
Reference in New Issue
Block a user