using CliFx.Attributes; using CliFx.Exceptions; using ZB.MOM.WW.OtOpcUa.Driver.Cli.Common; namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Cli; /// /// Base for every TwinCAT CLI command. Carries the AMS target options /// (--ams-net-id + --ams-port) + the per-call timeout. Commands that build /// a single-device / single-tag from flag input inherit /// from instead — that intermediate adds the /// --poll-only flag and the BuildOptions helper. /// public abstract class TwinCATCommandBase : DriverCommandBase { /// Gets the AMS Net ID of the target runtime. [CommandOption("ams-net-id", 'n', Description = "AMS Net ID of the target runtime (e.g. '192.168.1.40.1.1' or '127.0.0.1.1.1' for local).", IsRequired = true)] public string AmsNetId { get; init; } = default!; /// Gets the AMS port number. [CommandOption("ams-port", 'p', Description = "AMS port. TwinCAT 3 PLC runtime defaults to 851; TwinCAT 2 uses 801.")] public int AmsPort { get; init; } = 851; /// Gets the per-operation timeout in milliseconds. [CommandOption("timeout-ms", Description = "Per-operation timeout in ms (default 5000).")] public int TimeoutMs { get; init; } = 5000; /// /// Gets the per-operation timeout, projected from . The CliFx /// init accessor required by the abstract base property is intentionally a /// no-op: is the only source of truth, so any value an /// `init` initialiser supplies to this property directly is silently /// dropped. Do NOT add a backing field "fixing" the empty body — it would diverge /// from and the two would drift on every refactor /// (Driver.TwinCAT.Cli-007). /// /// public override TimeSpan Timeout { get => TimeSpan.FromMilliseconds(TimeoutMs); init { /* see XML summary — driven by TimeoutMs */ } } /// /// Gets the canonical TwinCAT gateway string the driver's TwinCATAmsAddress.TryParse /// consumes — shape ads://{AmsNetId}:{AmsPort}. /// protected string Gateway => $"ads://{AmsNetId}:{AmsPort}"; /// Gets the driver instance ID for this command. protected string DriverInstanceId => $"twincat-cli-{AmsNetId}:{AmsPort}"; /// /// Validates the numeric options every TwinCAT CLI command shares (timeout + AMS port). /// Subclasses override and call base.Validate() first to add their own range /// checks. Throwing here surfaces a clean CliFx one-line error before the driver gets /// a chance to fail with an opaque transport error (Driver.TwinCAT.Cli-001). /// protected virtual void Validate() { if (TimeoutMs <= 0) throw new CommandException( $"--timeout-ms must be greater than 0 (got {TimeoutMs})."); if (AmsPort is <= 0 or > 65535) throw new CommandException( $"--ams-port must be in the range 1..65535 (got {AmsPort})."); } // ---- Test hooks ---- // Protected members are exposed to the test assembly through these internal accessors so the // test project can cover Gateway / DriverInstanceId composition + range validation without // needing reflection on every assertion (Driver.TwinCAT.Cli-006). /// Gets the gateway string for testing. internal string GatewayForTest => Gateway; /// Gets the driver instance ID for testing. internal string DriverInstanceIdForTest => DriverInstanceId; /// Validates the command for testing. internal void ValidateForTest() => Validate(); }