using CliFx.Attributes; using CliFx.Infrastructure; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Cli.Commands; /// /// PR 5.1 / #316 — stream TC3 EventLogger alarms to the terminal until Ctrl+C. /// Mirrors the OPC UA Client CLI alarms verb shape: subscribe + print every /// incoming with timestamp, source, severity, and /// message text. Requires EnableAlarms=true on the driver — set via the /// base options builder when the verb is selected. /// [Command("alarms", Description = "Subscribe to TC3 EventLogger alarms via the driver's IAlarmSource bridge and " + "stream events to stdout until Ctrl+C.")] public sealed class AlarmsCommand : TwinCATCommandBase { [CommandOption("source", Description = "Optional alarm source filter (matched case-insensitively against the event's " + "Source field). Repeat the flag to match multiple sources; omit to subscribe to " + "every event the EventLogger surfaces.")] public IReadOnlyList Sources { get; init; } = Array.Empty(); public override async ValueTask ExecuteAsync(IConsole console) { ConfigureLogging(); var ct = console.RegisterCancellationHandler(); // Empty Tags + EnableAlarms=true builds a driver that opens only the alarm path. // TwinCATDriverOptions is a regular class with init-only properties — rebuild // the instance instead of using a record-style `with` clone. var baseOptions = BuildOptions([]); var options = new TwinCATDriverOptions { Devices = baseOptions.Devices, Tags = baseOptions.Tags, Probe = baseOptions.Probe, Timeout = baseOptions.Timeout, UseNativeNotifications = baseOptions.UseNativeNotifications, EnableControllerBrowse = baseOptions.EnableControllerBrowse, MaxArrayExpansion = baseOptions.MaxArrayExpansion, EnableAlarms = true, }; await using var driver = new TwinCATDriver(options, DriverInstanceId); IAlarmSubscriptionHandle? handle = null; try { await driver.InitializeAsync("{}", ct); driver.OnAlarmEvent += (_, e) => { var line = $"[{e.SourceTimestampUtc:HH:mm:ss.fff}] " + $"{e.SourceNodeId} " + $"sev={e.Severity} " + $"type={e.AlarmType} " + $"cond={e.ConditionId} " + $"\"{e.Message}\""; console.Output.WriteLine(line); }; handle = await driver.SubscribeAlarmsAsync(Sources, ct); var filterDesc = Sources.Count == 0 ? "all sources" : $"sources [{string.Join(", ", Sources)}]"; await console.Output.WriteLineAsync( $"Subscribed to TC3 EventLogger alarms on {AmsNetId}:{AmsPort} ({filterDesc}). Ctrl+C to stop."); await console.Output.WriteLineAsync( "Note: Beckhoff doesn't ship a managed TcEventLogger wrapper; the driver " + "uses a best-effort decode of AMS port 110 notifications. Some fields may " + "surface as 'Unknown' until a binary-protocol decoder lands."); try { await Task.Delay(System.Threading.Timeout.InfiniteTimeSpan, ct); } catch (OperationCanceledException) { // Expected on Ctrl+C. } } finally { if (handle is not null) { try { await driver.UnsubscribeAlarmsAsync(handle, CancellationToken.None); } catch { /* teardown best-effort */ } } await driver.ShutdownAsync(CancellationToken.None); } } }