feat(ab): native managed transport is the default for AbCip and AbLegacy; libplctag remains as fallback
Task 22 (spec section 8 P5), with the controller rulings that exclude PLC-5 and LogixPccc. - Transport is nullable on both option records; null (key absent) means "use the default" and is never serialized. Factory DTO parse: absent -> null; a bad value still fails via ParseEnum. - One resolver per driver (AbCipTransportResolver / AbLegacyTransportResolver), used by the driver constructor, the Test Connect probe and (through the driver) the CLI. AbCip: null -> Native. AbLegacy: null -> Native unless any device is Plc5 or LogixPccc, then the whole driver resolves to Libplctag. Explicit values always win; explicit Native with a Plc5/LogixPccc device logs one Warning at init. - Init log shows configured vs effective, e.g. transport=Native (default). - AdminUI forms: "Default (...)" / "Native (managed)" / "libplctag (fallback)". Default omits the key on save (reverses Task 19's explicit re-emit, which would have pinned every edited driver). - CLIs: --transport default|native|libplctag, default "default" via the resolver, so --plc-type Plc5 with no --transport runs libplctag. - Tests pinned to Libplctag where a default-path runtime was created: found by instrumenting the native factories with the current xUnit test name (one unit test, AbLegacyReinitConfigAdoption; plus the two fixture-gated AbCip probe E2E tests whose exact messages were proven on libplctag). TransportMatrix and the differential harness already pass explicit transports. - Switch/resolver/CLI/form tests are falsified by mutating the resolver (default -> Libplctag, exclusion removed, AbLegacy default -> Libplctag): each mutation turns them red. - Replaces the inline role-name literal in AbCipDriverFactoryExtensions' ParseEnum call with a named constant; RoleAuthorityGuardTests was already red on the base commit for that line. - Docs: AbCip.md / AbLegacy.md default + release note; CLAUDE.md AB section. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
+34
-12
@@ -76,7 +76,7 @@ the driver's `DriverConfig` JSON. Key fields:
|
||||
- **`Devices`** — one `AbCipDeviceOptions` per PLC (`HostAddress`, `PlcFamily`, optional `DeviceName`, per-device `AllowPacking` / `ConnectionSize` overrides).
|
||||
- **`Tags`** — pre-declared `AbCipTagDefinition` list; `Members` for UDT fan-out, `SafetyTag` for GuardLogix safety-partition tags.
|
||||
- **`Probe`** — connectivity-probe `Enabled` / `Interval` / `Timeout` / `ProbeTagPath`.
|
||||
- **`Transport`** — `Libplctag` (default) or `Native`; see [Transport](#transport-transport-option).
|
||||
- **`Transport`** — absent (default ⇒ `Native`), `Native`, or `Libplctag` (fallback); see [Transport](#transport-transport-option).
|
||||
- **Discovery** — `EnableControllerBrowse` (`@tags` walk) and `EnableDeclarationOnlyUdtGrouping` (whole-UDT read fast path).
|
||||
- **Alarms** — `EnableAlarmProjection` + `AlarmPollInterval` for the ALMD projection.
|
||||
|
||||
@@ -89,24 +89,46 @@ The driver has two interchangeable wire transports, selected by one `DriverConfi
|
||||
|
||||
| Key | Values | Default | Serialization |
|
||||
|---|---|---|---|
|
||||
| `transport` | `Libplctag` \| `Native` | `Libplctag` | string, case-insensitive on read, written as `"Libplctag"` / `"Native"` |
|
||||
| `transport` | absent \| `Native` \| `Libplctag` | absent ⇒ `Native` | string, case-insensitive on read, written as `"Libplctag"` / `"Native"`; "use the default" is encoded by OMITTING the key |
|
||||
|
||||
- **`Libplctag`** (default) drives the libplctag native library, as before.
|
||||
- **`Native`** is a managed in-process EtherNet/IP + CIP client (`Driver.Ab.Transport`). It is **opt-in**:
|
||||
the default stays `Libplctag` until the real-press read-only gate has run and the default is flipped.
|
||||
- **`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.
|
||||
- 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 **recreated**. 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 ("libplctag
|
||||
(default)" / "Native (managed)"). Saving another field preserves it.
|
||||
- The driver logs `transport=<X>` at Information on every init, including reconnect re-inits. AbCip
|
||||
- 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
|
||||
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
|
||||
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 native|libplctag` (default `libplctag`).
|
||||
- CLI: `--transport default|native|libplctag` (default `default`, which goes through the same resolver).
|
||||
- libplctag is confined to `Libplctag*.cs` adapters, enforced by a source-scan guard
|
||||
(`LibplctagConfinementGuardTests`), so removing it later is mechanical.
|
||||
|
||||
### Release note — native is the default (Task 22)
|
||||
|
||||
- **The native managed transport is now the default.** A driver config with no `transport` key runs
|
||||
`Native`; libplctag remains available as the fallback.
|
||||
- ⚠️ **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).
|
||||
- **When it takes effect:** a change of transport — including this default flip for an existing driver with
|
||||
no key — takes effect when a deploy recreates the driver instance, not on `ReinitializeAsync`.
|
||||
- **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)).
|
||||
|
||||
## Native transport behaviour
|
||||
|
||||
- **One CIP connection and one request scheduler per device** (`DeviceSessionPool`), shared by reads,
|
||||
@@ -137,15 +159,15 @@ The driver has two interchangeable wire transports, selected by one `DriverConfi
|
||||
## Known gaps
|
||||
|
||||
- ⚠️ **Native writes are verified against `ab_server` only (simulator-only), never a real controller.** The
|
||||
real-press gate (Task 21) is **read-only** and **has not run yet**; this page makes no real-controller
|
||||
claim for the native transport. Results land in
|
||||
real-press gate (Task 21) was **read-only**: it proved native reads, browse and templates on Z-35162
|
||||
(2026-10-08) and nothing else. Results:
|
||||
[native-ab-transport-gate-results](../plans/2026-10-07-native-ab-transport-gate-results.md).
|
||||
- **libplctag cannot be deleted** until a bench controller allows write verification.
|
||||
- Wide-struct fragmented writes (8-byte alignment) are unit-tested only, never proven on a controller (#567).
|
||||
- Native browse: listing pages are unpaced, and template reads outside an enumeration open a connection per
|
||||
call (#572).
|
||||
- Micro800, routed and `logixpccc` paths are covered by `ab_server` only. The Micro800 profile passes
|
||||
natively; the planned default flip excludes `logixpccc`.
|
||||
natively. The AbLegacy default excludes `logixpccc` (and PLC-5).
|
||||
- Follow-up issues for this workstream: #564 to #577.
|
||||
- Fixed along the way (pre-existing on master): `ab://host:44818/` with an empty CIP path (Micro800,
|
||||
unrouted MicroLogix) failed on libplctag with `ErrorBadData` because the adapter sent `Path=""`; it now
|
||||
|
||||
+35
-10
@@ -93,32 +93,57 @@ binds from the driver's `DriverConfig` JSON:
|
||||
- **`Devices`** — one `AbLegacyDeviceOptions` per PLC (`HostAddress`, `PlcFamily`, optional `DeviceName`).
|
||||
- **`Tags`** — pre-declared `AbLegacyTagDefinition` list (`Name`, `DeviceHostAddress`, `Address`, `DataType`, `Writable`, `WriteIdempotent`).
|
||||
- **`Probe`** — connectivity-probe `Enabled` / `Interval` / `Timeout` / `ProbeAddress`.
|
||||
- **`Transport`** — `Libplctag` (default) or `Native`; see [Transport](#transport-transport-option).
|
||||
- **`Transport`** — absent (default ⇒ `Native`, but `Libplctag` when any device is PLC-5 / `LogixPccc`), `Native`, or `Libplctag` (fallback); see [Transport](#transport-transport-option).
|
||||
|
||||
Full per-field descriptions live in the contracts assembly. The JSON skeleton is
|
||||
reproduced in [docs/v2/driver-specs.md §4](../v2/driver-specs.md).
|
||||
|
||||
## Transport (`transport` option)
|
||||
|
||||
Same switch as AB CIP: `transport` = `Libplctag` (default) \| `Native`, a string (case-insensitive on read,
|
||||
written as `"Libplctag"` / `"Native"`). `Native` is a managed PCCC-over-EtherNet/IP client
|
||||
(`Driver.Ab.Transport`) and is **opt-in**; the default stays `Libplctag` pending the real-press gate and the
|
||||
default flip. A change takes effect when the driver instance is recreated (a deploy that changes
|
||||
`DriverConfig`); the AdminUI form has a **Transport** dropdown in the advanced section, and the driver logs
|
||||
`transport=<X>` on every init. CLI: `--transport native|libplctag`. See
|
||||
Same switch as AB CIP: `transport` = absent \| `Native` \| `Libplctag`, a string (case-insensitive on read,
|
||||
written as `"Libplctag"` / `"Native"`; "use the default" is encoded by omitting the key). `Native` is a managed
|
||||
PCCC-over-EtherNet/IP client (`Driver.Ab.Transport`).
|
||||
|
||||
**Default resolution** (one place, `AbLegacyTransportResolver`, used by the driver, the Test Connect probe and
|
||||
the CLI): an absent key resolves to `Native`, **unless any configured device has `PlcFamily` `Plc5` or
|
||||
`LogixPccc`** — then the whole driver resolves to `Libplctag`, because the transport is chosen once per driver,
|
||||
not per device. An explicit value always wins: explicit `Native` with a PLC-5 / `LogixPccc` device is allowed
|
||||
(an operator choice) and logs one Warning at init naming the limitation.
|
||||
|
||||
A change takes effect when the driver instance is recreated (a deploy that changes `DriverConfig`). The
|
||||
AdminUI form's **Transport** dropdown offers "Default (Native; libplctag for PLC-5 / LogixPccc)" (omits the
|
||||
key on save), "Native (managed)" and "libplctag (fallback)". The driver logs
|
||||
`transport=<effective> (default|configured)` on every init, e.g. `transport=Libplctag (default; PLC-5/LogixPccc
|
||||
device present)`. CLI: `--transport default|native|libplctag` (default `default`, so `--plc-type Plc5` with no
|
||||
`--transport` runs libplctag). See
|
||||
[AbCip.md §Transport](AbCip.md#transport-transport-option) for the shared behaviour (one pooled connection
|
||||
per device, Forward Open throttle, routing).
|
||||
|
||||
Native routing: SLC500/MicroLogix/PLC-5 are direct; `logixpccc` uses UnconnectedSend and its SLC framing is
|
||||
**unverified**.
|
||||
|
||||
### Release note — native is the default (Task 22)
|
||||
|
||||
- **The native managed transport is now the default** for SLC 500 and MicroLogix. A driver config with no
|
||||
`transport` key runs `Native`; libplctag remains available as the fallback.
|
||||
- **Exclusion:** a defaulted driver with **any PLC-5 or `LogixPccc` device stays on libplctag**. Native PLC-5
|
||||
support is N-file reads only, and `LogixPccc` framing is unverified. Explicit `Native` is still honoured
|
||||
there, with a Warning at init.
|
||||
- ⚠️ **Client-visible:** native reports **`BadTypeMismatch`** for a mis-authored same-width type, where
|
||||
libplctag returned Good garbage (see [AbCip.md §Native transport behaviour](AbCip.md#native-transport-behaviour)).
|
||||
- **To fall back**, set `"transport": "Libplctag"` in the driver config.
|
||||
- **When it takes effect:** when a deploy recreates the driver instance (not on `ReinitializeAsync`). An existing
|
||||
driver with no key moves to Native on its next recreate.
|
||||
- **Evidence, and its limits:** all of AbLegacy is **simulator-proven only** (`ab_server`). No real SLC,
|
||||
MicroLogix or PLC-5 has been exercised. (The real-press gate, Z-35162 2026-10-08, covered AbCip reads only.)
|
||||
|
||||
### Native known gaps
|
||||
|
||||
- ⚠️ **Simulator-only.** PCCC reads and writes are verified against `ab_server` only. No real SLC/MicroLogix/
|
||||
PLC-5 has been exercised, and the real-press gate (Task 21) is read-only and has not run yet.
|
||||
PLC-5 has been exercised; the real-press gate (Task 21) was read-only and AbCip-only.
|
||||
- **PLC-5 native is N-file reads only.** Every other file type and all PLC-5 writes are refused, currently
|
||||
surfacing as `BadCommunicationError` rather than `BadNotSupported` (#571). The planned default flip excludes
|
||||
PLC-5 and `logixpccc`.
|
||||
surfacing as `BadCommunicationError` rather than `BadNotSupported` (#571). The default excludes PLC-5 and
|
||||
`logixpccc` (they resolve to libplctag unless `Native` is set explicitly).
|
||||
- Status-bit sub-element writes (e.g. `T4:0/DN`, `C5:0/DN`) are Unsupported natively (they would clobber the
|
||||
control word); nothing goes on the wire. Sub-element arrays are Unsupported natively. L-file bit indexes
|
||||
above 15 are allowed.
|
||||
|
||||
Reference in New Issue
Block a user