diff --git a/docs/ScriptedAlarms.md b/docs/ScriptedAlarms.md index 1155cd63..c3a7cbf8 100644 --- a/docs/ScriptedAlarms.md +++ b/docs/ScriptedAlarms.md @@ -111,6 +111,71 @@ Two mapping notes specific to this adapter: - `SubscribeAlarmsAsync` accepts a list of source-node-id filters, interpreted as Equipment-path prefixes. Empty list matches every alarm. Each emission is matched against every live subscription — the adapter keeps no per-subscription cursor. - `IAlarmSource.AcknowledgeAsync` does not carry a user identity. The adapter defaults the audit user to `"opcua-client"` so callers using the base interface still produce an audit entry. The server's Part 9 method handlers call the engine's richer `AcknowledgeAsync` / `ConfirmAsync` / `OneShotShelveAsync` / `TimedShelveAsync` / `UnshelveAsync` / `AddCommentAsync` directly with the authenticated principal instead. +## Native driver alarms (equipment-tag path) + +> Design reference: [docs/plans/2026-06-14-galaxy-phase-b-native-alarms-design.md](plans/2026-06-14-galaxy-phase-b-native-alarms-design.md) + +A native driver alarm is an authored equipment `Tag` bound to an `IAlarmSource` +driver (today: Galaxy `GalaxyMxGateway`) whose `TagConfig` carries an `"alarm"` +object alongside the usual `"FullName"`: + +```json +{ "FullName": "TestMachine_002.HiAlarm", "alarm": { "alarmType": "OffNormalAlarm", "severity": 700 } } +``` + +`"alarm"` **absent** → the tag materialises as an ordinary value variable +(unchanged behaviour). `"alarm"` **present** → the tag materialises as a Part 9 +`AlarmConditionState` under its equipment folder **instead of** a value variable. +No EF/schema change is required; the intent rides in the schemaless `TagConfig` +blob and is parsed byte-parity in both the compose (`Phase7Composer`) and deploy +(`DeploymentArtifact`) paths. + +### TagConfig alarm fields + +| Field | Values | Default | +|---|---|---| +| `alarmType` | `"AlarmCondition"`, `"OffNormalAlarm"`, `"DiscreteAlarm"`, `"LimitAlarm"` | `"AlarmCondition"` | +| `severity` | OPC UA 1–1000 scale | `500` | + +An unknown `alarmType` string falls back to the base `AlarmCondition` OPC UA +ObjectType. `severity` seeds the condition's initial severity at materialisation; +the driver's live alarm events may carry a different severity that overrides it at +runtime. + +### Runtime flow + +The driver's live `IAlarmSource.OnAlarmEvent` is the sole input. Transition kinds +(`Raise`, `Clear`, `Acknowledge`, `Retrigger`) drive the condition's Active / +Acknowledged / Severity / Message fields via `NativeAlarmProjector`, a pure +per-condition state projector. The projected `AlarmConditionSnapshot` is forwarded +through `DriverHostActor` → `OpcUaPublishActor.AlarmStateUpdate` → +`OtOpcUaNodeManager.WriteAlarmCondition` — the same delta-gated Part 9 +condition-event path that scripted alarms use, reused verbatim. + +The same `AlarmTransitionEvent` that scripted alarms publish is emitted to the +cluster `alerts` topic, so the AdminUI `/alerts` page and the Historian sink see +native alarm transitions identically. Publication to the `alerts` topic is +**Primary-gated** (only the redundancy Primary publishes cluster-wide transitions); +the OPC UA condition-node write is ungated on all nodes so a Secondary stays warm +for failover. + +The alarm is authored on the `Tags` tab of the equipment page (`/uns/equipment/{id}`) +by editing the tag's raw `TagConfig` JSON to include the `"alarm"` object. No +other configuration is required. + +### Deferred follow-ups + +Two items are explicitly out of scope for Phase B: + +1. **Inbound device-ack**: an OPC UA client `Acknowledge` currently updates the + local `AlarmConditionState` but does **not** yet propagate back to the device + via `IAlarmSource.AcknowledgeAsync` (→ AVEVA). Device-ack round-trip is a + deferred follow-up. +2. **AdminUI Galaxy address-picker pre-fill**: the `alarm` object must be authored + by editing the tag's raw `TagConfig` JSON today; a future picker enhancement + could pre-fill `alarmType` / `severity` from driver discovery + (`DriverAttributeInfo.IsAlarm`). + ## Inbound operator ack/shelve Operators interact with active scripted alarms through two surfaces — both converge on the same `alarm-commands` DPS topic consumed by `ScriptedAlarmHostActor`.