Files
lmxopcua/docs/plans/alarms-d1-smoke-artifact.md
T
Joseph Doherty 2791ab5810
v2-ci / build (pull_request) Successful in 4m15s
v2-ci / unit-tests (pull_request) Failing after 10m50s
docs(#420): final reconciliation — alarms-over-gateway feature complete; C.1 superseded
#420 (filed 2026-05-01 as '4 inert scaffolds + D.1 smoke') is stale. Current
master + the mxaccessgw sibling repo show the feature landed on both sides:

- mxaccessgw A.2/A.4: native alarms served via the wnwrap consumer
  (WnWrapAlarmConsumer over WNWRAPCONSUMERLib.wwAlarmConsumerClass) +
  FailoverAlarmConsumer + AlarmDispatcher; StreamAlarms active-alarm snapshot
  (20 alarms verified live). The WM_APP-pump premise this issue was written
  against was ruled out by the 2026-05-01 probes; the design pivoted to wnwrap.
- mxaccessgw A.3: NotWiredAlarmRpcDispatcher gone (only in generated proto);
  ack routes MxGatewayClient.AcknowledgeAlarmAsync -> AlarmClient.AlarmAckByGUID.
- lmxopcua: GalaxyDriver : IAlarmSource wires GatewayGalaxyAlarmFeed
  (-> OnAlarmEvent, native metadata incl. operator-comment) + inbound ack via
  GatewayGalaxyAlarmAcknowledger; native feed live-proven by
  GatewayGalaxyAlarmFeedLiveTests.
- C.1 (SdkAlarmHistorianWriteBackend / aahClientManaged): SUPERSEDED. The whole
  Wonderware sidecar was retired; alarm-history writes cut over to the
  HistorianGateway (GatewayAlarmHistorianWriter, SendEvent path), live-verified
  2026-07-15 (R2-06/R2-08, 6/6). The target no longer exists in the tree.

Updated the alarms-over-gateway.md banner (final reconciliation) + the
alarms-d1-smoke-artifact.md status note. Sole residual = a Windows-parity-rig
running-server OPC UA A&C round-trip (needs live Galaxy + human IDE).
2026-07-15 15:23:32 -04:00

6.1 KiB

Alarms D.1 — smoke artifact

Status (2026-05-29): alarm-source leg VERIFIED. Historian-write leg still pending the Windows sidecar + live AVEVA Historian.

UPDATE 2026-07-15 (Gitea #420): the historian-write leg is no longer a Wonderware-sidecar concern — it was cut over to the HistorianGateway. Alarm events now write through GatewayAlarmHistorianWriter (the gateway SendEvent path behind SqliteStoreAndForwardSink), and alarm SendEventReadEvents+ack was live-verified 2026-07-15 against the real AVEVA historian (R2-06/R2-08, 6/6 green). The bespoke Wonderware sidecar + SdkAlarmHistorianWriteBackend were retired. So this D.1 artifact's alarm-source leg is verified and the write-back leg is superseded-and-verified via the gateway.

Re-confirmed 2026-05-31 against the same gateway (http://10.100.0.48:5120): the Skip-gated live test passed again, pulling a native Raise transition (Galaxy!TestArea.TestMachine_001.TestAlarm001, raw sev 500 → OPC UA 750/High, category TestArea, operator comment Test alarm #1) through the production consumer. Independent re-run, not the original capture.

This is the D.1 deliverable called for by docs/plans/alarms-worker-wiring-plan.md — captured evidence that a live Galaxy alarm reaches lmxopcua through the native gateway path (not the sub-attribute fallback). It supersedes the "A.2 blocked" banners in alarms-over-gateway.md / alarms-worker-wiring-plan.md, which were written 2026-04-30 before the gateway's alarm feed was working.

What was verified

The mxaccessgw gateway does serve native MxAccess alarms today, and the lmxopcua consumer ingests them with full fidelity — including operator-comment, the field the 2026-04-30 plan flagged as "the only v1 regression."

Verified from the macOS dev box against the live gateway at http://10.100.0.48:5120 (reachable; nc -z succeeds). No acknowledge / no writes were issued — read-only StreamAlarms.

1. Gateway boundary — raw StreamAlarms (ZB.MOM.WW.MxGateway.Client)

A standalone client streamed the active-alarm snapshot: 20 active alarms, each carrying native metadata. Sample (one of 20):

{ "alarmFullReference": "Galaxy!TestArea.TestMachine_001.TestAlarm001",
  "sourceObjectReference": "TestMachine_001.TestAlarm001",
  "alarmTypeName": "DSC", "severity": 500,
  "currentState": "ALARM_CONDITION_STATE_ACTIVE", "category": "TestArea",
  "lastTransitionTimestamp": "2026-05-24T16:04:10.856Z",
  "operatorComment": "Test alarm #1" }

Followed by the SnapshotComplete marker. operatorComment, category, severity, currentState, and lastTransitionTimestamp are all populated.

2. lmxopcua consumer — GatewayGalaxyAlarmFeedGalaxyAlarmTransition

The Skip-gated live test Runtime/GatewayGalaxyAlarmFeedLiveTests.Live_gateway_delivers_native_alarm_transitions_through_the_consumer wires the real MxGatewayClient.StreamAlarmsAsync into the production consumer seam and passes. Captured output (D1_SMOKE_OUT):

# consumer transitions observed: 2+
Raise  Galaxy!TestArea.TestMachine_001.TestAlarm001 | sev=750(High) raw=500 | cat=TestArea | comment='Test alarm #1' | xitionUtc=2026-05-24T16:04:10.856Z
Raise  Galaxy!TestArea.TestMachine_003.TestAlarm001 | sev=750(High) raw=500 | cat=TestArea | comment='Test alarm #1' | xitionUtc=2026-05-07T18:14:00.594Z

The consumer preserves operatorComment + category + transition timestamp and applies the OPC UA severity-bucket mapping (MxAccessSeverityMapper: raw 500 → OPC UA 750, bucket High).

3. Full chain to the OPC UA Part 9 surface (code-path verified)

GalaxyDriver.OnAlarmFeedTransition maps GalaxyAlarmTransitionAlarmEventArgs, carrying OperatorComment, OriginalRaiseTimestampUtc, AlarmCategory, and the severity bucket onto IAlarmSource.OnAlarmEvent. AlarmEventArgs already declares those fields — so the E.7 contract extension is done, not pending. The server's Part-9 condition layer consumes IAlarmSource via AlarmSurfaceInvokerGenericDriverNodeManager. Unit coverage: GalaxyDriverAlarmSourceTests, GatewayGalaxyAlarmFeedTests.

How to re-run

export MXGW_ENDPOINT="http://10.100.0.48:5120"
export GALAXY_MXGW_API_KEY="<dev key from docker-dev/docker-compose.yml>"
export D1_SMOKE_OUT="/tmp/d1-consumer-transitions.txt"   # optional capture
dotnet test tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests \
  --filter "FullyQualifiedName~GatewayGalaxyAlarmFeedLiveTests"

Without the env vars the test Skips, so normal dotnet test runs are unaffected.

Not covered here (still open)

  1. Scripted-alarm historian write-back → AVEVA Historian (C.1's live leg). The SdkAlarmHistorianWriteBackend (real HistorianAccess.AddStreamedValue path) is implemented and unit-tested, but its Live_* write smoke needs the Windows historian sidecar + a live AVEVA Historian — neither reachable from the macOS dev box. Capture this leg on the Windows parity rig.
  2. Running-server → OPC UA A&C client round-trip. This artifact proves the driver consumer end; it does not exercise a full OtOpcUa server surfacing the condition to an OPC UA client, because the docker-dev stack stubs the Galaxy driver on Linux (DriverInstanceActor.ShouldStub). Capture on the Windows parity rig (or a Linux host with ShouldStub overridden to point the real driver at the gateway).

Mechanism — true MxAccess alarm-event support

The gateway delivers these alarms via true MxAccess alarm-event support in the mxaccessgw .NET client — a real alarm-event subscription, not the value-driven sub-attribute fallback. (Confirmed by the gateway maintainer; the client-side stream check above can only observe the resulting feed, which is why this artifact records the mechanism here rather than inferring it.) So A.2 is implemented as originally specified: MX_EVENT_FAMILY_ON_ALARM_TRANSITION carries genuine native alarm-event metadata, and the operator-comment / original-raise-time / category fields are first-class — not reconstructed from attribute reads.