5.3 KiB
AB Legacy diagnostic counters
Per-device diagnostic counters surface as auto-generated read-only OPC UA
variables under each device's synthetic _Diagnostics/ folder. HMIs can bind
directly without going through a separate diagnostics RPC. Mirrors the AB CIP
_System/ pattern from PR abcip-4.3.
Closes #253 (PR ablegacy-10).
The seven counters
Each device managed by the AbLegacyDriver exposes seven read-only nodes under
AbLegacy/<host>/_Diagnostics/<name>:
| Name | Type | Semantics |
|---|---|---|
RequestCount |
Int64 | Total ReadAsync requests issued against this device. One increment per non-diagnostic reference per call, success or failure. |
ResponseCount |
Int64 | Successful read responses. |
ErrorCount |
Int64 | Failed read responses (any non-Good status). |
RetryCount |
Int64 | Retry attempts beyond the first per the PR 9 retry loop. A single read with two retries adds two. |
LastErrorCode |
Int32 | Most recent libplctag status code on a failed read; 0 when no error has been seen since the last reset. |
LastErrorMessage |
String | Most recent libplctag error message on a failed read; empty when no error has been seen since the last reset. |
CommFailures |
Int64 | Count of read failures mapped to BadCommunicationError. Spans transient libplctag throws + retried-out chains so operators see a single "wire fell off" counter. |
Address shape: _Diagnostics/<deviceHostAddress>/<name> —
e.g. _Diagnostics/ab://10.0.0.5/1,0/RequestCount.
The <deviceHostAddress> segment is the canonical ab://host[:port]/cip-path
string from AbLegacyDeviceOptions.HostAddress. The browse path looks like
AbLegacy/<deviceHostAddress>/_Diagnostics/<name> — the same shape as a
user-config tag node, just under a reserved sibling folder.
Reset behaviour
| Trigger | Effect |
|---|---|
ReinitializeAsync |
Every counter for every device resets to zero, plus LastErrorMessage clears to empty. |
ShutdownAsync |
Same as Reinitialize — counters drop with the device map. |
| Driver process restart | Counters start at zero. |
| Probe transition Stopped→Running | No automatic reset — counters are cumulative across reconnect events so operators can spot intermittent links by watching CommFailures keep climbing. |
There is no in-process "reset" RPC at the time of writing. If you need to
clear counters without a redeploy, kick a ReinitializeAsync from the Admin
RPC surface — the driver re-EnsureDevice's each host so the freshly registered
counters start at zero.
What does not increment counters
Reads against _Diagnostics/<host>/<name> are driver-local observability,
not field traffic — they short-circuit before the libplctag dispatch and do
NOT increment RequestCount or any other counter. Otherwise a 1 Hz HMI poll
of RequestCount would make the counter chase its own tail.
Writes against _Diagnostics/* are rejected with BadNotWritable because
every diagnostic node is SecurityClassification.ViewOnly — a misbehaving
SCADA template can't accidentally clobber the diagnostic surface.
Collision with user tags
User-config tags must not shadow the seven reserved diagnostic names and
must not live under the synthetic _Diagnostics/ folder. Both shapes are
rejected at InitializeAsync time with a clear InvalidOperationException:
- A tag named
RequestCount(or any of the other six reserved names) is rejected because it would silently never resolve at read time — the diagnostics short-circuit wins. - A tag whose
Addressstarts with_Diagnostics/is rejected because the whole prefix is owned by the auto-emitted counters.
Pick a different name (SiteRequestCount, MachineRequestCount) or a
different address path (real PCCC files like N7:0).
HMI binding examples
OPC UA Client CLI
dotnet run --project src/ZB.MOM.WW.OtOpcUa.Client.CLI -- read `
-u opc.tcp://localhost:4840 `
-n "ns=2;s=AbLegacy/ab://10.0.0.5/1,0/_Diagnostics/RequestCount"
AB Legacy CLI (driver-direct, no OPC UA layer)
dotnet run --project src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Cli -- read `
-g "ab://10.0.0.5/1,0" -P Slc500 `
--address "_Diagnostics/RequestCount"
The driver-direct path lets you sanity-check the counter without standing up an OPC UA server — useful when triaging a wire-level issue on the bench.
Subscription pattern
Subscribe to all seven counters at a slow rate (e.g. 5–10 s) on a long-lived
overview dashboard, plus a faster rate (1 s) on LastErrorMessage /
LastErrorCode when actively debugging a flapping link. The diagnostics
short-circuit makes every read O(1) — there's no penalty for fast polling
of the counter itself, only the OPC UA subscription bookkeeping.
Cross-references
AbLegacyDiagnosticTags.cs— counter store + read short-circuitAbLegacyDriver.cs— increment sites inReadAsync, discovery emission inDiscoverAsyncAbLegacy-Test-Fixture.md—AbLegacyDiagnosticsTests- collision-rejection contract
- AB CIP
_System/parallel — same pattern with the CIP-specific six entries (incl. writeable_RefreshTagDbtrigger)