using CliFx; using CliFx.Attributes; using CliFx.Infrastructure; using Opc.Ua; using Opc.Ua.Client; namespace OpcUaCli.Commands; [Command("subscribe", Description = "Monitor a node for value changes")] public class SubscribeCommand : ICommand { /// /// Gets the OPC UA endpoint URL to connect to before subscribing. /// [CommandOption("url", 'u', Description = "OPC UA server endpoint URL", IsRequired = true)] public string Url { get; init; } = default!; [CommandOption("username", 'U', Description = "Username for authentication")] public string? Username { get; init; } [CommandOption("password", 'P', Description = "Password for authentication")] public string? Password { get; init; } /// /// Gets the node identifier to monitor for value changes. /// [CommandOption("node", 'n', Description = "Node ID to monitor", IsRequired = true)] public string NodeId { get; init; } = default!; /// /// Gets the sampling and publishing interval, in milliseconds, for the monitored item. /// [CommandOption("interval", 'i', Description = "Polling interval in milliseconds")] public int Interval { get; init; } = 1000; /// /// Connects to the OPC UA endpoint and streams monitored-item notifications until cancellation. /// /// The console used to display subscription updates. public async ValueTask ExecuteAsync(IConsole console) { using var session = await OpcUaHelper.ConnectAsync(Url, Username, Password); var subscription = new Subscription(session.DefaultSubscription) { PublishingInterval = Interval, DisplayName = "CLI Subscription" }; var item = new MonitoredItem(subscription.DefaultItem) { StartNodeId = new NodeId(NodeId), DisplayName = NodeId, SamplingInterval = Interval }; item.Notification += (monitoredItem, e) => { if (e.NotificationValue is MonitoredItemNotification notification) { Console.WriteLine( $"[{notification.Value.SourceTimestamp:O}] {NodeId} = {notification.Value.Value} ({notification.Value.StatusCode})"); } }; subscription.AddItem(item); session.AddSubscription(subscription); await subscription.CreateAsync(); await console.Output.WriteLineAsync( $"Subscribed to {NodeId} (interval: {Interval}ms). Press Ctrl+C to stop."); var ct = console.RegisterCancellationHandler(); int tick = 0; while (!ct.IsCancellationRequested) { await Task.Delay(2000, ct).ContinueWith(_ => { }); tick++; Console.WriteLine( $" [tick {tick}] Session={session.Connected}, Sub.Id={subscription.Id}, " + $"PublishingEnabled={subscription.PublishingEnabled}, " + $"MonitoredItemCount={subscription.MonitoredItemCount}, " + $"ItemStatus={item.Status?.Id}, " + $"LastNotification={((item.LastValue as MonitoredItemNotification)?.Value?.Value)} ({((item.LastValue as MonitoredItemNotification)?.Value?.StatusCode)})"); } await console.Output.WriteLineAsync("Unsubscribed."); } }