Files
lmxopcua/docs/AlarmTracking.md
Joseph Doherty 89004c052c docs: alarms-over-gateway completion banner + AlarmTracking v2 (PR B.5)
Sixteenth PR of the alarms-over-gateway epic
(docs/plans/alarms-over-gateway.md). Closes the documentation sweep
the plan calls for.

- docs/AlarmTracking.md — promoted top-level v2-final architecture
  doc (was a worktree-only draft pre-epic). Covers the three alarm
  sources (Galaxy MxAccess driver-native / Galaxy sub-attribute
  fallback / scripted alarms), how they converge on
  AlarmConditionService, the Acknowledge routing decision in
  DriverNodeManager (driver-native preferred over IWritable
  sub-attribute fallback), the sidecar historian write-back path
  for non-Galaxy producers, and cross-references to the plan +
  v1 archive.
- docs/v1/AlarmTracking.md — banner pointing readers at the v2
  doc; preserved as historical record.
- docs/drivers/Galaxy.md — capability list updated to include
  IAlarmSource (now eight capabilities, restored by B.2). Replaced
  the "IAlarmSource retired in 7.2" sentence with the restoration
  note + cross-link to docs/AlarmTracking.md.
- docs/plans/alarms-over-gateway.md — completion banner at the
  top of the plan, marking 14 of 16 PRs shipped 2026-04-30 and
  noting that A.2 + A.4 + D.1 are the hardware-gated follow-up.

Memory entries updated separately:
- project_alarms_over_gateway_epic.md (new) — epic summary +
  per-PR digest.
- project_galaxy_via_mxgateway.md — added "Alarms restored"
  bullet pointing at the new architecture.
- project_server_history_alarm_subsystems.md — bullet 2 updated
  to describe the new ack-routing decision (B.3) + bullet 3
  added describing the historian write-back path that B.4 + C.1
  + C.2 light up.
- MEMORY.md index — new pointer entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 21:09:04 -04:00

6.4 KiB

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) landed. The v1 architecture (Galaxy.Host's COM-side GalaxyAlarmTracker) is preserved at docs/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_TRANSITIONEventPump → driver OnAlarmEventAlarmConditionService
Galaxy sub-attribute fallback IWritable writes to $Alarm* sub-attributes gateway data subscription → driver OnDataChangeDriverNodeManager 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 IAlarmSourceDriverAlarmSourceAcknowledger 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 IAlarmSourceDriverWritableAcknowledger 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