722b8663c1
OPC UA (RealOpcUaClient): - Append 5 new SelectClauses at indices 13–17 (never renumber 0–12): - 13: AlarmConditionType/ActiveState/TransitionTime → OriginalRaiseTime - 14–17: LimitAlarmType HighHighLimit/HighLimit/LowLimit/LowLowLimit → LimitValue - New OpcUaAlarmMapper.PickLimitValue helper: first non-null in HiHi→Hi→Lo→LoLo priority order, InvariantCulture-formatted; empty string for non-limit alarm types. - HandleAlarmEvent reads new indices with fields.Count > N guards; hard minimum (6) unchanged so base ConditionType events still process without the limit fields. - Document unavailable-by-protocol fields (Category, Description, OperatorUser, CurrentValue) inline in BuildAlarmEventFilter and HandleAlarmEvent. MxGateway (MxGatewayAlarmMapper): - MapTransition: CurrentValue and LimitValue now populated via MxValueToString (uses MxValueExtensions.ToClrValue + InvariantCulture) from OnAlarmTransitionEvent proto fields current_value/limit_value. - MapSnapshot: same — populated from ActiveAlarmSnapshot.current_value/limit_value. - MxValueToString helper (internal): null-safe MxValue → string conversion. Tests (17 new, 40 total pass): - OpcUaAlarmMapperTests: PickLimitValue priority, InvariantCulture, all-null case. - MxGatewayAlarmMapperTests: CurrentValue/LimitValue populate from double/string MxValue; absent fields yield empty strings. - RealOpcUaClientAlarmFilterTests: index alignment assertions (count=18, per-index TypeDefinitionId+BrowsePath), regression guard on existing indices 0–12.
155 lines
5.5 KiB
C#
155 lines
5.5 KiB
C#
using ZB.MOM.WW.MxGateway.Client;
|
|
using ZB.MOM.WW.MxGateway.Contracts.Proto;
|
|
using ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters;
|
|
using CommonsTransitionKind = ZB.MOM.WW.ScadaBridge.Commons.Types.Enums.AlarmTransitionKind;
|
|
using ProtoConditionState = ZB.MOM.WW.MxGateway.Contracts.Proto.AlarmConditionState;
|
|
using ProtoTransitionKind = ZB.MOM.WW.MxGateway.Contracts.Proto.AlarmTransitionKind;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests;
|
|
|
|
/// <summary>Task-12: pure MxGateway alarm-feed proto → NativeAlarmTransition mapping.</summary>
|
|
public class MxGatewayAlarmMapperTests
|
|
{
|
|
[Fact]
|
|
public void MapTransition_AckTransition_IsAcknowledgedWithOperator()
|
|
{
|
|
var ev = new OnAlarmTransitionEvent
|
|
{
|
|
AlarmFullReference = "Tank01.Level.HiHi",
|
|
SourceObjectReference = "Tank01",
|
|
AlarmTypeName = "AnalogLimitAlarm.HiHi",
|
|
TransitionKind = ProtoTransitionKind.Acknowledge,
|
|
Severity = 600,
|
|
OperatorUser = "operator1",
|
|
OperatorComment = "ack",
|
|
Category = "Process",
|
|
Description = "hi"
|
|
};
|
|
|
|
var t = MxGatewayAlarmMapper.MapTransition(ev);
|
|
|
|
Assert.Equal(CommonsTransitionKind.Acknowledge, t.Kind);
|
|
Assert.True(t.Condition.Active);
|
|
Assert.True(t.Condition.Acknowledged);
|
|
Assert.Equal(600, t.Condition.Severity);
|
|
Assert.Equal("operator1", t.OperatorUser);
|
|
Assert.Equal("Tank01", t.SourceObjectReference);
|
|
}
|
|
|
|
[Fact]
|
|
public void MapConditionState_ActiveAcked_To_ActiveTrue_AckTrue()
|
|
{
|
|
var c = MxGatewayAlarmMapper.MapConditionState(ProtoConditionState.ActiveAcked, severity: 600);
|
|
Assert.True(c.Active);
|
|
Assert.True(c.Acknowledged);
|
|
Assert.Equal(600, c.Severity);
|
|
}
|
|
|
|
[Fact]
|
|
public void MapSnapshot_ActiveUnacked_IsSnapshotKind()
|
|
{
|
|
var snap = new ActiveAlarmSnapshot
|
|
{
|
|
AlarmFullReference = "Tank01.Level.Hi",
|
|
SourceObjectReference = "Tank01",
|
|
AlarmTypeName = "AnalogLimitAlarm.Hi",
|
|
CurrentState = ProtoConditionState.Active,
|
|
Severity = 1500 // out of range — must clamp
|
|
};
|
|
|
|
var t = MxGatewayAlarmMapper.MapSnapshot(snap);
|
|
|
|
Assert.Equal(CommonsTransitionKind.Snapshot, t.Kind);
|
|
Assert.True(t.Condition.Active);
|
|
Assert.False(t.Condition.Acknowledged);
|
|
Assert.Equal(1000, t.Condition.Severity);
|
|
}
|
|
|
|
// ── CurrentValue / LimitValue (M2.13 / #27) ──────────────────────────────
|
|
|
|
[Fact]
|
|
public void MapTransition_CurrentAndLimitValue_PopulatedFromProto()
|
|
{
|
|
// The gateway proto OnAlarmTransitionEvent carries current_value and
|
|
// limit_value as MxValue union fields. Verify both are mapped through
|
|
// MxValueToString into the neutral NativeAlarmTransition strings.
|
|
var ev = new OnAlarmTransitionEvent
|
|
{
|
|
AlarmFullReference = "Tank01.Level.HiHi",
|
|
SourceObjectReference = "Tank01",
|
|
AlarmTypeName = "AnalogLimitAlarm.HiHi",
|
|
TransitionKind = ProtoTransitionKind.Raise,
|
|
Severity = 800,
|
|
CurrentValue = 95.3.ToMxValue(),
|
|
LimitValue = 90.0.ToMxValue()
|
|
};
|
|
|
|
var t = MxGatewayAlarmMapper.MapTransition(ev);
|
|
|
|
Assert.Equal("95.3", t.CurrentValue);
|
|
Assert.Equal("90", t.LimitValue);
|
|
}
|
|
|
|
[Fact]
|
|
public void MapTransition_AbsentCurrentAndLimitValue_YieldsEmpty()
|
|
{
|
|
// When the gateway sends events without current/limit value fields (optional),
|
|
// the resulting transition must have empty strings — never null.
|
|
var ev = new OnAlarmTransitionEvent
|
|
{
|
|
AlarmFullReference = "Tank01.Level.Hi",
|
|
SourceObjectReference = "Tank01",
|
|
AlarmTypeName = "AnalogLimitAlarm.Hi",
|
|
TransitionKind = ProtoTransitionKind.Raise,
|
|
Severity = 600
|
|
// CurrentValue and LimitValue not set → proto default (null reference)
|
|
};
|
|
|
|
var t = MxGatewayAlarmMapper.MapTransition(ev);
|
|
|
|
Assert.Equal("", t.CurrentValue);
|
|
Assert.Equal("", t.LimitValue);
|
|
}
|
|
|
|
[Fact]
|
|
public void MapSnapshot_CurrentAndLimitValue_PopulatedFromProto()
|
|
{
|
|
// ActiveAlarmSnapshot also carries current_value and limit_value.
|
|
var snap = new ActiveAlarmSnapshot
|
|
{
|
|
AlarmFullReference = "Pump01.Vibration.HiHi",
|
|
SourceObjectReference = "Pump01",
|
|
AlarmTypeName = "AnalogLimitAlarm.HiHi",
|
|
CurrentState = ProtoConditionState.Active,
|
|
Severity = 900,
|
|
CurrentValue = 12.7.ToMxValue(),
|
|
LimitValue = 10.0.ToMxValue()
|
|
};
|
|
|
|
var t = MxGatewayAlarmMapper.MapSnapshot(snap);
|
|
|
|
Assert.Equal("12.7", t.CurrentValue);
|
|
Assert.Equal("10", t.LimitValue);
|
|
}
|
|
|
|
[Fact]
|
|
public void MapSnapshot_StringMxValue_ProducesStringCurrentValue()
|
|
{
|
|
// MxValue can carry string values (e.g. for discrete/string-type tags).
|
|
var snap = new ActiveAlarmSnapshot
|
|
{
|
|
AlarmFullReference = "Mode.Alarm",
|
|
SourceObjectReference = "Mode",
|
|
AlarmTypeName = "DiscreteAlarm",
|
|
CurrentState = ProtoConditionState.Active,
|
|
Severity = 500,
|
|
CurrentValue = "FAULT".ToMxValue()
|
|
};
|
|
|
|
var t = MxGatewayAlarmMapper.MapSnapshot(snap);
|
|
|
|
Assert.Equal("FAULT", t.CurrentValue);
|
|
Assert.Equal("", t.LimitValue); // not set
|
|
}
|
|
}
|