feat(dcl): OPC UA A&C field mapper (Task 11 part 1 — pure, unit-tested)

This commit is contained in:
Joseph Doherty
2026-05-29 16:13:02 -04:00
parent d3b3d15018
commit 1fbb814daa
2 changed files with 110 additions and 0 deletions
@@ -0,0 +1,52 @@
using ZB.MOM.WW.ScadaBridge.Commons.Types.Alarms;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters;
/// <summary>
/// Pure mapping helpers turning OPC UA Alarms &amp; Conditions event fields into the
/// protocol-neutral <see cref="AlarmConditionState"/> / transition shape. Kept
/// free of any OPC UA SDK types so it is unit-testable without a live server;
/// the SDK field extraction lives in <c>RealOpcUaClient</c> and is exercised by
/// the live smoke test (Task 28).
/// </summary>
public static class OpcUaAlarmMapper
{
/// <summary>Clamps an OPC UA severity (11000, sometimes out of range) to the unified 01000 scale.</summary>
public static int NormalizeSeverity(int severity) => Math.Clamp(severity, 0, 1000);
/// <summary>Builds an <see cref="AlarmConditionState"/> from the orthogonal A&amp;C sub-states.</summary>
public static AlarmConditionState BuildCondition(
bool active, bool acked, bool? confirmed, AlarmShelveState shelve, bool suppressed, int severity) =>
new(Active: active, Acknowledged: acked, Confirmed: confirmed,
Shelve: shelve, Suppressed: suppressed, Severity: NormalizeSeverity(severity));
/// <summary>
/// Derives the transition kind from the change in active/acked sub-states.
/// Acknowledgement takes precedence over an active/inactive edge when both
/// change in the same event; an unchanged event is reported as a StateChange.
/// </summary>
public static AlarmTransitionKind DeriveKind(bool prevAcked, bool nowAcked, bool prevActive, bool nowActive)
{
if (!prevAcked && nowAcked)
return AlarmTransitionKind.Acknowledge;
if (!prevActive && nowActive)
return AlarmTransitionKind.Raise;
if (prevActive && !nowActive)
return AlarmTransitionKind.Clear;
if (prevActive && nowActive)
return AlarmTransitionKind.Retrigger;
return AlarmTransitionKind.StateChange;
}
/// <summary>Maps the OPC UA ShelvingState current-state node name to the shelve enum.</summary>
public static AlarmShelveState MapShelve(string? shelvingStateName) => shelvingStateName switch
{
"OneShotShelved" => AlarmShelveState.OneShotShelved,
"TimedShelved" => AlarmShelveState.TimedShelved,
// OPC UA does not expose a distinct "permanent" shelve; treat any other
// shelved name as one-shot and "Unshelved"/null as unshelved.
null or "Unshelved" => AlarmShelveState.Unshelved,
_ => AlarmShelveState.OneShotShelved
};
}