Wire Galaxy security_classification to OPC UA AccessLevel (ReadOnly for SecuredWrite/VerifiedWrite/ViewOnly). Use deployed package chain for attribute queries to exclude undeployed attributes. Group primitive attributes under their parent variable node (merged Variable+Object). Add is_historized and is_alarm detection via HistoryExtension/AlarmExtension primitives. Implement OPC UA HistoryRead backed by Wonderware Historian Runtime database. Implement AlarmConditionState nodes driven by InAlarm with condition refresh support. Add historyread and alarms CLI commands for testing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
85 lines
3.1 KiB
C#
85 lines
3.1 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// Gets the OPC UA endpoint URL to connect to before subscribing.
|
|
/// </summary>
|
|
[CommandOption("url", 'u', Description = "OPC UA server endpoint URL", IsRequired = true)]
|
|
public string Url { get; init; } = default!;
|
|
|
|
/// <summary>
|
|
/// Gets the node identifier to monitor for value changes.
|
|
/// </summary>
|
|
[CommandOption("node", 'n', Description = "Node ID to monitor", IsRequired = true)]
|
|
public string NodeId { get; init; } = default!;
|
|
|
|
/// <summary>
|
|
/// Gets the sampling and publishing interval, in milliseconds, for the monitored item.
|
|
/// </summary>
|
|
[CommandOption("interval", 'i', Description = "Polling interval in milliseconds")]
|
|
public int Interval { get; init; } = 1000;
|
|
|
|
/// <summary>
|
|
/// Connects to the OPC UA endpoint and streams monitored-item notifications until cancellation.
|
|
/// </summary>
|
|
/// <param name="console">The console used to display subscription updates.</param>
|
|
public async ValueTask ExecuteAsync(IConsole console)
|
|
{
|
|
using var session = await OpcUaHelper.ConnectAsync(Url);
|
|
|
|
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.");
|
|
}
|
|
}
|