ffa5b2a4fa
Review 07 U7 (AbLegacy part): probe/read/write had no OperationCanceledException arm, so cancelling a slow read printed an unhandled-exception stack trace. The driver work now runs through AbLegacyCommandBase.RunCancellableAsync, which reports an operator cancellation as "Cancelled." and still propagates anything else. (The AbCip subscribe lock and SnapshotFormatter array rendering are other lanes' files.) Refs #738 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
94 lines
4.5 KiB
C#
94 lines
4.5 KiB
C#
using CliFx.Attributes;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.PlcFamilies;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Cli.Common;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Cli;
|
|
|
|
/// <summary>
|
|
/// Base for every AB Legacy CLI command. Carries the PCCC-specific endpoint options
|
|
/// (<c>--gateway</c> + <c>--plc-type</c>) on top of <see cref="DriverCommandBase"/>'s
|
|
/// shared verbose + timeout + logging helpers.
|
|
/// </summary>
|
|
public abstract class AbLegacyCommandBase : DriverCommandBase
|
|
{
|
|
/// <summary>Gets the gateway endpoint for AB Legacy connection.</summary>
|
|
[CommandOption("gateway", 'g', Description =
|
|
"Canonical AB Legacy gateway: ab://host[:port]/cip-path. Port defaults to 44818. " +
|
|
"cip-path depends on the family: SLC 5/05 + PLC-5 typically '1,0'; MicroLogix " +
|
|
"1100/1400 takes an empty path (direct EIP, no backplane).",
|
|
IsRequired = true)]
|
|
public string Gateway { get; init; } = default!;
|
|
|
|
/// <summary>Gets the PLC family type.</summary>
|
|
[CommandOption("plc-type", 'P', Description =
|
|
"Slc500 / MicroLogix / Plc5 / LogixPccc (default Slc500).")]
|
|
public AbLegacyPlcFamily PlcType { get; init; } = AbLegacyPlcFamily.Slc500;
|
|
|
|
/// <summary>Gets the transport stack used to reach the PLC.</summary>
|
|
[CommandOption("transport", Description =
|
|
"Transport stack: default, native or libplctag. default = libplctag for every --plc-type; native is " +
|
|
"opt-in (wire-proven only for N-file reads/writes and an F8 read; PLC-5 native is N-file reads only, " +
|
|
"LogixPccc unverified).")]
|
|
public AbLegacyCliTransport Transport { get; init; } = AbLegacyCliTransport.Default;
|
|
|
|
/// <summary>
|
|
/// Runs a one-shot command's driver work so a Ctrl-C (cancellation of <paramref name="ct"/>) prints
|
|
/// <c>Cancelled.</c> instead of an unhandled <see cref="OperationCanceledException"/> stack trace (review 07 U7).
|
|
/// Any other exception — and an OCE the operator did not ask for — still propagates.
|
|
/// </summary>
|
|
/// <param name="console">The CLI console.</param>
|
|
/// <param name="ct">The console's Ctrl-C token.</param>
|
|
/// <param name="body">The command body.</param>
|
|
/// <returns>A task that completes when the body has finished or was cancelled.</returns>
|
|
internal static async ValueTask RunCancellableAsync(CliFx.Infrastructure.IConsole console, CancellationToken ct, Func<Task> body)
|
|
{
|
|
try
|
|
{
|
|
await body();
|
|
}
|
|
catch (OperationCanceledException) when (ct.IsCancellationRequested)
|
|
{
|
|
await console.Error.WriteLineAsync("Cancelled.");
|
|
}
|
|
}
|
|
|
|
/// <summary>Gets the per-operation timeout in milliseconds.</summary>
|
|
[CommandOption("timeout-ms", Description = "Per-operation timeout in ms (default 5000).")]
|
|
public int TimeoutMs { get; init; } = 5000;
|
|
|
|
/// <inheritdoc />
|
|
public override TimeSpan Timeout
|
|
{
|
|
get => TimeSpan.FromMilliseconds(TimeoutMs);
|
|
init { /* driven by TimeoutMs */ }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Build an <see cref="AbLegacyDriverOptions"/> with the device + tag list a subclass
|
|
/// supplies. The CLI authors typed <see cref="AbLegacyTagDefinition"/>s from operator flags;
|
|
/// v3 delivers tags to the driver as <see cref="RawTagEntry"/> records, so each typed def is
|
|
/// serialised back to its <c>TagConfig</c> blob via
|
|
/// <see cref="AbLegacyTagDefinitionFactory.ToTagConfig"/> (RawPath = the def's <c>Name</c>,
|
|
/// DeviceName = the def's <c>DeviceHostAddress</c> — the CLI's single device). Probe disabled
|
|
/// for CLI one-shot runs.
|
|
/// </summary>
|
|
/// <param name="tags">The tag definitions to include in the options.</param>
|
|
/// <returns>Configured AB Legacy driver options.</returns>
|
|
protected AbLegacyDriverOptions BuildOptions(IReadOnlyList<AbLegacyTagDefinition> tags) => new()
|
|
{
|
|
Devices = [new AbLegacyDeviceOptions(
|
|
HostAddress: Gateway,
|
|
PlcFamily: PlcType,
|
|
DeviceName: $"cli-{PlcType}")],
|
|
RawTags = [.. tags.Select(t => new RawTagEntry(
|
|
t.Name, AbLegacyTagDefinitionFactory.ToTagConfig(t), t.WriteIdempotent, DeviceName: t.DeviceHostAddress))],
|
|
Timeout = Timeout,
|
|
Transport = Transport.ToConfigured(),
|
|
Probe = new AbLegacyProbeOptions { Enabled = false },
|
|
};
|
|
|
|
/// <summary>Gets the driver instance identifier for CLI operations.</summary>
|
|
protected string DriverInstanceId => $"ablegacy-cli-{Gateway}";
|
|
}
|