aafb9d4929
Wave-B EXEMPLAR. Retire the pre-declared/blob-parse tag model; the driver now builds its RawPath -> definition table from the artifact's RawTagEntry set via a pure mapper. - Contracts: rename ModbusEquipmentTagParser -> ModbusTagDefinitionFactory; TryParse(reference) -> FromTagConfig(tagConfig, rawPath) (def.Name = rawPath, no leading-brace heuristic); add inverse ToTagConfig(def) serializer; keep all strict-enum reads + guards; also read stringByteOrder/deadband/coalesceProhibited so a RawTagEntry round-trips the full authored def. Inspect() unchanged. - Options: Tags(IReadOnlyList<ModbusTagDefinition>) -> RawTags(IReadOnlyList<RawTagEntry>). - Driver: _tagsByName -> _tagsByRawPath (Ordinal); resolver byRawPath-only; build the table from RawTags at Initialize threading entry.WriteIdempotent onto each def; Discover/Teardown updated. - Factory: remove ModbusTagDto/BuildTag/ValidateStringLength + ConfigDto.Tags; bind RawTags from driver-config JSON. - CLI ModbusCommandBase.BuildOptions: serialise typed defs -> RawTagEntry via ToTagConfig. - Tests: migrate Tags= -> RawTags via ModbusRawTags.Entries helper; parser tests -> FromTagConfig(rawPath); factory String-length/enum tests re-seated on the mapper seam. - Propagated rename to ControlPlane EquipmentTagConfigInspector (outside Modbus projects). Modbus.Tests 315/315, Addressing.Tests 161/161, Cli.Tests 72/72 green. Docker-gated Driver.Modbus.IntegrationTests left untouched (won't compile against the new API; expected).
102 lines
5.1 KiB
C#
102 lines
5.1 KiB
C#
using CliFx.Attributes;
|
|
using CliFx.Exceptions;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
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. The CLI holds typed
|
|
/// definitions (synthesised from operator flags), so each is serialised back to a
|
|
/// <see cref="RawTagEntry"/> — the v3 delivery unit — via
|
|
/// <see cref="ModbusTagDefinitionFactory.ToTagConfig"/>, keyed by the def's synthesised name as
|
|
/// its RawPath. 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>
|
|
/// <returns>A <see cref="ModbusDriverOptions"/> ready to hand to the driver constructor.</returns>
|
|
protected ModbusDriverOptions BuildOptions(IReadOnlyList<ModbusTagDefinition> tags) => new()
|
|
{
|
|
Host = Host,
|
|
Port = Port,
|
|
UnitId = UnitId,
|
|
Timeout = Timeout,
|
|
AutoReconnect = !DisableAutoReconnect,
|
|
RawTags = [.. tags.Select(t => new RawTagEntry(t.Name, ModbusTagDefinitionFactory.ToTagConfig(t), t.WriteIdempotent))],
|
|
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>
|
|
/// 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.");
|
|
}
|
|
}
|