using CliFx.Attributes; using CliFx.Exceptions; using ZB.MOM.WW.OtOpcUa.Driver.Cli.Common; namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.Cli; /// /// Base for every Modbus CLI command. Carries the Modbus-TCP endpoint options /// (host / port / unit-id) on top of 's verbose + timeout /// + logging helpers, and exposes so each command can turn its /// parsed flags into a ready to hand to the driver ctor. /// public abstract class ModbusCommandBase : DriverCommandBase { /// Gets the Modbus-TCP server hostname or IP address. [CommandOption("host", 'h', Description = "Modbus-TCP server hostname or IP", IsRequired = true)] public string Host { get; init; } = default!; /// Gets the Modbus-TCP port number. [CommandOption("port", 'p', Description = "Modbus-TCP port (default 502)")] public int Port { get; init; } = 502; /// Gets the Modbus unit ID (slave ID). [CommandOption("unit-id", 'U', Description = "Modbus unit / slave ID (1-247, default 1)")] public byte UnitId { get; init; } = 1; /// Gets the per-PDU timeout in milliseconds. [CommandOption("timeout-ms", Description = "Per-PDU timeout in milliseconds (default 2000)")] public int TimeoutMs { get; init; } = 2000; /// Gets a value indicating whether to disable automatic reconnection. [CommandOption("disable-reconnect", Description = "Disable the built-in mid-transaction reconnect-and-retry. Matches the driver's " + "AutoReconnect=false setting — use when diagnosing socket teardown behaviour.")] public bool DisableAutoReconnect { get; init; } /// public override TimeSpan Timeout { get => TimeSpan.FromMilliseconds(TimeoutMs); init { /* driven by TimeoutMs property; setter required to satisfy base's init contract */ } } /// /// Construct a with the endpoint fields this base /// collected + whatever the subclass declares. Probe is /// disabled — CLI runs are one-shot, the probe loop would race the operator's /// command against its own keep-alive reads. /// /// The tag definitions to include in the options. protected ModbusDriverOptions BuildOptions(IReadOnlyList tags) => new() { Host = Host, Port = Port, UnitId = UnitId, Timeout = Timeout, AutoReconnect = !DisableAutoReconnect, Tags = tags, Probe = new ModbusProbeOptions { Enabled = false }, }; /// /// Short instance id used in Serilog output so operators running the CLI against /// multiple endpoints in parallel can distinguish the logs. /// protected string DriverInstanceId => $"modbus-cli-{Host}:{Port}"; /// /// Driver.Modbus.Cli-003: validate the endpoint flags at parse time so the operator /// gets a clear CliFx error instead of an opaque socket / argument exception thrown /// deep inside the driver. Ranges: /// /// --port: 1..65535 (IANA TCP port space, excludes the /// "any" sentinel 0 and rejects negative / overflowed values). /// --timeout-ms: strictly positive — a non-positive /// would propagate as an immediate-timeout into the /// driver and surface as a confusing TimeoutException. /// --unit-id: 1..247 — the Modbus spec unicast unit-id range. /// 0 is the broadcast address and not valid for read/write requests; 248-255 /// are reserved. (Documented in docs/Driver.Modbus.Cli.md.) /// /// protected void ValidateEndpoint() { if (Port < 1 || Port > 65535) throw new CommandException( $"--port must be in 1..65535 (got {Port})."); if (TimeoutMs <= 0) throw new CommandException( $"--timeout-ms must be strictly positive (got {TimeoutMs})."); if (UnitId < 1 || UnitId > 247) throw new CommandException( $"--unit-id must be in 1..247 per the Modbus spec (got {UnitId}); " + $"0 is the broadcast address, 248-255 are reserved."); } }