feat(native-alarms): per-binding IsMesReportable on native alarm source bindings (ScadaBridge#59)
TemplateNativeAlarmSource.IsMesReportable + tri-state InstanceNativeAlarmSourceOverride .IsMesReportableOverride flatten onto ResolvedNativeAlarmSource; NativeAlarmActor stamps every mirrored condition and InstanceActor the quiet-binding placeholder, so ScriptAlarm, Alarms.MesReportableAsync() and the #60 wire fields carry it. Additive to the 900-999 severity band. Migration AddNativeAlarmSourceMesReportable. Management commands, CLI --mes-reportable, Central UI (template dialog checkbox, instance override select), Transport DTOs/importer/diff and the revision hash (true-only, so existing hashes are unchanged) carry it. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
+19
-2
@@ -51,7 +51,7 @@ public class InstanceActorNativeAlarmTests : TestKit, IDisposable
|
||||
NullLogger<InstanceActor>.Instance,
|
||||
dclManager)));
|
||||
|
||||
private static FlattenedConfiguration ConfigWithNativeSource(string instanceName) => new()
|
||||
private static FlattenedConfiguration ConfigWithNativeSource(string instanceName, bool mesReportable = false) => new()
|
||||
{
|
||||
InstanceUniqueName = instanceName,
|
||||
NativeAlarmSources =
|
||||
@@ -60,7 +60,8 @@ public class InstanceActorNativeAlarmTests : TestKit, IDisposable
|
||||
{
|
||||
CanonicalName = "Pressure",
|
||||
ConnectionName = "Opc",
|
||||
SourceReference = "ns=2;s=T01"
|
||||
SourceReference = "ns=2;s=T01",
|
||||
IsMesReportable = mesReportable
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -100,6 +101,22 @@ public class InstanceActorNativeAlarmTests : TestKit, IDisposable
|
||||
a.SourceReference == "T01.Hi" && a.Kind == AlarmKind.NativeOpcUa && a.Condition.Severity == 800);
|
||||
}
|
||||
|
||||
/// <summary>Gitea #59: the quiet-binding placeholder row carries the binding's MES flag.</summary>
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void Snapshot_QuietNativeBinding_PlaceholderCarriesBindingMesFlag(bool flag)
|
||||
{
|
||||
var dcl = CreateTestProbe();
|
||||
var actor = CreateInstanceActorWithDcl("inst", ConfigWithNativeSource("inst", flag), dcl.Ref);
|
||||
dcl.ExpectMsg<SubscribeAlarmsRequest>();
|
||||
|
||||
actor.Tell(new SubscribeDebugViewRequest("inst", "c"));
|
||||
var snap = ExpectMsg<DebugViewSnapshot>();
|
||||
|
||||
Assert.Equal(flag, Assert.Single(snap.AlarmStates, a => a.IsConfiguredPlaceholder).IsMesReportable);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a quiet native binding emits a placeholder in the snapshot.</summary>
|
||||
[Fact]
|
||||
public void Snapshot_QuietNativeBinding_EmitsPlaceholder()
|
||||
|
||||
@@ -36,11 +36,12 @@ public class NativeAlarmActorTests : TestKit, IDisposable
|
||||
_storage.InitializeAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private static ResolvedNativeAlarmSource Source() => new()
|
||||
private static ResolvedNativeAlarmSource Source(bool mesReportable = false) => new()
|
||||
{
|
||||
CanonicalName = "Pressure",
|
||||
ConnectionName = "Opc",
|
||||
SourceReference = "ns=2;s=T01"
|
||||
SourceReference = "ns=2;s=T01",
|
||||
IsMesReportable = mesReportable
|
||||
};
|
||||
|
||||
private static NativeAlarmTransition Transition(
|
||||
@@ -48,6 +49,11 @@ public class NativeAlarmActorTests : TestKit, IDisposable
|
||||
new(sourceRef, "T01", "AnalogLimit.Hi", kind, condition,
|
||||
"Process", "hi", "hi", "", "", null, time ?? DateTimeOffset.UtcNow, "92", "90");
|
||||
|
||||
private IActorRef SpawnMes(IActorRef instanceActor, IActorRef dclManager, bool mesReportable) =>
|
||||
ActorOf(Props.Create(() => new NativeAlarmActor(
|
||||
Source(mesReportable), "inst", instanceActor, dclManager, _storage, _options,
|
||||
NullLogger<NativeAlarmActor>.Instance, AlarmKind.NativeOpcUa, null)));
|
||||
|
||||
private IActorRef Spawn(IActorRef instanceActor, IActorRef dclManager, IServiceProvider? serviceProvider = null) =>
|
||||
Spawn(instanceActor, dclManager, _options, serviceProvider);
|
||||
|
||||
@@ -157,6 +163,25 @@ public class NativeAlarmActorTests : TestKit, IDisposable
|
||||
Assert.Equal("Pressure", emitted.NativeSourceCanonicalName);
|
||||
}
|
||||
|
||||
/// <summary>Gitea #59: a binding flagged MES-reportable stamps every condition it mirrors; an unflagged one does not.</summary>
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void Raise_StampsIsMesReportable_FromBindingFlag(bool bindingFlag)
|
||||
{
|
||||
var instance = CreateTestProbe();
|
||||
var dcl = CreateTestProbe();
|
||||
var actor = SpawnMes(instance.Ref, dcl.Ref, bindingFlag);
|
||||
dcl.ExpectMsg<SubscribeAlarmsRequest>();
|
||||
|
||||
actor.Tell(new NativeAlarmTransitionUpdate("Opc", Transition(
|
||||
"T01.Hi", AlarmTransitionKind.Raise,
|
||||
// Severity 100 is OUTSIDE the 900-999 band, so only the binding flag can set it.
|
||||
new AlarmConditionState(true, false, null, AlarmShelveState.Unshelved, false, 100))));
|
||||
|
||||
Assert.Equal(bindingFlag, instance.ExpectMsg<AlarmStateChanged>().IsMesReportable);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a snapshot completing with a missing prior alarm emits a return to normal.</summary>
|
||||
[Fact]
|
||||
public void SnapshotComplete_WithMissingPriorAlarm_EmitsReturnToNormal()
|
||||
|
||||
Reference in New Issue
Block a user