using ZB.MOM.WW.ScadaBridge.Commons.Types.Alarms;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters;
///
/// Pure mapping helpers turning OPC UA Alarms & Conditions event fields into the
/// protocol-neutral / 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 RealOpcUaClient and is exercised by
/// the live smoke test.
///
public static class OpcUaAlarmMapper
{
/// Clamps an OPC UA severity (1–1000, sometimes out of range) to the unified 0–1000 scale.
/// The raw OPC UA severity value to clamp.
/// The severity clamped to the range [0, 1000].
public static int NormalizeSeverity(int severity) => Math.Clamp(severity, 0, 1000);
/// Builds an from the orthogonal A&C sub-states.
/// Whether the alarm condition is currently active.
/// Whether the alarm has been acknowledged.
/// Whether the alarm has been confirmed; null if not supported by the server.
/// The current shelving state of the alarm.
/// Whether the alarm is suppressed.
/// The raw OPC UA severity (1–1000); clamped via .
/// The composed .
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));
///
/// 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.
///
/// Acknowledged state before the event.
/// Acknowledged state after the event.
/// Active state before the event.
/// Active state after the event.
/// The that best describes the state change.
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;
}
/// Maps the OPC UA ShelvingState current-state node name to the shelve enum.
/// The OPC UA ShelvingState node name, or null when unshelved.
/// The corresponding ; defaults to .
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
};
///
/// Picks a representative display-only limit value from the four standard
/// LimitAlarmType set-point fields (HighHighLimit, HighLimit, LowLimit,
/// LowLowLimit) returned by the OPC UA event SelectClause.
///
///
/// The fields are absent (null raw value) on non-limit alarm types (discrete,
/// off-normal, etc.). When present, the first non-null value is returned in
/// priority order: HighHigh → High → Low → LowLow. The caller may use
/// AlarmTypeName or ConditionName to determine which specific
/// limit is active; this method intentionally returns the coarsest useful value
/// for the common single-limit case without requiring callers to understand the
/// OPC UA limit hierarchy.
///
///
/// Raw HighHighLimit field value (null when absent).
/// Raw HighLimit field value (null when absent).
/// Raw LowLimit field value (null when absent).
/// Raw LowLowLimit field value (null when absent).
///
/// A formatted string representation of the first non-null limit value, or an
/// empty string when all four fields are absent (non-limit alarm type).
///
public static string PickLimitValue(object? highHighRaw, object? highRaw, object? lowRaw, object? lowLowRaw)
{
// Standard OPC UA LimitAlarmType limit values are numeric (Double/Float/Int).
// Convert with InvariantCulture so the decimal separator is always '.' regardless
// of the server's locale.
foreach (var raw in new[] { highHighRaw, highRaw, lowRaw, lowLowRaw })
{
if (raw is not null)
return Convert.ToString(raw, System.Globalization.CultureInfo.InvariantCulture) ?? "";
}
return "";
}
}