Files
ScadaBridge/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/OpcUaAlarmMapper.cs
T
Joseph Doherty 9cff87fe85 docs(comments): strip internal task/milestone/bundle bookkeeping from code comments
Remove project bookkeeping citations from shipped code comments across the
solution: hyphenated task IDs (WP-14, StoreAndForward-025), milestone/task/
issue refs (M3, Task 4, Audit Log #23, #21), Bundle X task-bundle labels,
and C/D/K/S/T phase labels.

Comment text only — no code logic, string/log literals, or XML-doc structure
changed. Genuine descriptions are preserved (only the citation is stripped),
and technical lookalikes are retained (UTF-8, SHA-256, T00:00:00, M365,
UTC-5, pre-C4/pre-C5 schema versions). Flagged by the new CommentChecker
TaskReferenceInComment / TrackingReferenceInComment checks plus targeted
grep passes; full solution builds clean, append-only guard tests pass.
2026-07-07 11:03:26 -04:00

105 lines
5.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
/// </summary>
public static class OpcUaAlarmMapper
{
/// <summary>Clamps an OPC UA severity (11000, sometimes out of range) to the unified 01000 scale.</summary>
/// <param name="severity">The raw OPC UA severity value to clamp.</param>
/// <returns>The severity clamped to the range [0, 1000].</returns>
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>
/// <param name="active">Whether the alarm condition is currently active.</param>
/// <param name="acked">Whether the alarm has been acknowledged.</param>
/// <param name="confirmed">Whether the alarm has been confirmed; null if not supported by the server.</param>
/// <param name="shelve">The current shelving state of the alarm.</param>
/// <param name="suppressed">Whether the alarm is suppressed.</param>
/// <param name="severity">The raw OPC UA severity (11000); clamped via <see cref="NormalizeSeverity"/>.</param>
/// <returns>The composed <see cref="AlarmConditionState"/>.</returns>
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>
/// <param name="prevAcked">Acknowledged state before the event.</param>
/// <param name="nowAcked">Acknowledged state after the event.</param>
/// <param name="prevActive">Active state before the event.</param>
/// <param name="nowActive">Active state after the event.</param>
/// <returns>The <see cref="AlarmTransitionKind"/> that best describes the state change.</returns>
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>
/// <param name="shelvingStateName">The OPC UA ShelvingState node name, or null when unshelved.</param>
/// <returns>The corresponding <see cref="AlarmShelveState"/>; defaults to <see cref="AlarmShelveState.Unshelved"/>.</returns>
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
};
/// <summary>
/// Picks a representative display-only limit value from the four standard
/// <c>LimitAlarmType</c> set-point fields (HighHighLimit, HighLimit, LowLimit,
/// LowLowLimit) returned by the OPC UA event SelectClause.
///
/// <para>
/// 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
/// <c>AlarmTypeName</c> or <c>ConditionName</c> 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.
/// </para>
/// </summary>
/// <param name="highHighRaw">Raw HighHighLimit field value (null when absent).</param>
/// <param name="highRaw">Raw HighLimit field value (null when absent).</param>
/// <param name="lowRaw">Raw LowLimit field value (null when absent).</param>
/// <param name="lowLowRaw">Raw LowLowLimit field value (null when absent).</param>
/// <returns>
/// 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).
/// </returns>
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 "";
}
}