84 lines
3.8 KiB
C#
84 lines
3.8 KiB
C#
using System.Text;
|
|
using System.Text.Json;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.CLI;
|
|
|
|
/// <summary>
|
|
/// Serializes typed alarm-setpoint CLI flags into the trigger-config JSON the
|
|
/// server expects. Key names MUST stay in lockstep with the canonical codec at
|
|
/// src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/AlarmTriggerConfigCodec.cs
|
|
/// (that codec is internal to CentralUI, so this is a deliberate CLI-side mirror;
|
|
/// the round-trip test verifies the JSON against the live server — the real contract).
|
|
/// </summary>
|
|
internal static class AlarmTriggerConfigJson
|
|
{
|
|
/// <summary>
|
|
/// Builds the trigger-config JSON for <paramref name="triggerType"/> from the typed
|
|
/// flags, or returns null when none are supplied (so the alarm is created without a
|
|
/// trigger config). Unknown/blank trigger types yield null.
|
|
/// </summary>
|
|
internal static string? Build(
|
|
string triggerType, string? attribute,
|
|
string? matchValue, bool notEquals,
|
|
double? min, double? max,
|
|
double? thresholdPerSecond, double? windowSeconds, string? direction,
|
|
double? loLo, double? lo, double? hi, double? hiHi,
|
|
string? expression)
|
|
{
|
|
var type = triggerType?.Trim();
|
|
var anyTyped = attribute is not null || matchValue is not null || notEquals
|
|
|| min.HasValue || max.HasValue || thresholdPerSecond.HasValue || windowSeconds.HasValue
|
|
|| direction is not null || loLo.HasValue || lo.HasValue || hi.HasValue || hiHi.HasValue
|
|
|| expression is not null;
|
|
if (!anyTyped) return null;
|
|
|
|
using var stream = new MemoryStream();
|
|
using (var w = new Utf8JsonWriter(stream))
|
|
{
|
|
w.WriteStartObject();
|
|
if (!string.Equals(type, "Expression", StringComparison.OrdinalIgnoreCase))
|
|
w.WriteString("attributeName", attribute ?? "");
|
|
|
|
switch (type?.ToLowerInvariant())
|
|
{
|
|
case "valuematch":
|
|
var mv = matchValue ?? "";
|
|
if (notEquals) mv = "!=" + mv;
|
|
w.WriteString("matchValue", mv);
|
|
break;
|
|
case "rangeviolation":
|
|
if (min.HasValue) w.WriteNumber("min", min.Value);
|
|
if (max.HasValue) w.WriteNumber("max", max.Value);
|
|
break;
|
|
case "rateofchange":
|
|
if (thresholdPerSecond.HasValue) w.WriteNumber("thresholdPerSecond", thresholdPerSecond.Value);
|
|
if (windowSeconds.HasValue) w.WriteNumber("windowSeconds", windowSeconds.Value);
|
|
w.WriteString("direction", NormalizeDirection(direction));
|
|
break;
|
|
case "hilo":
|
|
// Only the four setpoints are exposed as flags. The codec also accepts
|
|
// per-setpoint priorities/deadbands/messages — intentionally omitted here;
|
|
// use raw --trigger-config for those (see the YAGNI scope guard in the plan).
|
|
if (loLo.HasValue) w.WriteNumber("loLo", loLo.Value);
|
|
if (lo.HasValue) w.WriteNumber("lo", lo.Value);
|
|
if (hi.HasValue) w.WriteNumber("hi", hi.Value);
|
|
if (hiHi.HasValue) w.WriteNumber("hiHi", hiHi.Value);
|
|
break;
|
|
case "expression":
|
|
w.WriteString("expression", expression ?? "");
|
|
break;
|
|
}
|
|
w.WriteEndObject();
|
|
}
|
|
return Encoding.UTF8.GetString(stream.ToArray());
|
|
}
|
|
|
|
// Mirrors AlarmTriggerConfigCodec.NormalizeDirection.
|
|
private static string NormalizeDirection(string? raw) => raw?.ToLowerInvariant() switch
|
|
{
|
|
"rising" or "up" or "positive" => "rising",
|
|
"falling" or "down" or "negative" => "falling",
|
|
_ => "either",
|
|
};
|
|
}
|