@@ -40,6 +40,18 @@ public abstract class AbCipCommandBase : DriverCommandBase
|
||||
"walk; unsupported on Micro800 (silent fallback to Symbolic with warning).")]
|
||||
public AddressingMode AddressingMode { get; init; } = AddressingMode.Auto;
|
||||
|
||||
/// <summary>
|
||||
/// PR abcip-5.1 — partner gateway URI for HSBY (Hot-Standby) paired chassis. When
|
||||
/// supplied, every CLI command auto-enables HSBY role probing on the device options
|
||||
/// so subcommands like <c>hsby-status</c> + diagnostics surface the active chassis
|
||||
/// without extra flags. Unset for non-redundant deployments.
|
||||
/// </summary>
|
||||
[CommandOption("partner", Description =
|
||||
"Partner gateway URI for ControlLogix HSBY pair (e.g. ab://10.0.0.6/1,0). When " +
|
||||
"set, the driver runs a second role-probe loop and the hsby-status command can " +
|
||||
"surface which chassis is currently Active. Optional.")]
|
||||
public string? Partner { get; init; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override TimeSpan Timeout
|
||||
{
|
||||
@@ -58,7 +70,17 @@ public abstract class AbCipCommandBase : DriverCommandBase
|
||||
HostAddress: Gateway,
|
||||
PlcFamily: Family,
|
||||
DeviceName: $"cli-{Family}",
|
||||
AddressingMode: AddressingMode)],
|
||||
AddressingMode: AddressingMode,
|
||||
// PR abcip-5.1 — surface --partner through the device options so commands that
|
||||
// use BuildOptions can take advantage of HSBY role probing without subclassing.
|
||||
// Hsby auto-enables only when a partner was actually supplied; pre-5.1 invocations
|
||||
// (no --partner) see exactly the legacy options shape.
|
||||
PartnerHostAddress: Partner,
|
||||
Hsby: string.IsNullOrWhiteSpace(Partner) ? null : new AbCipHsbyOptions
|
||||
{
|
||||
Enabled = true,
|
||||
ProbeInterval = TimeSpan.FromSeconds(2),
|
||||
})],
|
||||
Tags = tags,
|
||||
Timeout = Timeout,
|
||||
Probe = new AbCipProbeOptions { Enabled = false },
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user