Files
lmxopcua/src/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Cli/ModbusCommandBase.cs
T
Joseph Doherty 64e3fbe035
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
docs: backfill XML documentation across 756 files
Adds <summary>, <param>, <typeparam>, and <inheritdoc/> tags to public
members surfaced by commentchecker — resolves 5,847 of 5,869 issues
(99.6%) across three /fixdocs passes.
2026-05-28 08:10:17 -04:00

97 lines
4.5 KiB
C#

using CliFx.Attributes;
using CliFx.Exceptions;
using ZB.MOM.WW.OtOpcUa.Driver.Cli.Common;
namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.Cli;
/// <summary>
/// Base for every Modbus CLI command. Carries the Modbus-TCP endpoint options
/// (host / port / unit-id) on top of <see cref="DriverCommandBase"/>'s verbose + timeout
/// + logging helpers, and exposes <see cref="BuildOptions"/> so each command can turn its
/// parsed flags into a <see cref="ModbusDriverOptions"/> ready to hand to the driver ctor.
/// </summary>
public abstract class ModbusCommandBase : DriverCommandBase
{
/// <summary>Gets the Modbus-TCP server hostname or IP address.</summary>
[CommandOption("host", 'h', Description = "Modbus-TCP server hostname or IP", IsRequired = true)]
public string Host { get; init; } = default!;
/// <summary>Gets the Modbus-TCP port number.</summary>
[CommandOption("port", 'p', Description = "Modbus-TCP port (default 502)")]
public int Port { get; init; } = 502;
/// <summary>Gets the Modbus unit ID (slave ID).</summary>
[CommandOption("unit-id", 'U', Description = "Modbus unit / slave ID (1-247, default 1)")]
public byte UnitId { get; init; } = 1;
/// <summary>Gets the per-PDU timeout in milliseconds.</summary>
[CommandOption("timeout-ms", Description = "Per-PDU timeout in milliseconds (default 2000)")]
public int TimeoutMs { get; init; } = 2000;
/// <summary>Gets a value indicating whether to disable automatic reconnection.</summary>
[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; }
/// <inheritdoc />
public override TimeSpan Timeout
{
get => TimeSpan.FromMilliseconds(TimeoutMs);
init { /* driven by TimeoutMs property; setter required to satisfy base's init contract */ }
}
/// <summary>
/// Construct a <see cref="ModbusDriverOptions"/> with the endpoint fields this base
/// collected + whatever <paramref name="tags"/> 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.
/// </summary>
/// <param name="tags">The tag definitions to include in the options.</param>
protected ModbusDriverOptions BuildOptions(IReadOnlyList<ModbusTagDefinition> tags) => new()
{
Host = Host,
Port = Port,
UnitId = UnitId,
Timeout = Timeout,
AutoReconnect = !DisableAutoReconnect,
Tags = tags,
Probe = new ModbusProbeOptions { Enabled = false },
};
/// <summary>
/// Short instance id used in Serilog output so operators running the CLI against
/// multiple endpoints in parallel can distinguish the logs.
/// </summary>
protected string DriverInstanceId => $"modbus-cli-{Host}:{Port}";
/// <summary>
/// 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:
/// <list type="bullet">
/// <item><c>--port</c>: 1..65535 (IANA TCP port space, excludes the
/// "any" sentinel 0 and rejects negative / overflowed values).</item>
/// <item><c>--timeout-ms</c>: strictly positive — a non-positive
/// <see cref="TimeSpan"/> would propagate as an immediate-timeout into the
/// driver and surface as a confusing TimeoutException.</item>
/// <item><c>--unit-id</c>: 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 <c>docs/Driver.Modbus.Cli.md</c>.)</item>
/// </list>
/// </summary>
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.");
}
}