Files
lmxopcua/docs/drivers/AbLegacy-Diagnostics.md
2026-04-26 03:50:47 -04:00

113 lines
5.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 `Address` starts 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
```powershell
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)
```powershell
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. 510 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`](../../src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDiagnosticTags.cs)
— counter store + read short-circuit
- [`AbLegacyDriver.cs`](../../src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDriver.cs)
— increment sites in `ReadAsync`, discovery emission in `DiscoverAsync`
- [`AbLegacy-Test-Fixture.md`](AbLegacy-Test-Fixture.md) — `AbLegacyDiagnosticsTests`
+ collision-rejection contract
- [AB CIP `_System/` parallel](../../src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipSystemTagSource.cs)
— same pattern with the CIP-specific six entries (incl. writeable
`_RefreshTagDb` trigger)