124
src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipHsbyRoleProber.cs
Normal file
124
src/ZB.MOM.WW.OtOpcUa.Driver.AbCip/AbCipHsbyRoleProber.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip;
|
||||
|
||||
/// <summary>
|
||||
/// PR abcip-5.1 — resolved HSBY role for one chassis in a ControlLogix Hot-Standby pair.
|
||||
/// <see cref="Unknown"/> covers "couldn't read the role tag" (transport failure, tag not
|
||||
/// found, decode failure); the driver treats it as "no information yet, don't change
|
||||
/// ActiveAddress" rather than as a vote for Standby.
|
||||
/// </summary>
|
||||
public enum HsbyRole
|
||||
{
|
||||
/// <summary>Read failed or value was not decodable. Surface as "no information".</summary>
|
||||
Unknown = 0,
|
||||
|
||||
/// <summary>Chassis is the active member of the HSBY pair (Synchronized + serving I/O).</summary>
|
||||
Active = 1,
|
||||
|
||||
/// <summary>Chassis is the standby member — Synchronized but not driving I/O.</summary>
|
||||
Standby = 2,
|
||||
|
||||
/// <summary>Chassis has been disqualified by the HSBY module (e.g. firmware mismatch).</summary>
|
||||
Disqualified = 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PR abcip-5.1 — reads a ControlLogix HSBY role tag from one chassis and maps the value
|
||||
/// to <see cref="HsbyRole"/>. Two address formats are supported:
|
||||
/// <list type="bullet">
|
||||
/// <item><b>v20 / v24 / v32+ ControlLogix HSBY</b> — <c>WallClockTime.SyncStatus</c>
|
||||
/// (DINT-typed). Values: <c>0 = Standby</c>, <c>1 = Synchronized / Active</c>,
|
||||
/// <c>2 = Disqualified</c>. Other values map to <see cref="HsbyRole.Unknown"/>.</item>
|
||||
/// <item><b>PLC-5 / SLC500 fallback</b> — <c>S:34</c> Module Status word. Bit 0 of the
|
||||
/// integer value indicates "this chassis is Active"; the prober applies the
|
||||
/// bit-mask interpretation when the address starts with <c>"S:"</c> + maps
|
||||
/// <c>(value & 1) == 1 → Active</c>, otherwise → Standby.</item>
|
||||
/// </list>
|
||||
/// Read failure (initialise / read throw, non-zero libplctag status, undecodable buffer)
|
||||
/// returns <see cref="HsbyRole.Unknown"/> — callers (the driver's HSBY probe loop)
|
||||
/// interpret Unknown as "leave ActiveAddress alone for this tick".
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The prober is stateless / static — the per-chassis runtime is provided by
|
||||
/// <see cref="AbCipDriver.ProbeLoopAsync"/> + drives initialise / read on the runtime
|
||||
/// before delegating to <see cref="ProbeAsync"/>. Keeping the value-mapping logic isolated
|
||||
/// here lets unit tests assert the matrix (0 / 1 / 2 / S:34 bit 0 / unknown values) without
|
||||
/// standing up a probe loop.
|
||||
/// </remarks>
|
||||
public static class AbCipHsbyRoleProber
|
||||
{
|
||||
/// <summary>
|
||||
/// Read <paramref name="roleTagAddress"/> on <paramref name="runtime"/> + map the
|
||||
/// decoded value to a <see cref="HsbyRole"/>. The runtime is already initialised by
|
||||
/// the caller (<see cref="AbCipDriver.ProbeLoopAsync"/> shares the same lazy-init
|
||||
/// pattern with the regular probe loop); this method only issues the read + decodes.
|
||||
/// </summary>
|
||||
public static async Task<HsbyRole> ProbeAsync(
|
||||
IAbCipTagRuntime runtime, string roleTagAddress, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(runtime);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(roleTagAddress);
|
||||
try
|
||||
{
|
||||
await runtime.ReadAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (runtime.GetStatus() != 0) return HsbyRole.Unknown;
|
||||
var raw = runtime.DecodeValue(AbCipDataType.DInt, bitIndex: null);
|
||||
return MapValueToRole(raw, roleTagAddress);
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Wire / init / decode failure — surface as Unknown so the caller doesn't
|
||||
// misinterpret a transient transport hiccup as "this chassis went Standby".
|
||||
return HsbyRole.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pure value-to-role mapper. Exposed for unit tests so the matrix assertions can run
|
||||
/// without a runtime in scope. <see cref="ProbeAsync"/> is the production entry point.
|
||||
/// </summary>
|
||||
public static HsbyRole MapValueToRole(object? raw, string roleTagAddress)
|
||||
{
|
||||
if (raw is null) return HsbyRole.Unknown;
|
||||
if (!TryToInt64(raw, out var value)) return HsbyRole.Unknown;
|
||||
|
||||
// PLC-5 / SLC500 status-file fallback — bit 0 of S:34 is the role bit. Pattern-match
|
||||
// on the "S:" prefix because operators do put the file number after it (S:34, S:2,
|
||||
// etc) + the role bit lives in S:34 specifically on PLC-5 fronts but the bit-mask
|
||||
// semantics apply to any S:NN address an integration plumbs in.
|
||||
if (roleTagAddress.StartsWith("S:", StringComparison.OrdinalIgnoreCase))
|
||||
return (value & 1) == 1 ? HsbyRole.Active : HsbyRole.Standby;
|
||||
|
||||
// Default — WallClockTime.SyncStatus matrix (v20 / v24 / v32+ ControlLogix HSBY).
|
||||
return value switch
|
||||
{
|
||||
0 => HsbyRole.Standby,
|
||||
1 => HsbyRole.Active,
|
||||
2 => HsbyRole.Disqualified,
|
||||
_ => HsbyRole.Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
private static bool TryToInt64(object raw, out long value)
|
||||
{
|
||||
switch (raw)
|
||||
{
|
||||
case long l: value = l; return true;
|
||||
case int i: value = i; return true;
|
||||
case short s: value = s; return true;
|
||||
case sbyte sb: value = sb; return true;
|
||||
case byte b: value = b; return true;
|
||||
case ushort us: value = us; return true;
|
||||
case uint ui: value = ui; return true;
|
||||
case ulong ul when ul <= long.MaxValue: value = (long)ul; return true;
|
||||
case bool boolean: value = boolean ? 1 : 0; return true;
|
||||
case string str when long.TryParse(str, System.Globalization.NumberStyles.Integer,
|
||||
System.Globalization.CultureInfo.InvariantCulture, out var parsed):
|
||||
value = parsed; return true;
|
||||
default: value = 0; return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user