using CliFx.Attributes; using ZB.MOM.WW.OtOpcUa.Driver.Cli.Common; namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Cli; /// /// Base for every AB CIP CLI command. Carries the libplctag endpoint options /// (--gateway + --family) and exposes so each /// command can synthesise an from CLI flags + its own /// tag list. /// public abstract class AbCipCommandBase : DriverCommandBase { /// Gets the canonical AB CIP gateway address. [CommandOption("gateway", 'g', Description = "Canonical AB CIP gateway: ab://host[:port]/cip-path. Port defaults to 44818 " + "(EtherNet/IP). cip-path is family-specific: ControlLogix / CompactLogix need " + "'1,0' to reach slot 0 of the CPU chassis; Micro800 takes an empty path; " + "GuardLogix typically '1,0' same as ControlLogix.", IsRequired = true)] public string Gateway { get; init; } = default!; /// Gets the PLC family type. [CommandOption("family", 'f', Description = "ControlLogix / CompactLogix / Micro800 / GuardLogix (default ControlLogix).")] public AbCipPlcFamily Family { get; init; } = AbCipPlcFamily.ControlLogix; /// Gets the per-operation timeout in milliseconds. [CommandOption("timeout-ms", Description = "Per-operation timeout in ms (default 5000).")] public int TimeoutMs { get; init; } = 5000; /// /// /// The getter validates (Driver.AbCip.Cli-004) — a zero or /// negative --timeout-ms would otherwise propagate as a non-positive /// into the driver. The init accessor is unreachable /// because CliFx binds rather than Timeout; it throws /// so an object-initializer assignment /// (new ReadCommand { Timeout = ... }) fails fast instead of being silently /// discarded (Driver.AbCip.Cli-006). /// public override TimeSpan Timeout { get { if (TimeoutMs <= 0) throw new CliFx.Exceptions.CommandException( $"--timeout-ms must be > 0 (got {TimeoutMs}). " + "Pick a positive number of milliseconds for the per-operation timeout."); return TimeSpan.FromMilliseconds(TimeoutMs); } init => throw new NotSupportedException( $"{nameof(AbCipCommandBase)}.{nameof(Timeout)} is derived from {nameof(TimeoutMs)} " + "and cannot be assigned directly. Set TimeoutMs instead."); } /// /// Build an with the device + tag list a subclass /// supplies. Probe + alarm projection are disabled — CLI runs are one-shot; the /// probe loop would race the operator's own reads. /// /// The list of tag definitions to include in the options. protected AbCipDriverOptions BuildOptions(IReadOnlyList tags) => new() { Devices = [new AbCipDeviceOptions( HostAddress: Gateway, PlcFamily: Family, DeviceName: $"cli-{Family}")], Tags = tags, Timeout = Timeout, Probe = new AbCipProbeOptions { Enabled = false }, EnableControllerBrowse = false, EnableAlarmProjection = false, }; /// /// Short instance id used in Serilog output so operators running the CLI against /// multiple gateways in parallel can distinguish the logs. /// protected string DriverInstanceId => $"abcip-cli-{Gateway}"; /// /// Guards against being passed to a command /// that does not support UDT layouts. Call at the top of ExecuteAsync for any /// command that accepts --type but cannot handle memberless Structure tags. /// Throws a if /// is . /// /// The data type to validate. protected static void RejectStructure(AbCipDataType type) { if (type == AbCipDataType.Structure) throw new CliFx.Exceptions.CommandException( "Structure (UDT) reads are out of scope for this command — those need an explicit " + "member layout, which belongs in a real driver config."); } }