docs(alarms): #656 re-statement flag + ActiveAcknowledged kind (AlarmTracking, OpcUaClient, Galaxy, ScriptedAlarms, CLAUDE.md)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -1132,7 +1132,13 @@ host logger factory (wired in `DriverFactoryBootstrap`; the FOCAS and TwinCAT fa
|
|||||||
`ForwardNativeAlarm` writes no second "Activated" `/alerts` row — every native driver's told-record dies with its
|
`ForwardNativeAlarm` writes no second "Activated" `/alerts` row — every native driver's told-record dies with its
|
||||||
instance, and a deploy respawns the driver. Only `Retrigger` (or inactive → active) un-acks, so a source whose
|
instance, and a deploy respawns the driver. Only `Retrigger` (or inactive → active) un-acks, so a source whose
|
||||||
UPSTREAM is authoritative for the ack sends Retrigger for an unacked re-statement: OpcUaClient on first sight of a
|
UPSTREAM is authoritative for the ack sends Retrigger for an unacked re-statement: OpcUaClient on first sight of a
|
||||||
condition (no prior told), Galaxy for an unacked active snapshot entry.
|
condition (no prior told), Galaxy for an unacked active snapshot entry. **Those two also flag
|
||||||
|
`AlarmEventArgs.IsRestatement` (#656)** — history-only: a flagged Retrigger on an Active+unacked condition with the
|
||||||
|
same message AND severity, or a flagged Clear on an inactive one, writes no `/alerts` row (every Galaxy reconnect used
|
||||||
|
to duplicate them). Active+acked is the atomic **`AlarmTransitionKind.ActiveAcknowledged`** (Galaxy snapshot
|
||||||
|
`ActiveAcked`, OpcUaClient inactive→acked-active) — never Acknowledge (kept a cold host's `Active=false`: the alarm was
|
||||||
|
invisible) nor Raise+Acknowledge (an unacked intermediate). `NativeAlarmProjector` returns the rows to write; unflagged
|
||||||
|
sources (FOCAS/MTConnect/AbCip, live Galaxy) are unchanged. E2E: `Runtime.Tests/NativeAlarmRestatementEndToEndTests`.
|
||||||
- **Re-Initialize without teardown (#630):** FOCAS, AbLegacy, AbCip, TwinCAT, S7 and OpcUaClient `InitializeAsync`
|
- **Re-Initialize without teardown (#630):** FOCAS, AbLegacy, AbCip, TwinCAT, S7 and OpcUaClient `InitializeAsync`
|
||||||
first release the prior device/session state (cancel, ≤ 5 s join, dispose — incl. the AbCip alarm projection;
|
first release the prior device/session state (cancel, ≤ 5 s join, dispose — incl. the AbCip alarm projection;
|
||||||
FOCAS's is PAUSED, its told record kept, #647) — the device-state half of `ShutdownAsync` only; the poll engine is kept and the actor re-subscribes
|
FOCAS's is PAUSED, its told record kept, #647) — the device-state half of `ShutdownAsync` only; the poll engine is kept and the actor re-subscribes
|
||||||
|
|||||||
@@ -67,6 +67,25 @@ same condition is wired as an **event notifier of every referencing equipment fo
|
|||||||
counter) would un-ack on every respawn — conservative, never hiding an alarm — and a source whose message omits
|
counter) would un-ack on every respawn — conservative, never hiding an alarm — and a source whose message omits
|
||||||
what changed (a new alarm under identical text) would keep a stale ack across a respawn. A native source must also set `Kind`: an
|
what changed (a new alarm under identical text) would keep a stale ack across a respawn. A native source must also set `Kind`: an
|
||||||
`Unspecified` kind projects no state change.
|
`Unspecified` kind projects no state change.
|
||||||
|
- **Snapshot / first-sight re-statements (#656).** A source that RE-STATES upstream's current state — Galaxy's
|
||||||
|
reconnect snapshot entries, OpcUaClient's first event per condition after a start or deploy respawn — flags
|
||||||
|
`AlarmEventArgs.IsRestatement` (optional, default `false`; FOCAS, MTConnect, AbCip and live Galaxy transitions never
|
||||||
|
set it). The flag governs **history only, never node state**: the projector still projects, but a flagged `Retrigger`
|
||||||
|
on a condition already Active+unacked with the **same message AND severity**, or a flagged `Clear` on an
|
||||||
|
already-inactive condition, writes no `/alerts` row. Before this every gateway reconnect wrote a duplicate
|
||||||
|
"Activated" (and "Cleared") row per alarm while clients saw no event. A **live** `Retrigger` is a genuine
|
||||||
|
re-occurrence and keeps its row; a changed severity (an MTConnect Warning→Fault that keeps its text) does too.
|
||||||
|
Residual: a Galaxy alarm that genuinely re-occurred during an outage and is still Active+unacked with identical
|
||||||
|
text/severity at reconnect loses that row (indistinguishable upstream; the node shows no change either). A process
|
||||||
|
restart still writes a fresh "Activated" for every already-active alarm — the projector has no memory, correctly.
|
||||||
|
- **`AlarmTransitionKind.ActiveAcknowledged` (#656)** — "active AND acknowledged" in ONE projection (one node write,
|
||||||
|
at most one client event). `NativeAlarmProjector.Project(..., out historyKinds)` expands it into the rows that
|
||||||
|
actually changed: "Activated" (stamped `OriginalRaiseTimestampUtc`, no operator comment) only if the condition
|
||||||
|
was not already showing that alarm active, then "Acknowledged" (carrying the comment) only if it was not acked.
|
||||||
|
Galaxy maps a snapshot `ActiveAcked` entry to it — it used to map to `Acknowledge`, which kept the host's prior
|
||||||
|
`Active = false` on a cold host or an inactive prior, so an alarm active in AVEVA projected **inactive** and was
|
||||||
|
invisible to clients. OpcUaClient emits it for inactive → (active, acked) instead of `Raise` + `Acknowledge`,
|
||||||
|
which showed clients an Active+**unacked** intermediate event.
|
||||||
|
|
||||||
### Native condition state survives a full rebuild (Gitea #641)
|
### Native condition state survives a full rebuild (Gitea #641)
|
||||||
|
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ engine graded 1000 published 900 while its `/alerts` row said 1000. That re-buck
|
|||||||
### Runtime flow
|
### Runtime flow
|
||||||
|
|
||||||
The driver's live `IAlarmSource.OnAlarmEvent` is the sole input. Transition kinds
|
The driver's live `IAlarmSource.OnAlarmEvent` is the sole input. Transition kinds
|
||||||
(`Raise`, `Clear`, `Acknowledge`, `Retrigger`) drive the condition's Active /
|
(`Raise`, `Clear`, `Acknowledge`, `Retrigger`, `ActiveAcknowledged`) drive the condition's Active /
|
||||||
Acknowledged / Severity / Message fields via `NativeAlarmProjector`, a pure
|
Acknowledged / Severity / Message fields via `NativeAlarmProjector`, a pure
|
||||||
per-condition state projector. The projected `AlarmConditionSnapshot` is forwarded
|
per-condition state projector. The projected `AlarmConditionSnapshot` is forwarded
|
||||||
through `DriverHostActor` → `OpcUaPublishActor.AlarmStateUpdate` →
|
through `DriverHostActor` → `OpcUaPublishActor.AlarmStateUpdate` →
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ Full per-field descriptions live in `Config/GalaxyDriverOptions.cs`. The full JS
|
|||||||
|
|
||||||
The session-less alarm feed (`GatewayGalaxyAlarmFeed`) and alarm acknowledger (`GatewayGalaxyAlarmAcknowledger`) run on a separate always-on `_ownedMxClient` that is intentionally **not** recreated when `ReconnectSupervisor` rebuilds the worker session. The feed has its own re-invoke loop (~5 s backoff) that reopens `StreamAlarms` after any stream fault, and gRPC.NET's channel auto-reconnect recovers the underlying HTTP/2 connection after a gateway restart — so both the alarm stream and individual ack calls recover without client replacement. The acknowledger is completely stateless between calls: each `AcknowledgeAsync` issues a single unary RPC and returns; no dead-client state is latched on failure. Channel-level keepalive hardening (TCP keep-alive intervals, gRPC ping frames) would require exposing additional knobs on `MxGatewayClientOptions` in the sibling `mxaccessgw` repo — a future option if flaky long-lived connections are observed in production.
|
The session-less alarm feed (`GatewayGalaxyAlarmFeed`) and alarm acknowledger (`GatewayGalaxyAlarmAcknowledger`) run on a separate always-on `_ownedMxClient` that is intentionally **not** recreated when `ReconnectSupervisor` rebuilds the worker session. The feed has its own re-invoke loop (~5 s backoff) that reopens `StreamAlarms` after any stream fault, and gRPC.NET's channel auto-reconnect recovers the underlying HTTP/2 connection after a gateway restart — so both the alarm stream and individual ack calls recover without client replacement. The acknowledger is completely stateless between calls: each `AcknowledgeAsync` issues a single unary RPC and returns; no dead-client state is latched on failure. Channel-level keepalive hardening (TCP keep-alive intervals, gRPC ping frames) would require exposing additional knobs on `MxGatewayClientOptions` in the sibling `mxaccessgw` repo — a future option if flaky long-lived connections are observed in production.
|
||||||
|
|
||||||
|
Every (re)open of `StreamAlarms` replays the gateway's active-alarm snapshot. `GatewayGalaxyAlarmFeed` maps each entry to a transition flagged `IsRestatement` (#656): `Active` → `Retrigger` (upstream's unack is authoritative), `ActiveAcked` → `ActiveAcknowledged` (one atomic active+acked projection — the old `Acknowledge` mapping projected an acked alarm **inactive** on a cold host), `Inactive` → `Clear`. The host writes no `/alerts` row for a replayed entry that changes nothing the condition shows, so a gateway reconnect no longer duplicates history; live `Transition` messages are never flagged. The gateway folds UnackRtn and AckRtn into `Inactive`, so an inactive-unacked alarm cannot be distinguished at reconnect (gateway contract limit).
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
- **Unit tests**: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/` — fakes the gateway gRPC surface; covers Browse, Runtime, Health, and Config in isolation.
|
- **Unit tests**: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/` — fakes the gateway gRPC surface; covers Browse, Runtime, Health, and Config in isolation.
|
||||||
|
|||||||
@@ -101,7 +101,10 @@ initialise (the AdminUI form drops them on the next save). Pinned by
|
|||||||
> EventId** (Part 9 §5.7.3). The event's `ActiveState/Id` + `AckedState/Id` are selected and turned into the
|
> EventId** (Part 9 §5.7.3). The event's `ActiveState/Id` + `AckedState/Id` are selected and turned into the
|
||||||
> `Raise`/`Acknowledge`/`Clear`/`Retrigger` kinds the host projector consumes (without a kind no upstream alarm could ever
|
> `Raise`/`Acknowledge`/`Clear`/`Retrigger` kinds the host projector consumes (without a kind no upstream alarm could ever
|
||||||
> show active); the kinds are emitted from the state **last told to the host** per RawPath, so an identical re-delivery
|
> show active); the kinds are emitted from the state **last told to the host** per RawPath, so an identical re-delivery
|
||||||
> emits nothing (no re-raise, which would un-ack). Condition **branch** events (non-null `BranchId`) are ignored.
|
> emits nothing (no re-raise, which would un-ack). An inactive → active+acked move is ONE `ActiveAcknowledged` (#656; it
|
||||||
|
> used to be `Raise` + `Acknowledge`, a client-visible unacked intermediate). The first event per condition after a
|
||||||
|
> start or deploy respawn is flagged `IsRestatement`, so the host writes no duplicate "Activated" row when its
|
||||||
|
> condition already shows that state (see `docs/AlarmTracking.md`). Condition **branch** events (non-null `BranchId`) are ignored.
|
||||||
> **`ConditionRefresh`** is called after the event item is created, after every in-place reconnect, and on a
|
> **`ConditionRefresh`** is called after the event item is created, after every in-place reconnect, and on a
|
||||||
> `RefreshRequired` event, so a condition already active upstream is reported at subscribe; `RefreshStart`/`RefreshEnd`
|
> `RefreshRequired` event, so a condition already active upstream is reported at subscribe; `RefreshStart`/`RefreshEnd`
|
||||||
> markers are dropped. A server without it (`BadNotSupported`/`BadMethodInvalid`/…) is logged once and tolerated.
|
> markers are dropped. A server without it (`BadNotSupported`/`BadMethodInvalid`/…) is logged once and tolerated.
|
||||||
|
|||||||
Reference in New Issue
Block a user