2025-09-04 17:29:24 +08:00
|
|
|
|
using System.Collections;
|
|
|
|
|
|
using System.Collections.ObjectModel;
|
2025-09-02 13:09:00 +08:00
|
|
|
|
using AutoMapper;
|
2025-08-25 20:16:57 +08:00
|
|
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
|
|
|
|
using CommunityToolkit.Mvvm.Input;
|
2025-09-02 13:09:00 +08:00
|
|
|
|
using DMS.Core.Enums;
|
2025-08-25 21:26:18 +08:00
|
|
|
|
using DMS.Infrastructure.Interfaces.Services;
|
2025-09-01 21:03:34 +08:00
|
|
|
|
using DMS.Infrastructure.Models;
|
2025-10-04 20:46:39 +08:00
|
|
|
|
using DMS.WPF.Factories;
|
2025-09-04 19:59:35 +08:00
|
|
|
|
using DMS.WPF.Interfaces;
|
2025-09-04 17:29:24 +08:00
|
|
|
|
using DMS.WPF.Services;
|
2025-08-25 20:16:57 +08:00
|
|
|
|
using DMS.WPF.ViewModels.Items;
|
2025-08-25 21:26:18 +08:00
|
|
|
|
using Opc.Ua;
|
2025-08-25 20:16:57 +08:00
|
|
|
|
using Opc.Ua.Client;
|
|
|
|
|
|
|
|
|
|
|
|
namespace DMS.WPF.ViewModels.Dialogs;
|
|
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// OPC UA导入对话框的视图模型
|
|
|
|
|
|
/// 负责处理OPC UA服务器连接、节点浏览和变量导入等功能
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public partial class ImportOpcUaDialogViewModel : DialogViewModelBase<List<VariableItemViewModel>>, IDisposable
|
2025-08-25 20:16:57 +08:00
|
|
|
|
{
|
2025-09-02 16:13:06 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// OPC UA服务器端点URL
|
|
|
|
|
|
/// </summary>
|
2025-08-25 20:16:57 +08:00
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private string _endpointUrl = "opc.tcp://127.0.0.1:4855"; // 默认值
|
|
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// OPC UA根节点
|
|
|
|
|
|
/// </summary>
|
2025-09-01 21:03:34 +08:00
|
|
|
|
[ObservableProperty]
|
2025-09-02 13:09:00 +08:00
|
|
|
|
private OpcUaNodeItemViewModel _rootOpcUaNode;
|
2025-08-25 20:16:57 +08:00
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 当前选中节点下的所有变量集合
|
|
|
|
|
|
/// </summary>
|
2025-08-25 20:16:57 +08:00
|
|
|
|
[ObservableProperty]
|
2025-09-02 16:13:06 +08:00
|
|
|
|
private ObservableCollection<VariableItemViewModel> _opcUaNodeVariables = new();
|
2025-08-25 20:16:57 +08:00
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 用户选择的变量列表
|
|
|
|
|
|
/// </summary>
|
2025-09-02 15:02:39 +08:00
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private IList _selectedVariables = new ArrayList();
|
2025-09-02 16:13:06 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 是否全选变量
|
|
|
|
|
|
/// </summary>
|
2025-08-25 20:16:57 +08:00
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private bool _selectAllVariables;
|
|
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 是否已连接到OPC UA服务器
|
|
|
|
|
|
/// </summary>
|
2025-08-25 20:16:57 +08:00
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private bool _isConnected;
|
|
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 连接按钮显示文本
|
|
|
|
|
|
/// </summary>
|
2025-08-25 21:26:18 +08:00
|
|
|
|
[ObservableProperty]
|
2025-09-01 21:03:34 +08:00
|
|
|
|
private string _connectButtonText = "连接服务器";
|
2025-08-25 21:26:18 +08:00
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 连接按钮是否可用
|
|
|
|
|
|
/// </summary>
|
2025-08-25 21:26:18 +08:00
|
|
|
|
[ObservableProperty]
|
|
|
|
|
|
private bool _isConnectButtonEnabled = true;
|
|
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 当前选中的OPC UA节点
|
|
|
|
|
|
/// </summary>
|
2025-09-02 15:44:26 +08:00
|
|
|
|
[ObservableProperty]
|
2025-09-02 16:13:06 +08:00
|
|
|
|
[NotifyCanExecuteChangedFor(nameof(FindCurrentNodeVariablesCommand))] // 当选中节点改变时通知查找变量命令更新可执行状态
|
|
|
|
|
|
private OpcUaNodeItemViewModel _selectedNode;
|
2025-08-25 20:16:57 +08:00
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// OPC UA服务接口实例
|
|
|
|
|
|
/// </summary>
|
2025-08-25 21:26:18 +08:00
|
|
|
|
private readonly IOpcUaService _opcUaService;
|
2025-10-04 20:46:39 +08:00
|
|
|
|
private readonly IVariableItemViewModelFactory _variableItemViewModelFactory;
|
|
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 对象映射器实例
|
|
|
|
|
|
/// </summary>
|
2025-09-02 13:09:00 +08:00
|
|
|
|
private readonly IMapper _mapper;
|
2025-10-04 20:46:39 +08:00
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 取消令牌源,用于取消长时间运行的操作
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private readonly CancellationTokenSource _cancellationTokenSource;
|
2025-10-04 20:46:39 +08:00
|
|
|
|
|
2025-09-04 17:29:24 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 通知服务实例
|
|
|
|
|
|
/// </summary>
|
2025-09-04 19:59:35 +08:00
|
|
|
|
private readonly INotificationService _notificationService;
|
2025-08-25 21:26:18 +08:00
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 构造函数
|
|
|
|
|
|
/// 初始化ImportOpcUaDialogViewModel实例
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="opcUaService">OPC UA服务接口实例</param>
|
|
|
|
|
|
/// <param name="mapper">对象映射器实例</param>
|
2025-09-04 17:29:24 +08:00
|
|
|
|
/// <param name="notificationService">通知服务实例</param>
|
2025-10-04 20:46:39 +08:00
|
|
|
|
public ImportOpcUaDialogViewModel(IOpcUaService opcUaService, IVariableItemViewModelFactory variableItemViewModelFactory,
|
|
|
|
|
|
IMapper mapper, INotificationService notificationService)
|
2025-08-25 20:16:57 +08:00
|
|
|
|
{
|
2025-09-02 16:13:06 +08:00
|
|
|
|
_opcUaService = opcUaService;
|
2025-10-04 20:46:39 +08:00
|
|
|
|
_variableItemViewModelFactory = variableItemViewModelFactory;
|
2025-09-02 16:13:06 +08:00
|
|
|
|
_mapper = mapper;
|
2025-09-04 17:29:24 +08:00
|
|
|
|
_notificationService = notificationService;
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 初始化根节点
|
2025-09-02 13:09:00 +08:00
|
|
|
|
RootOpcUaNode = new OpcUaNodeItemViewModel() { DisplayName = "根节点", NodeId = Objects.ObjectsFolder, IsExpanded = true };
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 初始化取消令牌源
|
2025-09-01 21:03:34 +08:00
|
|
|
|
_cancellationTokenSource = new CancellationTokenSource();
|
2025-08-25 20:16:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 查找当前节点变量命令的可执行条件
|
|
|
|
|
|
/// 只有在已连接且有选中节点时才可执行
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public bool CanFindCurrentNodeVariables => IsConnected && SelectedNode != null;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 连接到OPC UA服务器命令
|
|
|
|
|
|
/// 负责建立与OPC UA服务器的连接并加载根节点信息
|
|
|
|
|
|
/// </summary>
|
2025-08-25 20:16:57 +08:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private async Task Connect()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 更新UI状态:禁用连接按钮并显示连接中状态
|
|
|
|
|
|
IsConnectButtonEnabled = false;
|
|
|
|
|
|
ConnectButtonText = "连接中...";
|
|
|
|
|
|
|
|
|
|
|
|
// 异步连接到OPC UA服务器
|
|
|
|
|
|
await _opcUaService.ConnectAsync(EndpointUrl);
|
2025-08-25 20:16:57 +08:00
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 检查连接是否成功建立
|
2025-09-01 21:03:34 +08:00
|
|
|
|
if (_opcUaService.IsConnected)
|
2025-08-25 21:26:18 +08:00
|
|
|
|
{
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 更新连接状态
|
2025-09-02 15:44:26 +08:00
|
|
|
|
IsConnected = true;
|
2025-08-25 21:26:18 +08:00
|
|
|
|
ConnectButtonText = "已连接";
|
2025-09-01 21:03:34 +08:00
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 浏览根节点并加载其子节点
|
|
|
|
|
|
var children = await _opcUaService.BrowseNode(_mapper.Map<OpcUaNode>(RootOpcUaNode));
|
|
|
|
|
|
RootOpcUaNode.Children = _mapper.Map<ObservableCollection<OpcUaNodeItemViewModel>>(children);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 处理特定异常类型提供更友好的用户提示
|
|
|
|
|
|
catch (UnauthorizedAccessException ex)
|
|
|
|
|
|
{
|
2025-09-04 17:29:24 +08:00
|
|
|
|
_notificationService.ShowError($"连接被拒绝,请检查用户名和密码: {ex.Message}");
|
2025-09-02 16:13:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (TimeoutException ex)
|
|
|
|
|
|
{
|
2025-09-04 17:29:24 +08:00
|
|
|
|
_notificationService.ShowError($"连接超时,请检查服务器地址和网络连接: {ex.Message}");
|
2025-08-25 20:16:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2025-09-04 17:29:24 +08:00
|
|
|
|
_notificationService.ShowError($"连接 OPC UA 服务器失败: {EndpointUrl} - {ex.Message}", ex);
|
2025-08-25 20:16:57 +08:00
|
|
|
|
}
|
2025-09-02 16:13:06 +08:00
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
// 确保按钮状态正确更新
|
|
|
|
|
|
// 如果连接失败,恢复按钮为可用状态
|
|
|
|
|
|
if (!IsConnected)
|
|
|
|
|
|
{
|
|
|
|
|
|
IsConnectButtonEnabled = true;
|
|
|
|
|
|
ConnectButtonText = "连接服务器";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-25 20:16:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 次要按钮命令(取消导入)
|
|
|
|
|
|
/// 负责断开与OPC UA服务器的连接并关闭对话框,返回用户选择的变量
|
|
|
|
|
|
/// </summary>
|
2025-09-02 15:02:39 +08:00
|
|
|
|
[RelayCommand]
|
2025-09-02 16:13:06 +08:00
|
|
|
|
private async Task SecondaryButton()
|
2025-09-02 15:02:39 +08:00
|
|
|
|
{
|
2025-09-02 16:13:06 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 断开与OPC UA服务器的连接
|
|
|
|
|
|
await _opcUaService.DisconnectAsync();
|
|
|
|
|
|
// 关闭对话框并返回用户选择的变量列表
|
|
|
|
|
|
Close(SelectedVariables.Cast<VariableItemViewModel>().ToList());
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2025-09-04 17:29:24 +08:00
|
|
|
|
_notificationService.ShowError($"断开连接时发生错误: {ex.Message}", ex);
|
2025-09-02 16:13:06 +08:00
|
|
|
|
}
|
2025-09-02 15:02:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 主要按钮命令(确认导入)
|
|
|
|
|
|
/// 负责断开与OPC UA服务器的连接并关闭对话框,返回所有加载的变量
|
|
|
|
|
|
/// </summary>
|
2025-09-02 13:09:00 +08:00
|
|
|
|
[RelayCommand]
|
2025-09-02 16:13:06 +08:00
|
|
|
|
private async Task PrimaryButton()
|
2025-09-02 13:09:00 +08:00
|
|
|
|
{
|
2025-09-02 16:13:06 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 断开与OPC UA服务器的连接
|
|
|
|
|
|
await _opcUaService.DisconnectAsync();
|
|
|
|
|
|
// 关闭对话框并返回所有加载的变量
|
|
|
|
|
|
Close(OpcUaNodeVariables.ToList());
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2025-09-04 17:29:24 +08:00
|
|
|
|
_notificationService.ShowError($"断开连接时发生错误: {ex.Message}", ex);
|
2025-09-02 16:13:06 +08:00
|
|
|
|
}
|
2025-09-02 13:09:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 关闭按钮命令
|
|
|
|
|
|
/// 负责断开与OPC UA服务器的连接
|
|
|
|
|
|
/// </summary>
|
2025-09-02 13:09:00 +08:00
|
|
|
|
[RelayCommand]
|
2025-09-02 16:13:06 +08:00
|
|
|
|
private async Task CloseButton()
|
2025-09-02 13:09:00 +08:00
|
|
|
|
{
|
2025-09-02 16:13:06 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 断开与OPC UA服务器的连接
|
|
|
|
|
|
await _opcUaService.DisconnectAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2025-09-04 17:29:24 +08:00
|
|
|
|
_notificationService.ShowError($"断开连接时发生错误: {ex.Message}", ex);
|
2025-09-02 16:13:06 +08:00
|
|
|
|
}
|
2025-09-02 13:09:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-25 20:16:57 +08:00
|
|
|
|
/// <summary>
|
2025-09-02 16:13:06 +08:00
|
|
|
|
/// 查找当前节点变量命令
|
|
|
|
|
|
/// 根据当前选中的节点查找其下的所有变量
|
2025-08-25 20:16:57 +08:00
|
|
|
|
/// </summary>
|
2025-09-02 16:13:06 +08:00
|
|
|
|
[RelayCommand(CanExecute = nameof(CanFindCurrentNodeVariables))]
|
|
|
|
|
|
private async Task FindCurrentNodeVariables()
|
2025-08-25 20:16:57 +08:00
|
|
|
|
{
|
2025-09-02 16:13:06 +08:00
|
|
|
|
try
|
2025-08-25 20:16:57 +08:00
|
|
|
|
{
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 检查是否有选中的节点
|
|
|
|
|
|
if (SelectedNode == null)
|
|
|
|
|
|
{
|
2025-09-04 17:29:24 +08:00
|
|
|
|
_notificationService.ShowError("请先选择左边的节点,然后再查找变量。");
|
2025-09-02 16:13:06 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-08-25 20:16:57 +08:00
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 清空当前变量列表
|
|
|
|
|
|
OpcUaNodeVariables.Clear();
|
2025-08-25 20:16:57 +08:00
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 设置选中节点的状态
|
|
|
|
|
|
SelectedNode.IsExpanded = true;
|
|
|
|
|
|
SelectedNode.IsSelected = true;
|
2025-08-25 20:16:57 +08:00
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 异步浏览节点变量(递归模式)
|
|
|
|
|
|
await BrowseNodeVariablesAsync(SelectedNode, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 处理用户取消操作的情况
|
2025-09-04 17:29:24 +08:00
|
|
|
|
_notificationService.ShowInfo("操作已被取消");
|
2025-09-02 16:13:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 处理其他异常情况
|
2025-09-04 17:29:24 +08:00
|
|
|
|
_notificationService.ShowError($"加载 OPC UA 节点变量失败: {SelectedNode?.NodeId} - {ex.Message}", ex);
|
2025-09-02 16:13:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-02 15:44:26 +08:00
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 加载节点变量方法
|
|
|
|
|
|
/// 根据指定节点加载其下的所有变量
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="node">要加载变量的OPC UA节点</param>
|
2025-09-02 13:09:00 +08:00
|
|
|
|
public async Task LoadNodeVariables(OpcUaNodeItemViewModel node)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 清空当前变量列表
|
2025-09-02 15:02:39 +08:00
|
|
|
|
OpcUaNodeVariables.Clear();
|
2025-08-25 20:16:57 +08:00
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 设置节点状态
|
2025-09-02 13:09:00 +08:00
|
|
|
|
node.IsExpanded = true;
|
|
|
|
|
|
node.IsSelected = true;
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 更新选中节点
|
|
|
|
|
|
SelectedNode = node;
|
|
|
|
|
|
|
|
|
|
|
|
// 异步浏览节点变量(非递归模式)
|
|
|
|
|
|
await BrowseNodeVariablesAsync(node);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 处理用户取消操作的情况
|
2025-09-04 17:29:24 +08:00
|
|
|
|
_notificationService.ShowInfo("操作已被取消");
|
2025-09-02 15:44:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 处理其他异常情况
|
2025-09-04 17:29:24 +08:00
|
|
|
|
_notificationService.ShowError($"加载 OPC UA 节点变量失败: {node?.NodeId} - {ex.Message}", ex);
|
2025-09-02 15:44:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 浏览节点变量异步方法
|
|
|
|
|
|
/// 递归或非递归地浏览指定节点下的所有变量
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="node">要浏览的节点</param>
|
|
|
|
|
|
/// <param name="isRecursive">是否递归浏览子节点</param>
|
|
|
|
|
|
private async Task BrowseNodeVariablesAsync(OpcUaNodeItemViewModel node, bool isRecursive = false)
|
2025-09-02 15:44:26 +08:00
|
|
|
|
{
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 参数有效性检查
|
|
|
|
|
|
if (node == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
try
|
2025-09-02 15:44:26 +08:00
|
|
|
|
{
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 检查是否有取消请求
|
|
|
|
|
|
_cancellationTokenSource.Token.ThrowIfCancellationRequested();
|
|
|
|
|
|
|
|
|
|
|
|
// 异步浏览节点获取子节点列表
|
|
|
|
|
|
var children = await _opcUaService.BrowseNode(_mapper.Map<OpcUaNode>(node));
|
2025-10-04 20:46:39 +08:00
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 再次检查是否有取消请求
|
|
|
|
|
|
_cancellationTokenSource.Token.ThrowIfCancellationRequested();
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历所有子节点
|
|
|
|
|
|
foreach (var child in children)
|
2025-09-02 13:09:00 +08:00
|
|
|
|
{
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 映射子节点为视图模型对象
|
|
|
|
|
|
var nodeItem = _mapper.Map<OpcUaNodeItemViewModel>(child);
|
|
|
|
|
|
|
|
|
|
|
|
// 判断节点类型是否为变量
|
|
|
|
|
|
if (child.NodeClass == NodeClass.Variable)
|
2025-09-02 13:09:00 +08:00
|
|
|
|
{
|
2025-10-04 20:46:39 +08:00
|
|
|
|
var variableItem = _variableItemViewModelFactory.CreateNewVariableItemViewModel();
|
|
|
|
|
|
variableItem.Name = child.DisplayName;
|
|
|
|
|
|
variableItem.OpcUaNodeId = child.NodeId.ToString();
|
|
|
|
|
|
variableItem.Protocol = ProtocolType.OpcUa;
|
|
|
|
|
|
variableItem.DataType = child.DataType;
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 创建并添加变量项到变量列表
|
2025-10-04 20:46:39 +08:00
|
|
|
|
OpcUaNodeVariables.Add(variableItem);
|
2025-09-02 16:13:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 如果是递归模式且节点不是变量,则递归浏览子节点
|
|
|
|
|
|
else if (isRecursive)
|
2025-09-02 13:09:00 +08:00
|
|
|
|
{
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 递归浏览子节点
|
|
|
|
|
|
await BrowseNodeVariablesAsync(nodeItem, true);
|
2025-09-02 13:09:00 +08:00
|
|
|
|
}
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 非递归模式下,将非变量节点添加到节点树中
|
|
|
|
|
|
else
|
2025-09-02 15:44:26 +08:00
|
|
|
|
{
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 避免重复添加相同节点
|
|
|
|
|
|
if (!node.Children.Any(n => n.NodeId == nodeItem.NodeId))
|
|
|
|
|
|
{
|
|
|
|
|
|
node.Children.Add(nodeItem);
|
|
|
|
|
|
}
|
2025-09-02 15:44:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-02 16:13:06 +08:00
|
|
|
|
catch (OperationCanceledException)
|
2025-09-02 15:44:26 +08:00
|
|
|
|
{
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 处理取消操作
|
2025-09-04 17:29:24 +08:00
|
|
|
|
_notificationService.ShowInfo("节点浏览操作已被取消");
|
2025-09-02 16:13:06 +08:00
|
|
|
|
throw; // 重新抛出异常以保持调用链
|
2025-09-02 13:09:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2025-09-02 16:13:06 +08:00
|
|
|
|
// 记录浏览节点失败的日志
|
2025-09-04 17:29:24 +08:00
|
|
|
|
_notificationService.ShowError($"浏览节点失败: {node.NodeId} - {ex.Message}", ex);
|
2025-09-02 16:13:06 +08:00
|
|
|
|
throw; // 重新抛出异常以保持调用链
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 处理来自服务器的数据变化通知
|
|
|
|
|
|
/// 当监视的OPC UA节点数据发生变化时会被调用
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="item">发生变化的监视项</param>
|
|
|
|
|
|
/// <param name="e">监视项通知事件参数</param>
|
|
|
|
|
|
private static void OnNotification(MonitoredItem item, MonitoredItemNotificationEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 遍历所有变化的值
|
|
|
|
|
|
foreach (var value in item.DequeueValues())
|
|
|
|
|
|
{
|
|
|
|
|
|
// 输出通知信息到控制台
|
|
|
|
|
|
Console.WriteLine(
|
|
|
|
|
|
$@"[通知] {item.DisplayName}: {value.Value} | 时间戳: {value.SourceTimestamp.ToLocalTime()} | 状态: {value.StatusCode}");
|
2025-09-02 13:09:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-02 16:13:06 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 释放资源方法
|
|
|
|
|
|
/// 实现IDisposable接口,负责释放使用的资源
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 发出取消请求
|
|
|
|
|
|
_cancellationTokenSource?.Cancel();
|
|
|
|
|
|
// 释放取消令牌源资源
|
|
|
|
|
|
_cancellationTokenSource?.Dispose();
|
|
|
|
|
|
}
|
2025-08-25 20:16:57 +08:00
|
|
|
|
}
|