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.");
}
}