feat(alarms): HiLo trigger type with per-band level, hysteresis, messages, overrides

Adds a new HiLo alarm trigger type with four configurable setpoints
(LoLo / Lo / Hi / HiHi). Each setpoint carries an optional priority,
deadband (for hysteresis), and operator message. The site runtime emits
AlarmStateChanged with an AlarmLevel field so consumers can differentiate
warning vs critical bands.

Plumbing:
  - new AlarmLevel enum + AlarmStateChanged.Level/Message init properties
  - AlarmTriggerEditor (Blazor) gets a HiLo render with severity tinting
  - AlarmTriggerConfigCodec extracted from the editor for testability
  - sitestream.proto carries level + message over gRPC
  - SemanticValidator enforces numeric attribute, setpoint ordering,
    non-negative deadband
  - on-trigger scripts get an Alarm global (Name/Level/Priority/Message)
    so notification routing can branch by severity
  - per-instance InstanceAlarmOverride entity + EF migration + flattening
    step + CLI commands; HiLo overrides merge setpoint-by-setpoint, binary
    types whole-replace
  - DebugView shows a Level badge + per-band message tooltip
  - App.razor auto-reloads on permanent Blazor circuit failure
  - docker/regen-proto.sh automates the proto regen workflow (the linux/arm64
    protoc segfault means generated files are checked in for now)
This commit is contained in:
Joseph Doherty
2026-05-13 03:23:32 -04:00
parent 783da8e21a
commit 751248feb6
46 changed files with 4693 additions and 204 deletions

View File

@@ -6,6 +6,7 @@ using ScadaLink.Commons.Messages.Streaming;
using ScadaLink.Commons.Types;
using ScadaLink.Communication.Grpc;
using AlarmState = ScadaLink.Commons.Types.Enums.AlarmState;
using AlarmLevel = ScadaLink.Commons.Types.Enums.AlarmLevel;
namespace ScadaLink.Communication.Actors;
@@ -59,7 +60,9 @@ public class StreamRelayActor : ReceiveActor
AlarmName = msg.AlarmName,
State = MapAlarmState(msg.State),
Priority = msg.Priority,
Timestamp = Timestamp.FromDateTimeOffset(msg.Timestamp)
Timestamp = Timestamp.FromDateTimeOffset(msg.Timestamp),
Level = MapAlarmLevel(msg.Level),
Message = msg.Message ?? string.Empty
}
};
@@ -88,4 +91,13 @@ public class StreamRelayActor : ReceiveActor
AlarmState.Active => AlarmStateEnum.AlarmStateActive,
_ => AlarmStateEnum.AlarmStateUnspecified
};
private static AlarmLevelEnum MapAlarmLevel(AlarmLevel level) => level switch
{
AlarmLevel.Low => AlarmLevelEnum.AlarmLevelLow,
AlarmLevel.LowLow => AlarmLevelEnum.AlarmLevelLowLow,
AlarmLevel.High => AlarmLevelEnum.AlarmLevelHigh,
AlarmLevel.HighHigh => AlarmLevelEnum.AlarmLevelHighHigh,
_ => AlarmLevelEnum.AlarmLevelNone
};
}