@@ -101,8 +101,17 @@ otopcua-ablegacy-cli read -g ab://192.168.1.20/1,0 -a L19:0 -t Long
|
||||
|
||||
# Timer ACC
|
||||
otopcua-ablegacy-cli read -g ab://192.168.1.20/1,0 -a T4:0.ACC -t TimerElement
|
||||
|
||||
# Diagnostic counter (PR ablegacy-10 / #253). The seven _Diagnostics/<name>
|
||||
# addresses live alongside user tags — short-circuit serves them straight from
|
||||
# the in-process counter store, so no PCCC frame is sent to the PLC.
|
||||
otopcua-ablegacy-cli read -g ab://192.168.1.20/1,0 --address _Diagnostics/RequestCount
|
||||
```
|
||||
|
||||
The diagnostic surface auto-emits per device — no config required. See
|
||||
`docs/drivers/AbLegacy-Diagnostics.md` for the full counter table + reset
|
||||
semantics + collision-rejection rules.
|
||||
|
||||
### `write`
|
||||
|
||||
```powershell
|
||||
|
||||
112
docs/drivers/AbLegacy-Diagnostics.md
Normal file
112
docs/drivers/AbLegacy-Diagnostics.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# 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. 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`](../../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)
|
||||
@@ -49,6 +49,16 @@ supplies a `FakeAbLegacyTag`.
|
||||
- `AbLegacyHostAndStatusTests` — probe + host-status transitions driven by
|
||||
fake-returned statuses
|
||||
- `AbLegacyDriverTests` — `IDriver` lifecycle
|
||||
- `AbLegacyDiagnosticsTests` — PR ablegacy-10 / #253 per-device diagnostic
|
||||
counters: 5 reads (3 ok / 2 fail) → `RequestCount=5`, `ResponseCount=3`,
|
||||
`ErrorCount=2`; `LastErrorCode` reflects the most recent libplctag status;
|
||||
`RetryCount` increments per retry attempt beyond the first; counters reset
|
||||
on `ReinitializeAsync`; discovery emits exactly 7 diagnostic variables per
|
||||
device under `_Diagnostics/`; collision rejection at `InitializeAsync` for
|
||||
user tags shadowing reserved names or `_Diagnostics/` addresses; the
|
||||
`_Diagnostics/<host>/<name>` short-circuit returns the live snapshot through
|
||||
`ReadAsync` without bumping `RequestCount`; two devices keep counters
|
||||
independent.
|
||||
- `AbLegacyDeadbandTests` — PR 8 per-tag deadband / change filter:
|
||||
absolute-only suppression sequence `[10.0, 10.5, 11.5, 11.6] -> [10.0, 11.5]`,
|
||||
percent-only suppression with a zero-prev short-circuit, both-set logical-OR
|
||||
|
||||
Reference in New Issue
Block a user