feat(dcl): MxGateway StreamAlarms adapter (snapshot + live transitions, reconnecting)
Adds IAlarmSubscribableConnection to MxGatewayDataConnection (shared session-less feed, ref-counted), IMxGatewayClient.RunAlarmStreamAsync over the package StreamAlarmsAsync with internal reconnect, and MxGatewayAlarmMapper (AlarmFeedMessage/OnAlarmTransitionEvent -> NativeAlarmTransition). Behavior verified against a live gateway in Task 28; mapper unit-tested.
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
using ZB.MOM.WW.MxGateway.Contracts.Proto;
|
||||
using ProtoConditionState = ZB.MOM.WW.MxGateway.Contracts.Proto.AlarmConditionState;
|
||||
using ProtoTransitionKind = ZB.MOM.WW.MxGateway.Contracts.Proto.AlarmTransitionKind;
|
||||
// Alias the Commons alarm types so their simple names bind here, unambiguous
|
||||
// against the colliding gateway proto enums above.
|
||||
using NativeAlarmTransition = ZB.MOM.WW.ScadaBridge.Commons.Types.Alarms.NativeAlarmTransition;
|
||||
using AlarmConditionState = ZB.MOM.WW.ScadaBridge.Commons.Types.Alarms.AlarmConditionState;
|
||||
using AlarmTransitionKind = ZB.MOM.WW.ScadaBridge.Commons.Types.Enums.AlarmTransitionKind;
|
||||
using AlarmShelveState = ZB.MOM.WW.ScadaBridge.Commons.Types.Enums.AlarmShelveState;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters;
|
||||
|
||||
/// <summary>
|
||||
/// Pure mapping from MxAccess Gateway alarm-feed proto messages to the
|
||||
/// protocol-neutral <see cref="NativeAlarmTransition"/> shape. The gateway proto
|
||||
/// enums (AlarmConditionState / AlarmTransitionKind) collide with the Commons
|
||||
/// alarm enums, so they are aliased here. Unit-tested without a live gateway.
|
||||
/// </summary>
|
||||
public static class MxGatewayAlarmMapper
|
||||
{
|
||||
/// <summary>Clamps the gateway severity onto the unified 0–1000 scale.</summary>
|
||||
public static int NormalizeSeverity(int severity) => Math.Clamp(severity, 0, 1000);
|
||||
|
||||
/// <summary>Maps a gateway condition-state + severity to the unified condition.</summary>
|
||||
public static AlarmConditionState MapConditionState(ProtoConditionState state, int severity)
|
||||
{
|
||||
var (active, acked) = state switch
|
||||
{
|
||||
ProtoConditionState.Active => (true, false),
|
||||
ProtoConditionState.ActiveAcked => (true, true),
|
||||
ProtoConditionState.Inactive => (false, true),
|
||||
_ => (false, true)
|
||||
};
|
||||
return new AlarmConditionState(active, acked, Confirmed: null,
|
||||
Shelve: AlarmShelveState.Unshelved, Suppressed: false, Severity: NormalizeSeverity(severity));
|
||||
}
|
||||
|
||||
/// <summary>Maps a gateway transition kind to the unified transition kind.</summary>
|
||||
public static AlarmTransitionKind MapKind(ProtoTransitionKind kind) => kind switch
|
||||
{
|
||||
ProtoTransitionKind.Raise => AlarmTransitionKind.Raise,
|
||||
ProtoTransitionKind.Acknowledge => AlarmTransitionKind.Acknowledge,
|
||||
ProtoTransitionKind.Clear => AlarmTransitionKind.Clear,
|
||||
ProtoTransitionKind.Retrigger => AlarmTransitionKind.Retrigger,
|
||||
_ => AlarmTransitionKind.StateChange
|
||||
};
|
||||
|
||||
/// <summary>Derives the mirrored condition from a transition kind + severity.</summary>
|
||||
private static AlarmConditionState ConditionFromKind(ProtoTransitionKind kind, int severity)
|
||||
{
|
||||
var (active, acked) = kind switch
|
||||
{
|
||||
ProtoTransitionKind.Raise => (true, false),
|
||||
ProtoTransitionKind.Acknowledge => (true, true),
|
||||
ProtoTransitionKind.Retrigger => (true, false),
|
||||
ProtoTransitionKind.Clear => (false, true),
|
||||
_ => (false, true)
|
||||
};
|
||||
return new AlarmConditionState(active, acked, Confirmed: null,
|
||||
Shelve: AlarmShelveState.Unshelved, Suppressed: false, Severity: NormalizeSeverity(severity));
|
||||
}
|
||||
|
||||
/// <summary>Maps a live <see cref="OnAlarmTransitionEvent"/> to a transition.</summary>
|
||||
public static NativeAlarmTransition MapTransition(OnAlarmTransitionEvent body) => new(
|
||||
SourceReference: body.AlarmFullReference,
|
||||
SourceObjectReference: body.SourceObjectReference,
|
||||
AlarmTypeName: body.AlarmTypeName,
|
||||
Kind: MapKind(body.TransitionKind),
|
||||
Condition: ConditionFromKind(body.TransitionKind, body.Severity),
|
||||
Category: body.Category,
|
||||
Description: body.Description,
|
||||
Message: body.Description,
|
||||
OperatorUser: body.OperatorUser,
|
||||
OperatorComment: body.OperatorComment,
|
||||
OriginalRaiseTime: body.OriginalRaiseTimestamp?.ToDateTimeOffset(),
|
||||
TransitionTime: body.TransitionTimestamp?.ToDateTimeOffset() ?? DateTimeOffset.UtcNow,
|
||||
CurrentValue: "",
|
||||
LimitValue: "");
|
||||
|
||||
/// <summary>The end-of-snapshot sentinel transition (no condition payload).</summary>
|
||||
public static NativeAlarmTransition SnapshotComplete() => new(
|
||||
"", "", "", AlarmTransitionKind.SnapshotComplete,
|
||||
new AlarmConditionState(false, true, null, AlarmShelveState.Unshelved, false, 0),
|
||||
"", "", "", "", "", null, DateTimeOffset.UtcNow, "", "");
|
||||
|
||||
/// <summary>Maps one initial-snapshot <see cref="ActiveAlarmSnapshot"/> entry to a Snapshot transition.</summary>
|
||||
public static NativeAlarmTransition MapSnapshot(ActiveAlarmSnapshot snapshot) => new(
|
||||
SourceReference: snapshot.AlarmFullReference,
|
||||
SourceObjectReference: snapshot.SourceObjectReference,
|
||||
AlarmTypeName: snapshot.AlarmTypeName,
|
||||
Kind: AlarmTransitionKind.Snapshot,
|
||||
Condition: MapConditionState(snapshot.CurrentState, snapshot.Severity),
|
||||
Category: snapshot.Category,
|
||||
Description: snapshot.Description,
|
||||
Message: snapshot.Description,
|
||||
OperatorUser: snapshot.OperatorUser,
|
||||
OperatorComment: snapshot.OperatorComment,
|
||||
OriginalRaiseTime: snapshot.OriginalRaiseTimestamp?.ToDateTimeOffset(),
|
||||
TransitionTime: snapshot.LastTransitionTimestamp?.ToDateTimeOffset() ?? DateTimeOffset.UtcNow,
|
||||
CurrentValue: "",
|
||||
LimitValue: "");
|
||||
}
|
||||
Reference in New Issue
Block a user