docs(ab): native transport, Transport switch, known gaps and the type-check change
This commit is contained in:
+67
-1
@@ -76,12 +76,78 @@ 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).
|
||||
- **Discovery** — `EnableControllerBrowse` (`@tags` walk) and `EnableDeclarationOnlyUdtGrouping` (whole-UDT read fast path).
|
||||
- **Alarms** — `EnableAlarmProjection` + `AlarmPollInterval` for the ALMD projection.
|
||||
|
||||
Full per-field descriptions live in `AbCipDriverOptions.cs`. The JSON skeleton is
|
||||
reproduced in [docs/v2/driver-specs.md §3](../v2/driver-specs.md).
|
||||
|
||||
## Transport (`transport` option)
|
||||
|
||||
The driver has two interchangeable wire transports, selected by one `DriverConfig` key:
|
||||
|
||||
| Key | Values | Default | Serialization |
|
||||
|---|---|---|---|
|
||||
| `transport` | `Libplctag` \| `Native` | `Libplctag` | string, case-insensitive on read, written as `"Libplctag"` / `"Native"` |
|
||||
|
||||
- **`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.
|
||||
- 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
|
||||
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`).
|
||||
- libplctag is confined to `Libplctag*.cs` adapters, enforced by a source-scan guard
|
||||
(`LibplctagConfinementGuardTests`), so removing it later is mechanical.
|
||||
|
||||
## Native transport behaviour
|
||||
|
||||
- **One CIP connection and one request scheduler per device** (`DeviceSessionPool`), shared by reads,
|
||||
writes, browse and template reads. The Multiple Service Packet scheduler coalesces requests with one
|
||||
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.
|
||||
- **Routing:** Micro800 is connected with an empty route regardless of `UseConnectedMessaging`.
|
||||
SLC500/MicroLogix/PLC-5 over this driver are direct. `logixpccc` uses UnconnectedSend; its SLC framing is
|
||||
**unverified**.
|
||||
- ⚠️ **Type check (client-visible change).** The native path compares the type code the controller returns
|
||||
with the authored `DataType` and reports **`BadTypeMismatch`** on disagreement. libplctag never checks it,
|
||||
so a same-width mis-authored tag read as **Good garbage**: authoring a tag as `DINT` while the controller
|
||||
holds a `REAL` returns the REAL's bits as an integer with a Good status under libplctag, and
|
||||
`BadTypeMismatch` natively. That is the fix working, but a consumer will see Bad quality on tags that
|
||||
previously looked healthy: correct the authored type. Same-width signed/unsigned pairs are exempt, and
|
||||
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.
|
||||
|
||||
## 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
|
||||
[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`.
|
||||
- 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
|
||||
sends no path.
|
||||
|
||||
## Alarm Projection
|
||||
|
||||
`IAlarmSource` is served by `AbCipAlarmProjection`, which polls each subscribed
|
||||
@@ -190,7 +256,7 @@ UDT-typed tags found in the walk.
|
||||
- **Native heap is invisible to the GC.** libplctag's native `Tag` heap does not show up in CLR allocation figures. Watch whole-process RSS, and use `ReinitializeAsync` (tears down + re-creates every device's libplctag handles) as the remediation for native-heap growth. (There is no per-driver footprint number to consult: `IDriver.GetMemoryFootprint()` was deleted as unconsumed in Gitea #525.)
|
||||
- **Handle eviction on failure** — a non-zero libplctag status or a transport exception evicts the cached tag runtime so the next read/write re-creates a fresh handle, mirroring the probe loop's recreate-on-failure behaviour.
|
||||
- **Read statuses carry the libplctag cause** — a tag the controller does not have reads `BadNodeIdUnknown` (not `BadCommunicationError`), and it does **not** open the device's connect-backoff window (the controller answered; before this, one typo throttled every sibling tag on the device). A genuine connection failure still opens it.
|
||||
- **Width check on scalar reads** — libplctag never checks the CIP type the controller returns. A scalar atomic read whose buffer is not the authored type's width (e.g. a REAL authored as INT — the legacy Kepware export does exactly this for some press tags) reads `BadTypeMismatch` instead of Good garbage. Same-width mistakes (DINT vs REAL) are still undetectable.
|
||||
- **Width check on scalar reads** — libplctag never checks the CIP type the controller returns. A scalar atomic read whose buffer is not the authored type's width (e.g. a REAL authored as INT — the legacy Kepware export does exactly this for some press tags) reads `BadTypeMismatch` instead of Good garbage. Same-width mistakes (DINT vs REAL) are undetectable under libplctag; the `Native` transport reports them as `BadTypeMismatch` (see [Native transport behaviour](#native-transport-behaviour)).
|
||||
- **Module-defined I/O tags** — `Local:1:I.Data`, `ABB_OmniCore:I.ConnectionFaulted` and similar colon-bearing base names are valid tag paths (controller scope only).
|
||||
- **User string arrays** — `STR0016[N]` and similar arrays stride by the actual element size (read buffer ÷ count); libplctag.NET 1.5.2 leaves `ElementSize` unset after a read.
|
||||
- **Declaration-only UDT grouping is a footgun unless verified** — only enable `EnableDeclarationOnlyUdtGrouping` when every UDT's member declaration order has been hand-verified against the controller's compiled layout.
|
||||
|
||||
Reference in New Issue
Block a user