Files
lmxopcua/docs/AlarmTracking.md
Joseph Doherty 985b7aba26 Doc refresh (task #202) — core architecture docs for multi-driver OtOpcUa
Rewrite seven core-architecture docs to match the shipped multi-driver platform.
The v1 single-driver LmxNodeManager framing is replaced with the Core +
capability-interface model — Galaxy is now one driver of seven, and each doc
points at the current class names + source paths.

What changed per file:
- OpcUaServer.md — OtOpcUaServer as StandardServer host; per-driver
  DriverNodeManager + CapabilityInvoker wiring; Config-DB-driven configuration
  (sp_PublishGeneration, DraftRevisionToken, Admin UI); Phase 6.2
  AuthorizationGate integration.
- AddressSpace.md — GenericDriverNodeManager.BuildAddressSpaceAsync walks
  ITagDiscovery.DiscoverAsync and streams DriverAttributeInfo through
  IAddressSpaceBuilder; CapturingBuilder registers alarm-condition sinks;
  per-driver NodeId schemes replace the fixed ns=1;s=ZB root.
- ReadWriteOperations.md — OnReadValue / OnWriteValue dispatch to
  IReadable.ReadAsync / IWritable.WriteAsync through CapabilityInvoker,
  honoring WriteIdempotentAttribute (#143); two-layer authorization
  (WriteAuthzPolicy + Phase 6.2 AuthorizationGate).
- Subscriptions.md — ISubscribable.SubscribeAsync/UnsubscribeAsync is the
  capability surface; STA-thread story is now Galaxy-specific (StaPump inside
  Driver.Galaxy.Host), other drivers are free-threaded.
- AlarmTracking.md — IAlarmSource is optional; AlarmSurfaceInvoker wraps
  Subscribe/Ack/Unsubscribe with fan-out by IPerCallHostResolver and the
  no-retry AlarmAcknowledge pipeline (#143); CapturingBuilder registers sinks
  at build time.
- DataTypeMapping.md — DriverDataType + SecurityClassification are the
  driver-agnostic enums; per-driver mappers (GalaxyProxyDriver inline,
  AbCipDataType, ModbusDriver, etc.); SecurityClassification is metadata only,
  ACL enforcement is at the server layer.
- IncrementalSync.md — IRediscoverable covers backend-change signals;
  sp_ComputeGenerationDiff + DiffViewer drive generation-level change
  detection; IDriver.ReinitializeAsync is the in-process recovery path.
2026-04-20 01:33:28 -04:00

6.2 KiB

Alarm Tracking

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.

IAlarmSource surface

Task<IAlarmSubscriptionHandle> SubscribeAlarmsAsync(
    IReadOnlyList<string> sourceNodeIds, CancellationToken cancellationToken);
Task UnsubscribeAlarmsAsync(IAlarmSubscriptionHandle handle, CancellationToken cancellationToken);
Task AcknowledgeAsync(IReadOnlyList<AlarmAcknowledgeRequest> acknowledgements,
    CancellationToken cancellationToken);
event EventHandler<AlarmEventArgs>? OnAlarmEvent;

The driver fires OnAlarmEvent for every transition (Active, Acknowledged, Inactive) with an AlarmEventArgs carrying the source node id, condition id, alarm type, message, severity (AlarmSeverity enum), and source timestamp.

AlarmSurfaceInvoker

AlarmSurfaceInvoker (src/ZB.MOM.WW.OtOpcUa.Core/Resilience/AlarmSurfaceInvoker.cs) wraps the three mutating surfaces through CapabilityInvoker:

  • SubscribeAlarmsAsync / UnsubscribeAlarmsAsync run through the DriverCapability.AlarmSubscribe pipeline — retries apply under the tier configuration.
  • AcknowledgeAsync runs through DriverCapability.AlarmAcknowledge which does NOT retry per decision #143. A timed-out ack may have already registered at the plant floor; replay would silently double-acknowledge.

Multi-host fan-out: when the driver implements IPerCallHostResolver, each source node id is resolved individually and batches are grouped by host so a dead PLC inside a multi-device driver doesn't poison sibling breakers. Single-host drivers fall back to IDriver.DriverInstanceId as the pipeline-key host.

Condition-node creation via CapturingBuilder

Alarm-condition nodes are materialized at address-space build time. During GenericDriverNodeManager.BuildAddressSpaceAsync the builder is wrapped in a CapturingBuilder that observes every Variable() call. When a driver calls IVariableHandle.MarkAsAlarmCondition(AlarmConditionInfo) on a returned handle, the server-side DriverNodeManager.VariableHandle creates a sibling AlarmConditionState node and returns an IAlarmConditionSink. The wrapper stores the sink in _alarmSinks keyed by the variable's full reference, then GenericDriverNodeManager registers a forwarder on IAlarmSource.OnAlarmEvent that routes each push to the matching sink by SourceNodeId. Unknown source ids are dropped silently — they may belong to another driver.

The AlarmConditionState layout matches OPC UA Part 9:

  • SourceNode → the originating variable
  • SourceName / ConditionName → from AlarmConditionInfo.SourceName
  • Initial state: enabled, inactive, acknowledged, severity per InitialSeverity, retain false
  • HasCondition references wire the source variable ↔ the condition node bidirectionally

Drivers flag alarm-bearing variables at discovery time via DriverAttributeInfo.IsAlarm = true. The Galaxy driver, for example, sets this on attributes that have an AlarmExtension primitive in the Galaxy repository DB; FOCAS sets it on the CNC alarm register.

State transitions

ConditionSink.OnTransition runs under the node manager's Lock and maps the AlarmEventArgs.AlarmType string to Part 9 state:

AlarmType Action
Active SetActiveState(true), SetAcknowledgedState(false), Retain = true
Acknowledged SetAcknowledgedState(true)
Inactive SetActiveState(false); Retain = false once both inactive and acknowledged

Severity is remapped: AlarmSeverity.Low/Medium/High/Critical → OPC UA numeric 250 / 500 / 700 / 900. Message.Value is set from AlarmEventArgs.Message on every transition. ClearChangeMasks(true) and ReportEvent(condition) fire the OPC UA event notification for clients subscribed to any ancestor notifier.

Acknowledge dispatch

Alarm acknowledgement initiated by an OPC UA client flows:

  1. The SDK invokes the AlarmConditionState.OnAcknowledge method delegate.
  2. The handler checks the session's roles for AlarmAck — drivers never see a request the session wasn't entitled to make.
  3. AlarmSurfaceInvoker.AcknowledgeAsync is called with the source / condition / comment tuple. The invoker groups by host and runs each batch through the no-retry AlarmAcknowledge pipeline.

Drivers return normally for success or throw to signal the ack failed at the backend.

EventNotifier propagation

Drivers that want hierarchical alarm subscriptions propagate EventNotifier.SubscribeToEvents up the containment chain during discovery — the Galaxy driver flips the flag on every ancestor of an alarm-bearing object up to the driver root, mirroring v1 behavior. Clients subscribed at the driver root, a mid-level folder, or the Objects/ root see alarm events from every descendant with an AlarmConditionState sibling. The driver-root FolderState is created in DriverNodeManager.CreateAddressSpace with EventNotifier = SubscribeToEvents | HistoryRead so alarm event subscriptions and alarm history both have a single natural target.

ConditionRefresh

The OPC UA ConditionRefresh service queues the current state of every retained condition back to the requesting monitored items. DriverNodeManager iterates the node manager's AlarmConditionState collection and queues each condition whose Retain.Value == true — matching the Part 9 requirement.

Key source files

  • src/ZB.MOM.WW.OtOpcUa.Core.Abstractions/IAlarmSource.cs — capability contract + AlarmEventArgs
  • src/ZB.MOM.WW.OtOpcUa.Core/Resilience/AlarmSurfaceInvoker.cs — per-host fan-out + no-retry ack
  • src/ZB.MOM.WW.OtOpcUa.Core/OpcUa/GenericDriverNodeManager.csCapturingBuilder + alarm forwarder
  • src/ZB.MOM.WW.OtOpcUa.Server/OpcUa/DriverNodeManager.csVariableHandle.MarkAsAlarmCondition + ConditionSink
  • src/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host/Backend/Alarms/GalaxyAlarmTracker.cs — Galaxy-specific alarm-event production