f2ee027145
- Driver.TwinCAT.Cli-001: TwinCATCommandBase.Validate rejects non-positive TimeoutMs / IntervalMs and AmsPort outside 1..65535; ExecuteAsync calls it first. - Driver.TwinCAT.Cli-002: SubscribeCommand serialises every WriteLine through a writeLock to remove the notification-callback vs banner interleave risk. - Driver.TwinCAT.Cli-003: SubscribeCommand.DescribeMechanism derives the banner label from the returned ISubscriptionHandle.DiagnosticId so it can't disagree with what the driver actually did. - Driver.TwinCAT.Cli-004: introduced TwinCATTagCommandBase carrying --poll-only + BuildOptions; BrowseCommand stays on the slimmer TwinCATCommandBase so --poll-only no longer surfaces in browse --help. - Driver.TwinCAT.Cli-005: ProbeCommand --type now carries the 't' short alias to match the other commands. - Driver.TwinCAT.Cli-006: 35 new tests covering Gateway / AmsAddress parse / BuildOptions / PollOnly / browse-helpers / probe-alias / mechanism derivation. - Driver.TwinCAT.Cli-007: replaced the empty-init <inheritdoc/> with an explicit summary warning future maintainers about the no-op init. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
75 lines
3.5 KiB
C#
75 lines
3.5 KiB
C#
using CliFx.Attributes;
|
|
using CliFx.Exceptions;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Cli.Common;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Cli;
|
|
|
|
/// <summary>
|
|
/// Base for every TwinCAT CLI command. Carries the AMS target options
|
|
/// (<c>--ams-net-id</c> + <c>--ams-port</c>) + the per-call timeout. Commands that build
|
|
/// a single-device / single-tag <see cref="TwinCATDriverOptions"/> from flag input inherit
|
|
/// from <see cref="TwinCATTagCommandBase"/> instead — that intermediate adds the
|
|
/// <c>--poll-only</c> flag and the <c>BuildOptions</c> helper.
|
|
/// </summary>
|
|
public abstract class TwinCATCommandBase : DriverCommandBase
|
|
{
|
|
[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!;
|
|
|
|
[CommandOption("ams-port", 'p', Description =
|
|
"AMS port. TwinCAT 3 PLC runtime defaults to 851; TwinCAT 2 uses 801.")]
|
|
public int AmsPort { get; init; } = 851;
|
|
|
|
[CommandOption("timeout-ms", Description = "Per-operation timeout in ms (default 5000).")]
|
|
public int TimeoutMs { get; init; } = 5000;
|
|
|
|
/// <summary>
|
|
/// The per-operation timeout, projected from <see cref="TimeoutMs"/>. The CliFx
|
|
/// <c>init</c> accessor required by the abstract base property is intentionally a
|
|
/// no-op: <see cref="TimeoutMs"/> is the only source of truth, so any value an
|
|
/// `init` initialiser supplies to <see cref="Timeout"/> directly is silently
|
|
/// dropped. Do NOT add a backing field "fixing" the empty body — it would diverge
|
|
/// from <see cref="TimeoutMs"/> and the two would drift on every refactor
|
|
/// (Driver.TwinCAT.Cli-007).
|
|
/// </summary>
|
|
public override TimeSpan Timeout
|
|
{
|
|
get => TimeSpan.FromMilliseconds(TimeoutMs);
|
|
init { /* see XML summary — driven by TimeoutMs */ }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Canonical TwinCAT gateway string the driver's <c>TwinCATAmsAddress.TryParse</c>
|
|
/// consumes — shape <c>ads://{AmsNetId}:{AmsPort}</c>.
|
|
/// </summary>
|
|
protected string Gateway => $"ads://{AmsNetId}:{AmsPort}";
|
|
|
|
protected string DriverInstanceId => $"twincat-cli-{AmsNetId}:{AmsPort}";
|
|
|
|
/// <summary>
|
|
/// Validates the numeric options every TwinCAT CLI command shares (timeout + AMS port).
|
|
/// Subclasses override and call <c>base.Validate()</c> 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).
|
|
/// </summary>
|
|
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).
|
|
internal string GatewayForTest => Gateway;
|
|
internal string DriverInstanceIdForTest => DriverInstanceId;
|
|
internal void ValidateForTest() => Validate();
|
|
}
|