feat(dcl): populate obtainable NativeAlarmTransition fields from OPC UA and MxGateway (#27, M2.13)
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.
This commit is contained in:
+90
@@ -73,4 +73,94 @@ public class RealOpcUaClientAlarmFilterTests
|
||||
var filter = RealOpcUaClient.BuildAlarmEventFilter(parsed);
|
||||
Assert.Empty(filter.WhereClause.Elements);
|
||||
}
|
||||
|
||||
// ── SelectClause index alignment (M2.13 / #27) ───────────────────────────
|
||||
// CRITICAL: HandleAlarmEvent reads fields[N] by position. Verify new clauses
|
||||
// are APPENDED at indices 13–17 so existing mappings (0–12) are undisturbed.
|
||||
|
||||
[Fact]
|
||||
public void BuildAlarmEventFilter_HasExactly18SelectClauses()
|
||||
{
|
||||
// Baseline: 6 base fields + 7 A&C sub-state fields + 5 new appended fields = 18.
|
||||
// If this count changes, review HandleAlarmEvent index mappings immediately.
|
||||
var filter = RealOpcUaClient.BuildAlarmEventFilter(AlarmConditionFilter.AllowAll);
|
||||
Assert.Equal(18, filter.SelectClauses.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildAlarmEventFilter_Index13_IsAlarmConditionType_ActiveState_TransitionTime()
|
||||
{
|
||||
// Index 13 must be AlarmConditionType/ActiveState/TransitionTime → OriginalRaiseTime.
|
||||
var filter = RealOpcUaClient.BuildAlarmEventFilter(AlarmConditionFilter.AllowAll);
|
||||
var clause = filter.SelectClauses[13];
|
||||
Assert.Equal(ObjectTypeIds.AlarmConditionType, clause.TypeDefinitionId);
|
||||
Assert.Equal(2, clause.BrowsePath.Count);
|
||||
Assert.Equal("ActiveState", clause.BrowsePath[0].Name);
|
||||
Assert.Equal("TransitionTime", clause.BrowsePath[1].Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildAlarmEventFilter_Index14_IsLimitAlarmType_HighHighLimit()
|
||||
{
|
||||
var filter = RealOpcUaClient.BuildAlarmEventFilter(AlarmConditionFilter.AllowAll);
|
||||
var clause = filter.SelectClauses[14];
|
||||
Assert.Equal(ObjectTypeIds.LimitAlarmType, clause.TypeDefinitionId);
|
||||
Assert.Equal("HighHighLimit", clause.BrowsePath[0].Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildAlarmEventFilter_Index15_IsLimitAlarmType_HighLimit()
|
||||
{
|
||||
var filter = RealOpcUaClient.BuildAlarmEventFilter(AlarmConditionFilter.AllowAll);
|
||||
var clause = filter.SelectClauses[15];
|
||||
Assert.Equal(ObjectTypeIds.LimitAlarmType, clause.TypeDefinitionId);
|
||||
Assert.Equal("HighLimit", clause.BrowsePath[0].Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildAlarmEventFilter_Index16_IsLimitAlarmType_LowLimit()
|
||||
{
|
||||
var filter = RealOpcUaClient.BuildAlarmEventFilter(AlarmConditionFilter.AllowAll);
|
||||
var clause = filter.SelectClauses[16];
|
||||
Assert.Equal(ObjectTypeIds.LimitAlarmType, clause.TypeDefinitionId);
|
||||
Assert.Equal("LowLimit", clause.BrowsePath[0].Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildAlarmEventFilter_Index17_IsLimitAlarmType_LowLowLimit()
|
||||
{
|
||||
var filter = RealOpcUaClient.BuildAlarmEventFilter(AlarmConditionFilter.AllowAll);
|
||||
var clause = filter.SelectClauses[17];
|
||||
Assert.Equal(ObjectTypeIds.LimitAlarmType, clause.TypeDefinitionId);
|
||||
Assert.Equal("LowLowLimit", clause.BrowsePath[0].Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildAlarmEventFilter_ExistingIndices0To12_Unchanged()
|
||||
{
|
||||
// Guard: the first 13 SelectClauses (indices 0–12) must remain unchanged so
|
||||
// that existing HandleAlarmEvent logic is not silently broken by future edits.
|
||||
var filter = RealOpcUaClient.BuildAlarmEventFilter(AlarmConditionFilter.AllowAll);
|
||||
|
||||
// Indices 0–5: base event fields (EventType…Severity) from BaseEventType.
|
||||
for (var i = 0; i <= 5; i++)
|
||||
Assert.Equal(ObjectTypeIds.BaseEventType, filter.SelectClauses[i].TypeDefinitionId);
|
||||
|
||||
// Index 6: AlarmConditionType/ActiveState/Id
|
||||
Assert.Equal(ObjectTypeIds.AlarmConditionType, filter.SelectClauses[6].TypeDefinitionId);
|
||||
Assert.Equal("ActiveState", filter.SelectClauses[6].BrowsePath[0].Name);
|
||||
Assert.Equal("Id", filter.SelectClauses[6].BrowsePath[1].Name);
|
||||
|
||||
// Index 7: AcknowledgeableConditionType/AckedState/Id
|
||||
Assert.Equal(ObjectTypeIds.AcknowledgeableConditionType, filter.SelectClauses[7].TypeDefinitionId);
|
||||
Assert.Equal("AckedState", filter.SelectClauses[7].BrowsePath[0].Name);
|
||||
|
||||
// Index 11: ConditionType/ConditionName
|
||||
Assert.Equal(ObjectTypeIds.ConditionType, filter.SelectClauses[11].TypeDefinitionId);
|
||||
Assert.Equal("ConditionName", filter.SelectClauses[11].BrowsePath[0].Name);
|
||||
|
||||
// Index 12: ConditionType/Comment
|
||||
Assert.Equal(ObjectTypeIds.ConditionType, filter.SelectClauses[12].TypeDefinitionId);
|
||||
Assert.Equal("Comment", filter.SelectClauses[12].BrowsePath[0].Name);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user