using CliFx.Attributes; using CliFx.Infrastructure; namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Cli.Commands; /// /// PR abcip-5.1 — print the current HSBY role on each chassis of a paired ControlLogix /// ControlLogix Hot-Standby setup. Requires --partner on the base command + /// reads WallClockTime.SyncStatus on both gateways once before printing. /// [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 . 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 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(); 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 ?? ""}"); } finally { await driver.ShutdownAsync(CancellationToken.None); } } }