104 lines
4.7 KiB
C#
104 lines
4.7 KiB
C#
using CliFx.Attributes;
|
|
using CliFx.Infrastructure;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Cli.Commands;
|
|
|
|
/// <summary>
|
|
/// PR abcip-5.1 — print the current HSBY role on each chassis of a paired ControlLogix
|
|
/// ControlLogix Hot-Standby setup. Requires <c>--partner</c> on the base command +
|
|
/// reads <c>WallClockTime.SyncStatus</c> on both gateways once before printing.
|
|
/// </summary>
|
|
[Command("hsby-status", Description =
|
|
"Read the WallClockTime.SyncStatus role tag on a ControlLogix HSBY pair and print " +
|
|
"which chassis is currently Active. Requires --partner.")]
|
|
public sealed class HsbyStatusCommand : AbCipCommandBase
|
|
{
|
|
[CommandOption("role-tag", Description =
|
|
"Role-tag address. Default WallClockTime.SyncStatus matches v20+ ControlLogix HSBY; " +
|
|
"use S:34 for legacy SLC500 / PLC-5 status-byte fronts.")]
|
|
public string RoleTagAddress { get; init; } = "WallClockTime.SyncStatus";
|
|
|
|
[CommandOption("samples", Description =
|
|
"Number of role-probe ticks to wait for before printing (default 3). Larger values " +
|
|
"give the role-prober loop more chances to sample both chassis through transient " +
|
|
"transport hiccups.")]
|
|
public int Samples { get; init; } = 3;
|
|
|
|
public override async ValueTask ExecuteAsync(IConsole console)
|
|
{
|
|
ConfigureLogging();
|
|
var ct = console.RegisterCancellationHandler();
|
|
|
|
if (string.IsNullOrWhiteSpace(Partner))
|
|
{
|
|
await console.Error.WriteLineAsync(
|
|
"hsby-status requires --partner <ab://gateway/cip-path>. Without a partner the " +
|
|
"command has no second chassis to compare roles against.");
|
|
return;
|
|
}
|
|
|
|
// Override the base BuildOptions so we can pin the role-tag address + a tight probe
|
|
// interval — the default 2 s would mean Samples * 2 s before the print fires, too slow
|
|
// for an interactive CLI. Tag list stays empty; only the role probe runs.
|
|
var options = new AbCipDriverOptions
|
|
{
|
|
Devices = [new AbCipDeviceOptions(
|
|
HostAddress: Gateway,
|
|
PlcFamily: Family,
|
|
DeviceName: $"cli-{Family}",
|
|
AddressingMode: AddressingMode,
|
|
PartnerHostAddress: Partner,
|
|
Hsby: new AbCipHsbyOptions
|
|
{
|
|
Enabled = true,
|
|
RoleTagAddress = RoleTagAddress,
|
|
ProbeInterval = TimeSpan.FromMilliseconds(500),
|
|
})],
|
|
Tags = [],
|
|
Timeout = Timeout,
|
|
Probe = new AbCipProbeOptions { Enabled = false },
|
|
EnableControllerBrowse = false,
|
|
EnableAlarmProjection = false,
|
|
};
|
|
|
|
await using var driver = new AbCipDriver(options, DriverInstanceId);
|
|
try
|
|
{
|
|
await driver.InitializeAsync("{}", ct);
|
|
|
|
// Wait Samples * ProbeInterval so the role probe has had time to sample each
|
|
// chassis at least <Samples> times. The role probe loop spins inside the driver;
|
|
// we just sleep + read GetDeviceState's ActiveAddress.
|
|
await Task.Delay(TimeSpan.FromMilliseconds(500 * Math.Max(1, Samples)), ct);
|
|
|
|
// Pull HSBY state out via DriverHealth.Diagnostics. Single-pair config emits
|
|
// the flat AbCip.HsbyActive / AbCip.HsbyPrimaryRole / AbCip.HsbyPartnerRole keys.
|
|
var diag = driver.GetHealth().Diagnostics
|
|
?? new Dictionary<string, double>();
|
|
var primaryRole = diag.TryGetValue("AbCip.HsbyPrimaryRole", out var pr)
|
|
? (HsbyRole)(int)pr : HsbyRole.Unknown;
|
|
var partnerRole = diag.TryGetValue("AbCip.HsbyPartnerRole", out var qr)
|
|
? (HsbyRole)(int)qr : HsbyRole.Unknown;
|
|
var activeCode = diag.TryGetValue("AbCip.HsbyActive", out var ac) ? (int)ac : 0;
|
|
var activeAddress = activeCode switch
|
|
{
|
|
1 => Gateway,
|
|
2 => Partner,
|
|
_ => null,
|
|
};
|
|
|
|
await console.Output.WriteLineAsync($"Primary: {Gateway}");
|
|
await console.Output.WriteLineAsync($"Partner: {Partner}");
|
|
await console.Output.WriteLineAsync($"Role tag: {RoleTagAddress}");
|
|
await console.Output.WriteLineAsync();
|
|
await console.Output.WriteLineAsync($"Primary role: {primaryRole}");
|
|
await console.Output.WriteLineAsync($"Partner role: {partnerRole}");
|
|
await console.Output.WriteLineAsync($"Active chassis: {activeAddress ?? "<none>"}");
|
|
}
|
|
finally
|
|
{
|
|
await driver.ShutdownAsync(CancellationToken.None);
|
|
}
|
|
}
|
|
}
|