From ea14ace150acf87790d592778f428bdfe2e827fe Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 29 May 2026 15:41:10 -0400 Subject: [PATCH] feat(commons): IAlarmSubscribableConnection seam + DCL native alarm messages --- .../Protocol/IAlarmSubscribableConnection.cs | 28 +++++++++++++++++ .../DataConnection/NativeAlarmMessages.cs | 13 ++++++++ .../DataConnection/SubscribeAlarmsRequest.cs | 30 +++++++++++++++++++ .../Messages/NativeAlarmMessagesTests.cs | 27 +++++++++++++++++ 4 files changed, 98 insertions(+) create mode 100644 src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Protocol/IAlarmSubscribableConnection.cs create mode 100644 src/ZB.MOM.WW.ScadaBridge.Commons/Messages/DataConnection/NativeAlarmMessages.cs create mode 100644 src/ZB.MOM.WW.ScadaBridge.Commons/Messages/DataConnection/SubscribeAlarmsRequest.cs create mode 100644 tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Messages/NativeAlarmMessagesTests.cs diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Protocol/IAlarmSubscribableConnection.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Protocol/IAlarmSubscribableConnection.cs new file mode 100644 index 00000000..c9c18b25 --- /dev/null +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Protocol/IAlarmSubscribableConnection.cs @@ -0,0 +1,28 @@ +using ZB.MOM.WW.ScadaBridge.Commons.Types.Alarms; + +namespace ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Protocol; + +/// Callback invoked when a native alarm transition (incl. snapshot replay) arrives. +/// The protocol-neutral transition emitted by the adapter. +public delegate void AlarmTransitionCallback(NativeAlarmTransition transition); + +/// +/// Optional capability for an implementation that +/// can mirror a source's native alarms (OPC UA Alarms & Conditions, MxAccess +/// Gateway StreamAlarms). Mirrors the +/// capability-interface pattern; consumed by the DataConnectionActor only. +/// +public interface IAlarmSubscribableConnection +{ + /// + /// Subscribes to native alarm transitions for the conditions under + /// . The adapter replays a snapshot of + /// currently-active conditions (Snapshot…SnapshotComplete) on every + /// (re)subscribe. Returns a subscription id for . + /// + Task SubscribeAlarmsAsync(string sourceReference, string? conditionFilter, + AlarmTransitionCallback callback, CancellationToken cancellationToken = default); + + /// Cancels an active alarm subscription by its id. + Task UnsubscribeAlarmsAsync(string subscriptionId, CancellationToken cancellationToken = default); +} diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/DataConnection/NativeAlarmMessages.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/DataConnection/NativeAlarmMessages.cs new file mode 100644 index 00000000..9526e991 --- /dev/null +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/DataConnection/NativeAlarmMessages.cs @@ -0,0 +1,13 @@ +using ZB.MOM.WW.ScadaBridge.Commons.Types.Alarms; + +namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.DataConnection; + +/// DCL → instance: a native alarm transition routed by source reference. +public record NativeAlarmTransitionUpdate(string ConnectionName, NativeAlarmTransition Transition); + +/// +/// DCL → instance: the alarm feed for a source became unavailable (connection +/// lost). Consumers mark their mirrored alarms uncertain rather than clearing +/// them; the reconnect snapshot reconciles state. +/// +public record NativeAlarmSourceUnavailable(string ConnectionName, string SourceReference, DateTimeOffset Timestamp); diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/DataConnection/SubscribeAlarmsRequest.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/DataConnection/SubscribeAlarmsRequest.cs new file mode 100644 index 00000000..01e53b07 --- /dev/null +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/DataConnection/SubscribeAlarmsRequest.cs @@ -0,0 +1,30 @@ +namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.DataConnection; + +/// +/// Sent by a NativeAlarmActor (via the DCL manager) to subscribe an instance to +/// native alarms for a source binding. The DataConnectionActor opens one alarm +/// feed per connection and routes transitions by source-object reference. +/// +public record SubscribeAlarmsRequest( + string CorrelationId, + string InstanceUniqueName, + string ConnectionName, + string SourceReference, + string? ConditionFilter, + DateTimeOffset Timestamp); + +/// Reply to a . +public record SubscribeAlarmsResponse( + string CorrelationId, + string InstanceUniqueName, + bool Success, + string? ErrorMessage, + DateTimeOffset Timestamp); + +/// Cancels a native alarm subscription for an instance + source. +public record UnsubscribeAlarmsRequest( + string CorrelationId, + string InstanceUniqueName, + string ConnectionName, + string SourceReference, + DateTimeOffset Timestamp); diff --git a/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Messages/NativeAlarmMessagesTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Messages/NativeAlarmMessagesTests.cs new file mode 100644 index 00000000..a6100c56 --- /dev/null +++ b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Messages/NativeAlarmMessagesTests.cs @@ -0,0 +1,27 @@ +using ZB.MOM.WW.ScadaBridge.Commons.Messages.DataConnection; +using ZB.MOM.WW.ScadaBridge.Commons.Types.Alarms; +using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums; + +namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Messages; + +public class NativeAlarmMessagesTests +{ + [Fact] + public void SubscribeAlarmsRequest_CarriesSourceAndFilter() + { + var r = new SubscribeAlarmsRequest("c1", "inst", "PlantOpcUa", "ns=2;s=Tank01", null, DateTimeOffset.UnixEpoch); + Assert.Equal("ns=2;s=Tank01", r.SourceReference); + Assert.Null(r.ConditionFilter); + } + + [Fact] + public void NativeAlarmTransitionUpdate_WrapsTransition() + { + var t = new NativeAlarmTransition("Tank01.Hi", "Tank01", "x", AlarmTransitionKind.Raise, + new AlarmConditionState(true, false, null, AlarmShelveState.Unshelved, false, 500), + "", "", "", "", "", null, DateTimeOffset.UnixEpoch, "", ""); + var u = new NativeAlarmTransitionUpdate("PlantOpcUa", t); + Assert.Equal("PlantOpcUa", u.ConnectionName); + Assert.Equal("Tank01", u.Transition.SourceObjectReference); + } +}