feat(scripted-alarms): richer AlarmConditionState bridge to the OPC UA node (T15)
This commit is contained in:
+3
-4
@@ -195,12 +195,11 @@ public sealed class OtOpcUaTelemetryHookTests : RuntimeActorTestBase
|
||||
/// <param name="quality">The OPC UA quality status.</param>
|
||||
/// <param name="sourceTimestampUtc">The source timestamp in UTC.</param>
|
||||
public void WriteValue(string nodeId, object? value, OpcUaQuality quality, DateTime sourceTimestampUtc) => Writes++;
|
||||
/// <summary>Records an alarm state write.</summary>
|
||||
/// <summary>Records an alarm condition write.</summary>
|
||||
/// <param name="alarmNodeId">The alarm node identifier.</param>
|
||||
/// <param name="active">Whether the alarm is active.</param>
|
||||
/// <param name="acknowledged">Whether the alarm is acknowledged.</param>
|
||||
/// <param name="state">The full condition state snapshot.</param>
|
||||
/// <param name="occurredUtc">The time the alarm occurred in UTC.</param>
|
||||
public void WriteAlarmState(string alarmNodeId, bool active, bool acknowledged, DateTime occurredUtc) => Writes++;
|
||||
public void WriteAlarmCondition(string alarmNodeId, AlarmConditionSnapshot state, DateTime occurredUtc) => Writes++;
|
||||
/// <summary>Materialises an alarm condition (stub implementation).</summary>
|
||||
/// <param name="alarmNodeId">The alarm node identifier.</param>
|
||||
/// <param name="equipmentNodeId">The equipment folder node identifier.</param>
|
||||
|
||||
@@ -246,12 +246,11 @@ public sealed class OpcUaPublishActorRebuildTests : RuntimeActorTestBase
|
||||
/// <param name="ts">The timestamp of the write.</param>
|
||||
public void WriteValue(string nodeId, object? value, OpcUaQuality quality, DateTime ts)
|
||||
=> Calls.Enqueue($"WV:{nodeId}");
|
||||
/// <summary>Records an alarm state write call.</summary>
|
||||
/// <summary>Records an alarm condition write call.</summary>
|
||||
/// <param name="alarmNodeId">The alarm node ID.</param>
|
||||
/// <param name="active">Whether the alarm is active.</param>
|
||||
/// <param name="acknowledged">Whether the alarm is acknowledged.</param>
|
||||
/// <param name="state">The full condition state snapshot.</param>
|
||||
/// <param name="ts">The timestamp of the state change.</param>
|
||||
public void WriteAlarmState(string alarmNodeId, bool active, bool acknowledged, DateTime ts)
|
||||
public void WriteAlarmCondition(string alarmNodeId, AlarmConditionSnapshot state, DateTime ts)
|
||||
=> Calls.Enqueue($"WA:{alarmNodeId}");
|
||||
/// <summary>Records a materialise-alarm-condition call.</summary>
|
||||
/// <param name="alarmNodeId">The alarm node ID (== ScriptedAlarmId).</param>
|
||||
|
||||
@@ -18,7 +18,7 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase
|
||||
{
|
||||
var actor = Sys.ActorOf(OpcUaPublishActor.PropsForTests());
|
||||
actor.Tell(new OpcUaPublishActor.AttributeValueUpdate("ns=2;s=Tag1", 42.0, OpcUaQuality.Good, DateTime.UtcNow));
|
||||
actor.Tell(new OpcUaPublishActor.AlarmStateUpdate("ns=2;s=Alarm1", true, false, DateTime.UtcNow));
|
||||
actor.Tell(new OpcUaPublishActor.AlarmStateUpdate("ns=2;s=Alarm1", Snapshot(active: true), DateTime.UtcNow));
|
||||
actor.Tell(new OpcUaPublishActor.RebuildAddressSpace(CorrelationId.NewId()));
|
||||
actor.Tell(new OpcUaPublishActor.ServiceLevelChanged(240));
|
||||
|
||||
@@ -53,24 +53,38 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase
|
||||
}, duration: TimeSpan.FromMilliseconds(500));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AlarmStateUpdate routes to sink WriteAlarmState.</summary>
|
||||
/// <summary>Verifies that AlarmStateUpdate routes to sink WriteAlarmCondition with the full snapshot.</summary>
|
||||
[Fact]
|
||||
public void AlarmStateUpdate_routes_to_sink_WriteAlarmState()
|
||||
public void AlarmStateUpdate_routes_to_sink_WriteAlarmCondition()
|
||||
{
|
||||
var sink = new RecordingSink();
|
||||
var actor = Sys.ActorOf(OpcUaPublishActor.PropsForTests(sink: sink));
|
||||
|
||||
actor.Tell(new OpcUaPublishActor.AlarmStateUpdate("ns=2;s=A1", Active: true, Acknowledged: false, DateTime.UtcNow));
|
||||
actor.Tell(new OpcUaPublishActor.AlarmStateUpdate(
|
||||
"ns=2;s=A1", Snapshot(active: true, acknowledged: false, severity: 700), DateTime.UtcNow));
|
||||
|
||||
AwaitAssert(() =>
|
||||
{
|
||||
sink.Alarms.Count.ShouldBe(1);
|
||||
sink.Alarms[0].AlarmNodeId.ShouldBe("ns=2;s=A1");
|
||||
sink.Alarms[0].Active.ShouldBeTrue();
|
||||
sink.Alarms[0].Acknowledged.ShouldBeFalse();
|
||||
sink.Alarms[0].State.Active.ShouldBeTrue();
|
||||
sink.Alarms[0].State.Acknowledged.ShouldBeFalse();
|
||||
sink.Alarms[0].State.Severity.ShouldBe((ushort)700);
|
||||
}, duration: TimeSpan.FromMilliseconds(500));
|
||||
}
|
||||
|
||||
/// <summary>Builds a test <see cref="AlarmConditionSnapshot"/> with sensible defaults so each test
|
||||
/// only specifies the fields it cares about.</summary>
|
||||
private static AlarmConditionSnapshot Snapshot(
|
||||
bool active = false,
|
||||
bool acknowledged = true,
|
||||
bool confirmed = true,
|
||||
bool enabled = true,
|
||||
AlarmShelvingKind shelving = AlarmShelvingKind.Unshelved,
|
||||
ushort severity = 500,
|
||||
string message = "test") =>
|
||||
new(active, acknowledged, confirmed, enabled, shelving, severity, message);
|
||||
|
||||
/// <summary>Verifies that RebuildAddressSpace calls sink Rebuild.</summary>
|
||||
[Fact]
|
||||
public void RebuildAddressSpace_calls_sink_Rebuild()
|
||||
@@ -148,16 +162,16 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase
|
||||
{
|
||||
/// <summary>Gets the queue of recorded value updates.</summary>
|
||||
public ConcurrentQueue<(string NodeId, object? Value, OpcUaQuality Quality, DateTime Ts)> ValueQueue { get; } = new();
|
||||
/// <summary>Gets the queue of recorded alarm state updates.</summary>
|
||||
public ConcurrentQueue<(string AlarmNodeId, bool Active, bool Acknowledged, DateTime Ts)> AlarmQueue { get; } = new();
|
||||
/// <summary>Gets the queue of recorded alarm condition updates.</summary>
|
||||
public ConcurrentQueue<(string AlarmNodeId, AlarmConditionSnapshot State, DateTime Ts)> AlarmQueue { get; } = new();
|
||||
/// <summary>Count of rebuild calls.</summary>
|
||||
public int RebuildCalls;
|
||||
|
||||
/// <summary>Gets the list of recorded value updates.</summary>
|
||||
public List<(string NodeId, object? Value, OpcUaQuality Quality, DateTime Ts)> Values =>
|
||||
ValueQueue.ToList();
|
||||
/// <summary>Gets the list of recorded alarm state updates.</summary>
|
||||
public List<(string AlarmNodeId, bool Active, bool Acknowledged, DateTime Ts)> Alarms =>
|
||||
/// <summary>Gets the list of recorded alarm condition updates.</summary>
|
||||
public List<(string AlarmNodeId, AlarmConditionSnapshot State, DateTime Ts)> Alarms =>
|
||||
AlarmQueue.ToList();
|
||||
|
||||
/// <summary>Records a value update.</summary>
|
||||
@@ -168,13 +182,12 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase
|
||||
public void WriteValue(string nodeId, object? value, OpcUaQuality quality, DateTime ts) =>
|
||||
ValueQueue.Enqueue((nodeId, value, quality, ts));
|
||||
|
||||
/// <summary>Records an alarm state update.</summary>
|
||||
/// <summary>Records an alarm condition update.</summary>
|
||||
/// <param name="alarmNodeId">The OPC UA alarm node identifier.</param>
|
||||
/// <param name="active">Whether the alarm is active.</param>
|
||||
/// <param name="acknowledged">Whether the alarm is acknowledged.</param>
|
||||
/// <param name="state">The full condition state snapshot.</param>
|
||||
/// <param name="ts">The timestamp of the update.</param>
|
||||
public void WriteAlarmState(string alarmNodeId, bool active, bool acknowledged, DateTime ts) =>
|
||||
AlarmQueue.Enqueue((alarmNodeId, active, acknowledged, ts));
|
||||
public void WriteAlarmCondition(string alarmNodeId, AlarmConditionSnapshot state, DateTime ts) =>
|
||||
AlarmQueue.Enqueue((alarmNodeId, state, ts));
|
||||
|
||||
/// <summary>Materialises an alarm condition (no-op in test).</summary>
|
||||
/// <param name="alarmNodeId">The alarm node ID.</param>
|
||||
|
||||
+13
-3
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user