diff --git a/docs/AlarmTracking.md b/docs/AlarmTracking.md new file mode 100644 index 0000000..6141492 --- /dev/null +++ b/docs/AlarmTracking.md @@ -0,0 +1,129 @@ +# Alarm tracking — v2 final architecture + +This document describes how OtOpcUa surfaces alarms to OPC UA Part 9 +clients after the **alarms-over-gateway** epic +([docs/plans/alarms-over-gateway.md](plans/alarms-over-gateway.md)) +landed. The v1 architecture (Galaxy.Host's COM-side `GalaxyAlarmTracker`) +is preserved at [docs/v1/AlarmTracking.md](v1/AlarmTracking.md) for +historical reference. + +## Three alarm sources, one OPC UA Part 9 surface + +| Source | Driver capability | Path | +|----------------------------------|--------------------------|------| +| **Galaxy MxAccess (driver-native)** | `GalaxyDriver : IAlarmSource` | gateway → worker → MxAccess alarm sink → `MX_EVENT_FAMILY_ON_ALARM_TRANSITION` → `EventPump` → driver `OnAlarmEvent` → `AlarmConditionService` | +| **Galaxy sub-attribute fallback** | `IWritable` writes to `$Alarm*` sub-attributes | gateway data subscription → driver `OnDataChange` → `DriverNodeManager` ConditionSink → `AlarmConditionService` | +| **Scripted alarms** | `Phase7EngineComposer` | server-side script evaluator → `Phase7EngineComposer.RouteToHistorianAsync` + `AlarmConditionService` | + +All three converge on `AlarmConditionService` (`src/ZB.MOM.WW.OtOpcUa.Server/Alarms/AlarmConditionService.cs`), +which owns the OPC UA Part 9 state machine and dispatches transitions +to the OPC UA condition node managers. Driver-native transitions take +precedence over sub-attribute synthesis when both arrive for the same +condition — the dedup logic prefers the richer driver-native record +because it carries the full operator + raise-time + category metadata +that the value-driven path collapses. + +## Galaxy driver path (driver-native) + +Restored in PR B.2 of the epic. `GalaxyDriver` implements +`IAlarmSource` with these surfaces: + +- `SubscribeAlarmsAsync(sourceNodeIds)` → returns a sentinel handle. + The driver doesn't multiplex per source-node-id today; every + active handle observes the gateway's alarm-event stream. The + server-side `AlarmConditionService` filters by source-node before + raising the OPC UA condition. +- `UnsubscribeAlarmsAsync(handle)` → symmetric handle removal. +- `AcknowledgeAsync(requests)` → routes one gateway RPC per + acknowledgement through `IGalaxyAlarmAcknowledger`. Production + uses `GatewayGalaxyAlarmAcknowledger` calling + `MxGatewayClient.AcknowledgeAlarmAsync` (PR E.2 SDK method). +- `OnAlarmEvent` → bridges `EventPump.OnAlarmTransition` (PR B.1) + onto `AlarmEventArgs`. Suppressed when no alarm subscription is + active so untracked transitions don't leak through. + +The proto contract carries the rich payload — alarm full reference, +source-object reference, alarm-type-name, transition kind (Raise / +Acknowledge / Clear / Retrigger), severity (raw MxAccess scale), +original raise timestamp, transition timestamp, operator user, +operator comment, alarm category, description. `MxAccessSeverityMapper` +(PR B.1) translates the raw severity onto the four-bucket +`AlarmSeverity` ladder — boundaries match v1's `GalaxyAlarmTracker` +so customers see no surprise re-classification. + +The richer fields surface on `Core.Abstractions.AlarmEventArgs` via +the optional properties added in PR E.7 (`OperatorComment`, +`OriginalRaiseTimestampUtc`, `AlarmCategory`). Consumers that don't +need them are unaffected; consumers that do (Client.UI, Client.CLI +verbose mode) read the new fields when present. + +## Galaxy sub-attribute fallback + +For Galaxy templates without `$Alarm*` extensions, the value-driven +path stays in place: `DriverNodeManager` registers an +`AlarmConditionState` per Galaxy variable that bears alarm-bearing +sub-attributes (`InAlarm`, `Acked`, `Priority`, `Description`), +subscribes to those sub-attributes, and synthesizes Part 9 transitions +when the values change. This path operated as the only Galaxy alarm +path between PR 7.2 and the alarms-over-gateway epic; it remains the +fallback today. + +When both paths report the same condition, +`AlarmConditionService.AlarmConditionState` keeps the +driver-native record and discards the duplicate sub-attribute +synthesis. Driver-native transitions are richer (carry operator +comment + original raise time) and arrive lower-latency (no +publishing-interval delay on the sub-attribute reads), so they win +the dedup. + +## Acknowledge routing + +`DriverNodeManager` picks the acknowledger when registering each +condition (PR B.3 logic): + +- Driver implements `IAlarmSource` → + `DriverAlarmSourceAcknowledger` routes the operator comment + through `IAlarmSource.AcknowledgeAsync` via the existing + `AlarmSurfaceInvoker` (Phase 6.1 resilience pipeline; no-retry + per decision #143). End-to-end operator-comment fidelity is + preserved. +- Driver doesn't implement `IAlarmSource` → + `DriverWritableAcknowledger` writes the comment into the + `AckMsgWriteRef` sub-attribute via `IWritable.WriteAsync`. Same + resilience pipeline; collapses the comment into a single string + write at the wire level. + +The OPC UA Part 9 `AlarmConditionState.OnAcknowledge` delegate +already validates the session's `AlarmAck` role before dispatching, +so the gateway-side ack RPC only sees authenticated, authorised +calls. + +## Historian write-back (non-Galaxy alarms) + +Scripted alarms (and any future non-Galaxy `IAlarmSource` like +AB CIP ALMD) route to AVEVA Historian via the Wonderware sidecar: + +- `Phase7Composer.ResolveHistorianSink` resolves an + `IAlarmHistorianWriter` from either a driver that natively + implements it or the DI-registered `WonderwareHistorianClient` + (the sidecar IPC client). Driver-provided wins when both are + present. +- `SqliteStoreAndForwardSink` queues each transition to a local + SQLite database and drains in the background via the resolved + writer. +- Sidecar (PR C.1 + C.2) forwards the events to `aahClientManaged`'s + alarm-event write API; the live SDK call site is pinned during + PR D.1's deploy-rig validation. + +Galaxy-native alarms with `$Alarm*` extensions reach AVEVA Historian +directly via System Platform's `HistorizeToAveva` toggle on the +alarm primitive — no involvement from OtOpcUa. This sidecar path is +exclusively for non-Galaxy alarm producers. + +## Cross-references + +- Plan: [docs/plans/alarms-over-gateway.md](plans/alarms-over-gateway.md) +- v1 archive: [docs/v1/AlarmTracking.md](v1/AlarmTracking.md) +- Galaxy driver: [docs/drivers/Galaxy.md](drivers/Galaxy.md) +- Phase 7 scripting + alarming: [docs/v2/implementation/phase-7-scripting-and-alarming.md](v2/implementation/phase-7-scripting-and-alarming.md) +- Security + ACL: [docs/Security.md](Security.md) diff --git a/docs/drivers/Galaxy.md b/docs/drivers/Galaxy.md index 32ddbb3..163d54a 100644 --- a/docs/drivers/Galaxy.md +++ b/docs/drivers/Galaxy.md @@ -15,7 +15,8 @@ For the driver spec (capability surface, config shape, addressing), see [docs/v2 | ITagDiscovery / IReadable / | | IWritable / ISubscribable / | | IRediscoverable / | - | IHostConnectivityProbe | + | IHostConnectivityProbe / | + | IAlarmSource | +-------------------+-------------------+ | gRPC (default http://localhost:5120) @@ -33,7 +34,18 @@ For the driver spec (capability surface, config shape, addressing), see [docs/v2 +---------------------------------------+ ``` -History reads + alarm-condition tracking moved server-side in PR 7.2 (`IHistoryRouter`, `AlarmConditionService`). Galaxy no longer implements `IHistoryProvider` or `IAlarmSource` of its own. +History reads moved server-side in PR 7.2 (`IHistoryRouter`). Galaxy no longer implements `IHistoryProvider` of its own. + +`IAlarmSource` was retired with PR 7.2 and **restored in PR B.2** of the +alarms-over-gateway epic ([docs/plans/alarms-over-gateway.md](../plans/alarms-over-gateway.md)). +Alarm transitions arrive on the same gateway `StreamEvents` channel as +data-change events under the new `MX_EVENT_FAMILY_ON_ALARM_TRANSITION` +family; acknowledgements route through the gateway's +`AcknowledgeAlarm` RPC. The previous value-driven sub-attribute path +remains as a fallback for Galaxy templates without `$Alarm*` +extensions — the server-side `AlarmConditionService` dedups when both +paths fire on the same condition. See [docs/AlarmTracking.md](../AlarmTracking.md) +for the v2-final architecture. ## Project Layout diff --git a/docs/plans/alarms-over-gateway.md b/docs/plans/alarms-over-gateway.md index 4d62a0d..e41ee0f 100644 --- a/docs/plans/alarms-over-gateway.md +++ b/docs/plans/alarms-over-gateway.md @@ -1,5 +1,17 @@ # Plan — alarms over the mxaccessgw gateway +> ✅ **Completed 2026-04-30 — historical record.** +> The 14-PR sequence (A.1 / A.3, B.1 / B.2 / B.3 / B.4, C.1 / C.2, +> E.1 / E.2 / E.3 / E.4 / E.5 / E.6 / E.7) shipped. The gateway-side +> public RPC surface, the driver-native ack path, the sidecar alarm +> historian writer, and the five client SDKs are all live. **A.2** +> (worker MxAccess alarm subscription) and **A.4** (worker +> ConditionRefresh command) require the AVEVA worker host's MxAccess +> Toolkit C++ SDK and ship as a follow-up gated on dev-rig +> validation. **D.1** (refresh `C:\publish` + smoke-run on the dev +> rig) ships once A.2 is hardware-verified. The remainder of this +> document is preserved as the design record. + Coordinated epic across two repos: - **`lmxopcua`** (this repo) — `c:\Users\dohertj2\Desktop\lmxopcua\` diff --git a/docs/v1/AlarmTracking.md b/docs/v1/AlarmTracking.md index c7ccf23..e7d7afd 100644 --- a/docs/v1/AlarmTracking.md +++ b/docs/v1/AlarmTracking.md @@ -1,4 +1,12 @@ -# Alarm Tracking +# Alarm Tracking — v1 archive + +> **Historical record.** This document describes the v1 / pre-PR-7.2 +> Galaxy alarm path that ran inside `Galaxy.Host`'s STA pump as +> `GalaxyAlarmTracker`. PR 7.2 retired the in-process Galaxy stack; the +> alarms-over-gateway epic (B.2 / B.3 / E.7) restored Galaxy's +> `IAlarmSource` capability against the new gateway-mediated transport. +> See [docs/AlarmTracking.md](../AlarmTracking.md) for the v2 final +> architecture — that is the document to read for current behaviour. Alarm surfacing is an optional driver capability exposed via `IAlarmSource` (`src/ZB.MOM.WW.OtOpcUa.Core.Abstractions/IAlarmSource.cs`). Drivers whose backends have an alarm concept implement it — today: Galaxy (MXAccess alarms), FOCAS (CNC alarms), OPC UA Client (A&C events from the upstream server). Modbus / S7 / AB CIP / AB Legacy / TwinCAT do not implement the interface and the feature is simply absent from their subtrees.