feat(scripted-alarms): richer AlarmConditionState bridge to the OPC UA node (T15)

This commit is contained in:
Joseph Doherty
2026-06-10 19:41:16 -04:00
parent b31d7cb03f
commit 4eb1d65e2b
16 changed files with 349 additions and 108 deletions
@@ -5,6 +5,7 @@ using Serilog;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Alerts;
using ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
using ZB.MOM.WW.OtOpcUa.Core.ScriptedAlarms;
using ZB.MOM.WW.OtOpcUa.Core.Scripting;
using ZB.MOM.WW.OtOpcUa.OpcUaServer;
@@ -131,7 +132,16 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
var state = publish.ExpectMsg<OpcUaPublishActor.AlarmStateUpdate>(Timeout);
state.AlarmNodeId.ShouldBe("alm-1");
state.Active.ShouldBeTrue();
// The full Part 9 snapshot bridges through (T15) — every Core condition field maps:
// on activation the engine sets Active, clears Ack AND Confirm (a new active occurrence needs a
// fresh ack→clear→confirm cycle), keeps Enabled, and leaves Shelving unshelved.
state.State.Active.ShouldBeTrue(); // Condition.Active == Active
state.State.Acknowledged.ShouldBeFalse(); // Condition.Acked == Unacknowledged on activation
state.State.Confirmed.ShouldBeFalse(); // Condition.Confirmed == Unconfirmed on activation
state.State.Enabled.ShouldBeTrue(); // Condition.Enabled == Enabled
state.State.Shelving.ShouldBe(AlarmShelvingKind.Unshelved); // Condition.Shelving.Kind == Unshelved
state.State.Severity.ShouldBe((ushort)1000); // 800 → Critical bucket → 1000
state.State.Message.ShouldBe("condition"); // e.Message
var evt = alerts.ExpectMsg<AlarmTransitionEvent>(Timeout);
evt.AlarmId.ShouldBe("alm-1");
@@ -156,13 +166,13 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
// Activate first.
host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow));
publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.Active, Timeout);
publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout);
alerts.FishForMessage<AlarmTransitionEvent>(e => e.TransitionKind == "Activated", Timeout);
// Now clear.
host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 10, DateTime.UtcNow));
var cleared = publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => !m.Active, Timeout);
var cleared = publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => !m.State.Active, Timeout);
cleared.AlarmNodeId.ShouldBe("alm-1");
var evt = alerts.FishForMessage<AlarmTransitionEvent>(e => e.TransitionKind == "Cleared", Timeout);