Files
lmxopcua/src/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Cli/Commands/ReadCommand.cs
Joseph Doherty f2ee027145 fix(driver-twincat-cli): resolve Low code-review findings (Driver.TwinCAT.Cli-001,002,003,004,005,006,007)
- 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>
2026-05-23 08:34:57 -04:00

56 lines
2.1 KiB
C#

using CliFx.Attributes;
using CliFx.Infrastructure;
using ZB.MOM.WW.OtOpcUa.Driver.Cli.Common;
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Cli.Commands;
/// <summary>
/// Read one TwinCAT symbol by path. Structure writes/reads are out of scope — fan the
/// member list into individual reads if you need them.
/// </summary>
[Command("read", Description = "Read a single TwinCAT symbol.")]
public sealed class ReadCommand : TwinCATTagCommandBase
{
[CommandOption("symbol", 's', Description =
"Symbol path. Program scope: 'MAIN.bStart'. Global: 'GVL.Counter'. " +
"Nested UDT member: 'Motor1.Status.Running'. Array element: 'Recipe[3]'.",
IsRequired = true)]
public string SymbolPath { get; init; } = default!;
[CommandOption("type", 't', Description =
"Bool / SInt / USInt / Int / UInt / DInt / UDInt / LInt / ULInt / Real / LReal / " +
"String / WString / Time / Date / DateTime / TimeOfDay (default DInt).")]
public TwinCATDataType DataType { get; init; } = TwinCATDataType.DInt;
public override async ValueTask ExecuteAsync(IConsole console)
{
Validate();
ConfigureLogging();
var ct = console.RegisterCancellationHandler();
var tagName = SynthesiseTagName(SymbolPath, DataType);
var tag = new TwinCATTagDefinition(
Name: tagName,
DeviceHostAddress: Gateway,
SymbolPath: SymbolPath,
DataType: DataType,
Writable: false);
var options = BuildOptions([tag]);
await using var driver = new TwinCATDriver(options, DriverInstanceId);
try
{
await driver.InitializeAsync("{}", ct);
var snapshot = await driver.ReadAsync([tagName], ct);
await console.Output.WriteLineAsync(SnapshotFormatter.Format(SymbolPath, snapshot[0]));
}
finally
{
await driver.ShutdownAsync(CancellationToken.None);
}
}
internal static string SynthesiseTagName(string symbolPath, TwinCATDataType type)
=> $"{symbolPath}:{type}";
}