Auto: abcip-4.3 — diagnostic / system tags as browseable variables

Closes #240
This commit is contained in:
Joseph Doherty
2026-04-26 02:55:56 -04:00
parent 9c108cd00a
commit 901a5b9b21
10 changed files with 915 additions and 10 deletions

View File

@@ -297,3 +297,68 @@ the equality threshold too loose; revisit the per-tag config.
- Modbus driver — read-side deadband in `ModbusDriver` predates this
write-side equivalent; the config shape is intentionally similar.
- Kepware "Deadband (write)" knob — this is the AB CIP equivalent.
## System tags / `_System` folder
PR abcip-4.3 surfaces five read-only diagnostic variables under
`AbCip/<device>/_System/` so SCADA / Admin clients can pivot from "is the
wire up?" to "what's our scan rate / tag count?" without leaving the OPC UA
address space. The values come straight from the live
`IHostConnectivityProbe` + `DriverHealth` surfaces — reads bypass libplctag
and are served from the in-memory snapshot the probe loop / read loop
updates.
### What it ships
| Variable | Type | Source | Notes |
|---|---|---|---|
| `_ConnectionStatus` | String | `HostState` | `Running` / `Stopped` / `Unknown` / `Faulted`. Mirrors what the connectivity probe sees. |
| `_ScanRate` | Float64 | `AbCipProbeOptions.Interval` | Configured probe interval in milliseconds — compare against `_LastScanTimeMs` to spot wire stretch. |
| `_TagCount` | Int32 | `_tagsByName` | Discovered tag count for this device, excluding `_System/*`. |
| `_DeviceError` | String | `DriverHealth.LastError` | Most recent error message; empty when the device is healthy. |
| `_LastScanTimeMs` | Float64 | `ReadAsync` wall-clock | Duration of the most-recent `ReadAsync` iteration on this device. |
### When the snapshot updates
- **Probe transitions** — every `Running ↔ Stopped` flip refreshes the
device's snapshot inline, so a client subscribed to
`_System/_ConnectionStatus` sees the new state on the next OPC UA
publish tick.
- **Read iterations** — `ReadAsync` recomputes `_LastScanTimeMs` per
device that owned at least one reference in the batch + writes a fresh
snapshot before returning.
- **Driver init** — every device gets a seeded snapshot
(`Unknown` / `0` / `""`) before the probe loop spins up so a read that
arrives before the first probe iteration returns a stable shape rather
than null.
### Browse + read example
```powershell
# Browse the synthetic folder
otopcua-client-cli browse -u opc.tcp://localhost:4840 \
-n "ns=2;s=AbCip/ab://10.0.0.5/1,0/_System"
# Read the connection status
otopcua-client-cli read -u opc.tcp://localhost:4840 \
-n "ns=2;s=AbCip/ab://10.0.0.5/1,0/_System/_ConnectionStatus"
```
The driver-side reference embeds the device host address (the
`_System/<device>/<name>` form) so the dispatcher can route by device
without an additional registry. PR abcip-4.4 will turn `_RefreshTagDb` into
a writeable refresh trigger; everything 4.3 ships is `ViewOnly`.
### Verification
- **Unit**: `AbCipSystemTagSourceTests`
(`tests/.../AbCip.Tests`) — covers snapshot round-trip, two-device
isolation, recognised-name lookup, default-shape on unseeded devices,
discovery emits the five canonical nodes, and `ReadAsync` dispatches
through the source instead of libplctag.
- **Integration**: `AbCipSystemTagDiscoveryTests`
(`tests/.../AbCip.IntegrationTests`) — `[AbServerFact]` connects to a
real `ab_server`, browses `_System/`, reads each variable, asserts
every one returns Good with a non-null value.
- **E2E**: `scripts/e2e/test-abcip.ps1` — see the *SystemTagBrowse*
assertion.