75 lines
3.9 KiB
C#
75 lines
3.9 KiB
C#
namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
|
|
/// <summary>
|
|
/// The one worst-of-input rule every derived-value seam uses — scripted alarms, virtual tags and
|
|
/// the Calculation driver.
|
|
///
|
|
/// <para><b>The contract it implements</b> (archreview R3-02, "the stale-Good family"): a value
|
|
/// consumer never reports a quality better than its source's. A node derived from driver values
|
|
/// carries, at worst, the worst status among its inputs — a Bad input makes the derived node Bad
|
|
/// with that input's sub-code, an Uncertain input makes it Uncertain, and a source that has
|
|
/// stopped communicating is a Bad input for everything downstream of it.</para>
|
|
///
|
|
/// <para><b>Why it lives here.</b> Core.Abstractions is the one assembly all three seams already
|
|
/// reference (Commons, Runtime and the driver assemblies all sit above it), and
|
|
/// <see cref="DataValueSnapshot"/> — the shape the rule reads — is declared beside it. Stating
|
|
/// the rule three times is how the three seams drifted apart in the first place.</para>
|
|
///
|
|
/// <para><b>Severity, not equality.</b> Comparison is on the OPC UA Part 4 severity bits (the top
|
|
/// two: <c>00</c> Good, <c>01</c> Uncertain, <c>10</c>/<c>11</c> Bad); the sub-code below them is
|
|
/// carried through verbatim because the sub-code is the diagnostic
|
|
/// (<c>BadNoCommunication</c> = go look at the device, <c>BadWaitingForInitialData</c> = it never
|
|
/// arrived, <c>BadInternalError</c> = the script).</para>
|
|
/// </summary>
|
|
public static class InputQuality
|
|
{
|
|
/// <summary>
|
|
/// No value has ever been received for this input — the status a fresh node is materialised
|
|
/// with, and what <see cref="WorstOf"/> reports for a dependency slot that holds no snapshot
|
|
/// at all. Spelled here rather than taken from <c>Commons.OpcUa.OpcUaStatus</c> because
|
|
/// Core.Abstractions sits <em>below</em> Commons and deliberately references nothing.
|
|
/// </summary>
|
|
public const uint BadWaitingForInitialData = 0x80320000u;
|
|
|
|
/// <summary>
|
|
/// The worst status among <paramref name="inputs"/>: the highest Part 4 severity, carrying
|
|
/// that input's own sub-code. A <see langword="null"/> entry is a dependency that has never
|
|
/// been pushed and reports <see cref="BadWaitingForInitialData"/> — reporting Good for it is
|
|
/// the laundering this helper exists to stop. An empty set is Good: there is nothing to be
|
|
/// worse than.
|
|
/// </summary>
|
|
/// <param name="inputs">The dependency snapshots, in a stable order.</param>
|
|
/// <returns>The worst status code observed.</returns>
|
|
/// <remarks>
|
|
/// Among <em>equal</em> severity the first entry seen wins, so a re-evaluation over the same
|
|
/// inputs reports the same sub-code instead of oscillating between two equally-bad codes.
|
|
/// </remarks>
|
|
public static uint WorstOf(IEnumerable<DataValueSnapshot?> inputs)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(inputs);
|
|
|
|
var worst = 0u;
|
|
var worstSeverity = 0u;
|
|
foreach (var input in inputs)
|
|
{
|
|
var status = input?.StatusCode ?? BadWaitingForInitialData;
|
|
var severity = status >> 30;
|
|
if (severity <= worstSeverity) continue;
|
|
worstSeverity = severity;
|
|
worst = status;
|
|
}
|
|
|
|
return worst;
|
|
}
|
|
|
|
/// <summary>
|
|
/// True when a consumer may evaluate against a value carrying <paramref name="statusCode"/>.
|
|
/// Good and Uncertain are usable — an Uncertain value is present and a script can make a
|
|
/// qualified decision from it; Bad is not, and a consumer holds instead (the alarm engine's
|
|
/// long-standing policy, generalised).
|
|
/// </summary>
|
|
/// <param name="statusCode">The status code to classify.</param>
|
|
/// <returns><see langword="true"/> for Good or Uncertain severity.</returns>
|
|
public static bool IsUsable(uint statusCode) => (statusCode >> 30) < 2;
|
|
}
|