Auto: abcip-4.4 — _RefreshTagDb writeable system tag

Closes #241
This commit is contained in:
Joseph Doherty
2026-04-26 03:16:28 -04:00
parent e46e4de31f
commit e0e5e04e48
8 changed files with 877 additions and 45 deletions

View File

@@ -306,17 +306,19 @@ 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.
updates. PR abcip-4.4 added `_RefreshTagDb` as a sixth, writeable entry —
the Kepware-style refresh trigger.
### 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. |
| Variable | Type | Access | Source | Notes |
|---|---|---|---|---|
| `_ConnectionStatus` | String | ViewOnly | `HostState` | `Running` / `Stopped` / `Unknown` / `Faulted`. Mirrors what the connectivity probe sees. |
| `_ScanRate` | Float64 | ViewOnly | `AbCipProbeOptions.Interval` | Configured probe interval in milliseconds — compare against `_LastScanTimeMs` to spot wire stretch. |
| `_TagCount` | Int32 | ViewOnly | `_tagsByName` | Discovered tag count for this device, excluding `_System/*`. |
| `_DeviceError` | String | ViewOnly | `DriverHealth.LastError` | Most recent error message; empty when the device is healthy. |
| `_LastScanTimeMs` | Float64 | ViewOnly | `ReadAsync` wall-clock | Duration of the most-recent `ReadAsync` iteration on this device. |
| `_RefreshTagDb` | Boolean | **Operate** | n/a (write-only trigger) | PR abcip-4.4 — Kepware-style refresh trigger. Reads always return `false`. Writing any truthy value (`true`, non-zero number, `"true"` / `"1"` strings, case-insensitive) dispatches to `RebrowseAsync` against the device's cached `IAddressSpaceBuilder`. Falsy / unparseable writes are no-ops that report `Good` so a UI that resets the trigger flag doesn't see a phantom error. The `AbCip.RefreshTriggers` diagnostic counter increments per truthy write. |
### When the snapshot updates
@@ -346,19 +348,59 @@ otopcua-client-cli read -u opc.tcp://localhost:4840 \
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`.
without an additional registry. PR abcip-4.4 turned `_RefreshTagDb` into
a writeable refresh trigger; the rest of the surface remains `ViewOnly`.
### Refreshing the tag DB via OPC UA write
PR abcip-4.4 wires `_RefreshTagDb` to the same `RebrowseAsync` entry point
the CLI's `rebrowse` command exercises (issue #233). Operators have two
roughly-equivalent ways to force a controller-side `@tags` re-walk after a
program download:
```powershell
# Path A — OPC UA write to the system tag (production / Admin UI path)
otopcua-client-cli write -u opc.tcp://localhost:4840 \
-n "ns=2;s=AbCip/ab://10.0.0.5/1,0/_System/_RefreshTagDb" \
-v true --type Boolean
# Path B — direct CLI rebrowse against a transient driver (admin / debug path)
otopcua-abcip-cli rebrowse -g ab://10.0.0.5/1,0
```
Both paths drop the UDT template cache + re-run the enumerator walk. Path A
is the operator-facing surface (the same `IDriverControl.RebrowseAsync`
contract, just dispatched from the OPC UA write surface instead of an
in-process call). Path B spins up its own driver instance so it doesn't
share the live server's cache, which makes it useful for one-off
controller-side validation.
The `AbCip.RefreshTriggers` driver-diagnostics counter increments per
successful truthy write, so the Admin UI / driver-diagnostics RPC can show
a "Refreshes since boot" tile that pairs naturally with the existing
`WritesSuppressed` / `WritesPassedThrough` write-coalescer counters.
### 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
discovery emits the six canonical nodes, and `ReadAsync` dispatches
through the source instead of libplctag.
- **Unit**: `AbCipRefreshTagDbTests`
(`tests/.../AbCip.Tests`) — PR abcip-4.4 — covers discovery emits the
trigger as Operate, reads always return `false`, truthy/falsy/null write
semantics, the `AbCip.RefreshTriggers` counter, two-device counter
independence, defends-in-depth `BadNotWritable` for read-only system
variables, no-op-Good when no builder is cached yet, and mixed-batch
routing alongside ordinary tag writes.
- **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.
- **Integration**: `AbCipRefreshTagDbTests`
(`tests/.../AbCip.IntegrationTests`) — PR abcip-4.4 — `[AbServerFact]`
drives a `_RefreshTagDb` write, asserts the template cache drops + the
per-device counter advances against a live `ab_server`.
- **E2E**: `scripts/e2e/test-abcip.ps1` — see the *SystemTagBrowse* +
*RefreshTagDbWrite* assertions.