完成连接OpcUa服务器

This commit is contained in:
2025-08-25 21:26:18 +08:00
parent 8290c96b1b
commit f80a1669fb
11 changed files with 86 additions and 155 deletions

View File

@@ -9,25 +9,15 @@ namespace DMS.Infrastructure.Interfaces.Services
{
public interface IOpcUaService
{
/// <summary>
/// 创建 OPC UA 会话
/// </summary>
/// <param name="opcUaServerUrl">OPC UA 服务器地址</param>
/// <param name="stoppingToken">取消令牌</param>
/// <returns></returns>
public Task CreateSession(string opcUaServerUrl, CancellationToken stoppingToken = default);
/// <summary>
/// 连接到 OPC UA 服务器(异步)
/// </summary>
/// <param name="stoppingToken">取消令牌</param>
/// <returns></returns>
public Task ConnectAsync(CancellationToken stoppingToken = default);
public Task ConnectAsync(string opcUaServerUrl,CancellationToken stoppingToken = default);
/// <summary>
/// 连接到 OPC UA 服务器(同步)
/// </summary>
public void Connect();
/// <summary>
/// 断开 OPC UA 服务器连接

View File

@@ -14,7 +14,7 @@ namespace DMS.Infrastructure.Services
public class OpcUaService : IOpcUaService
{
private Session? _session;
private string? _serverUrl;
private string _serverUrl;
/// <summary>
/// 创建 OPC UA 会话
@@ -22,17 +22,14 @@ namespace DMS.Infrastructure.Services
/// <param name="opcUaServerUrl">OPC UA 服务器地址</param>
/// <param name="stoppingToken">取消令牌</param>
/// <returns></returns>
public async Task CreateSession(string opcUaServerUrl, CancellationToken stoppingToken = default)
public async Task CreateSession( CancellationToken stoppingToken = default)
{
if (string.IsNullOrEmpty(opcUaServerUrl))
{
throw new ArgumentException("OPC UA server URL cannot be null or empty.", nameof(opcUaServerUrl));
}
try
{
_session = await OpcUaHelper.CreateOpcUaSessionAsync(opcUaServerUrl, stoppingToken);
_serverUrl = opcUaServerUrl;
_session = await OpcUaHelper.CreateOpcUaSessionAsync(_serverUrl, stoppingToken);
}
catch (Exception ex)
{
@@ -43,8 +40,13 @@ namespace DMS.Infrastructure.Services
/// <summary>
/// 连接到 OPC UA 服务器
/// </summary>
public async Task ConnectAsync(CancellationToken stoppingToken = default)
public async Task ConnectAsync(string opcUaServerUrl, CancellationToken stoppingToken = default)
{
_serverUrl = opcUaServerUrl;
if (string.IsNullOrEmpty(opcUaServerUrl))
{
throw new ArgumentException("OPC UA server URL cannot be null or empty.", nameof(opcUaServerUrl));
}
if (string.IsNullOrEmpty(_serverUrl))
{
throw new InvalidOperationException("Server URL is not set. Please call CreateSession first.");
@@ -57,43 +59,10 @@ namespace DMS.Infrastructure.Services
}
// 重新创建会话
await CreateSession(_serverUrl, stoppingToken);
await CreateSession( stoppingToken);
}
/// <summary>
/// 连接到 OPC UA 服务器(同步版本,用于向后兼容)
/// </summary>
public void Connect()
{
if (string.IsNullOrEmpty(_serverUrl))
{
throw new InvalidOperationException("Server URL is not set. Please call CreateSession first.");
}
// 如果已经连接,直接返回
if (_session?.Connected == true)
{
return;
}
// 检查会话是否存在但未连接
if (_session != null)
{
// 尝试重新激活会话
try
{
_session.Reconnect();
return;
}
catch
{
// 如果重新连接失败,继续到重新创建会话的步骤
}
}
// 如果没有会话或重新连接失败,抛出异常提示需要调用 CreateSession
throw new InvalidOperationException("Session is not created or connection lost. Please call CreateSession first.");
}
/// <summary>
/// 断开 OPC UA 服务器连接

View File

@@ -18,6 +18,7 @@ namespace DMS.WPF.Services
{ typeof(ConfirmDialogViewModel), typeof(ConfirmDialog) },
{ typeof(VariableTableDialogViewModel), typeof(VariableTableDialog) },
{ typeof(ImportExcelDialogViewModel), typeof(ImportExcelDialog) },
{ typeof(ImportOpcUaDialogViewModel), typeof(ImportOpcUaDialog) },
{ typeof(VariableDialogViewModel), typeof(VariableDialog) },
// { typeof(MqttDialogViewModel), typeof(MqttDialog) }, // Add other mappings here
// ... other dialogs

View File

@@ -1,68 +0,0 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using DMS.Helper;
using DMS.Message;
using DMS.WPF.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using DMS.Core.Enums;
namespace DMS.Services;
public partial class NavgatorServices : ObservableRecipient, IRecipient<NavgatorMessage>
{
// [ObservableProperty]
private ViewModelBase currentViewModel;
public NavgatorServices()
{
IsActive = true;
}
// partial void OnCurrentViewModelChanging(ViewModelBase viewModel)
// {
// viewModel?.OnLoading();
// }
//
// partial void OnCurrentViewModelChanged(ViewModelBase viewModel)
// {
// OnViewModelChanged?.Invoke();
// viewModel?.OnLoaded();
// }
public ViewModelBase CurrentViewModel
{
get => currentViewModel;
set { currentViewModel = value; }
}
public async void Receive(NavgatorMessage message)
{
try
{
ViewModelBase nextViewModel = message.Value;
//如果OnExit返回False终止跳转
if (currentViewModel != null)
{
var isExit = await currentViewModel.OnExitAsync();
if (!isExit)
{
return;
}
}
nextViewModel?.OnLoading();
CurrentViewModel = message.Value;
OnViewModelChanged?.Invoke();
currentViewModel?.OnLoaded();
}
catch (Exception e)
{
NotificationHelper.ShowError($"切换视图时发生了错误:{e.Message}", e);
}
}
public event Action OnViewModelChanged;
}

View File

@@ -1,14 +1,15 @@
// 文件: DMS.WPF/Services/NavigationService.cs
using DMS.Helper;
using DMS.ViewModels;
using DMS.WPF.ViewModels;
using DMS.WPF.ViewModels.Items;
using DMS.WPF.Views;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
using DMS.ViewModels;
using DMS.WPF.ViewModels.Items;
using DMS.WPF.Views;
namespace DMS.WPF.Services;
@@ -39,6 +40,13 @@ public class NavigationService : INavigationService
var mainViewModel = App.Current.Services.GetRequiredService<MainViewModel>();
var viewModel = GetViewModelByKey(menu.TargetViewKey);
if (viewModel == null)
{
NotificationHelper.ShowError($"切换界面失败,没有找到界面:{menu.TargetViewKey}");
return;
}
if (viewModel is INavigatable navigatableViewModel)
{
@@ -70,7 +78,7 @@ public class NavigationService : INavigationService
case "SettingView":
return App.Current.Services.GetRequiredService<SettingViewModel>();
default:
throw new KeyNotFoundException($"未找到与键 '{key}' 关联的视图模型类型。请检查 NavigationService 的映射配置。");
return null;
}
}
}

View File

@@ -106,7 +106,7 @@ public partial class DevicesViewModel : ViewModelBase, INavigatable
{
Header = device.Name,
Icon = SegoeFluentIcons.Devices2.Glyph,
TargetViewKey = "DevicesView"
TargetViewKey = "DeviceDetailView"
};
if (device.IsAddDefVarTable)
{

View File

@@ -2,7 +2,9 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DMS.Core.Models;
using DMS.Helper;
using DMS.Infrastructure.Interfaces.Services;
using DMS.WPF.ViewModels.Items;
using Opc.Ua;
using Opc.Ua.Client;
using System.Collections.ObjectModel;
@@ -27,14 +29,25 @@ public partial class ImportOpcUaDialogViewModel : DialogViewModelBase<List<Varia
[ObservableProperty]
private bool _isConnected;
[ObservableProperty]
private string _connectButtonText="连接服务器";
[ObservableProperty]
private bool _isConnectButtonEnabled = true;
private Session _session;
public ImportOpcUaDialogViewModel()
private readonly IOpcUaService _opcUaService;
private CancellationTokenSource _cancellationTokenSource;
public ImportOpcUaDialogViewModel(IOpcUaService opcUaService)
{
//OpcUaNodes = new ObservableCollection<OpcUaNode>();
SelectedNodeVariables = new ObservableCollection<Variable>();
// Automatically connect when the ViewModel is created
//ConnectC.Execute(null);
this._opcUaService = opcUaService;
_cancellationTokenSource=new CancellationTokenSource();
}
@@ -44,27 +57,29 @@ public partial class ImportOpcUaDialogViewModel : DialogViewModelBase<List<Varia
try
{
// 断开现有连接
if (_session != null && _session.Connected)
if (!_opcUaService.IsConnected())
{
await _session.CloseAsync();
_session.Dispose();
_session = null;
await _opcUaService.ConnectAsync(EndpointUrl, _cancellationTokenSource.Token);
}
IsConnected = false;
SelectedNodeVariables.Clear();
IsConnected= _opcUaService.IsConnected();
if (IsConnected)
{
ConnectButtonText = "已连接";
IsConnectButtonEnabled = false;
}
//_session = await ServiceHelper.CreateOpcUaSessionAsync(EndpointUrl);
NotificationHelper.ShowSuccess($"已连接到 OPC UA 服务器: {EndpointUrl}");
IsConnected = true;
// 浏览根节点
var rootNodeId = new NodeId(ObjectIds.ObjectsFolder);
var list=_opcUaService.BrowseNodes(rootNodeId);
//await BrowseNodes(OpcUaNodes, ObjectIds.ObjectsFolder);
}
catch (Exception ex)
{
IsConnected = false;
IsConnectButtonEnabled = false;
ConnectButtonText = "连接服务器";
NotificationHelper.ShowError($"连接 OPC UA 服务器失败: {EndpointUrl} - {ex.Message}", ex);
}
}

View File

@@ -17,7 +17,6 @@ public partial class MqttsViewModel : ViewModelBase
private readonly DataServices _dataServices;
private readonly IDialogService _dialogService;
private readonly ILogger<MqttsViewModel> _logger;
private readonly NavgatorServices _navgatorServices;
[ObservableProperty]
private ObservableCollection<MqttServerItemViewModel> _mqtts;

View File

@@ -286,7 +286,7 @@ partial class VariableTableViewModel : ViewModelBase, INavigatable
}
// 显示OPC UA导入对话框让用户选择要导入的变量
ImportOpcUaDialogViewModel importOpcUaDialogViewModel = new ImportOpcUaDialogViewModel();
ImportOpcUaDialogViewModel importOpcUaDialogViewModel = App.Current.Services.GetRequiredService<ImportOpcUaDialogViewModel>() ;
var importedVariables = await _dialogService.ShowDialogAsync(importOpcUaDialogViewModel);
if (importedVariables == null || !importedVariables.Any())
{

View File

@@ -15,6 +15,7 @@
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
@@ -22,9 +23,31 @@
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<!-- 连接区域 -->
<StackPanel
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0,0,0,10"
Orientation="Horizontal">
<TextBox
x:Name="EndpointUrlTextBox"
Width="300"
Margin="0,0,10,0"
VerticalAlignment="Center"
IsEnabled="False"
Text="{Binding EndpointUrl, UpdateSourceTrigger=PropertyChanged}" />
<Button
x:Name="ConnectButton"
Command="{Binding ConnectCommand}"
Content="{Binding ConnectButtonText}"
IsEnabled="{Binding IsConnectButtonEnabled}"
Style="{StaticResource AccentButtonStyle}" />
</StackPanel>
<!-- 节点树 -->
<TreeView
Grid.Row="0"
Grid.Row="1"
Grid.Column="0"
Margin="0,0,10,0"
ItemsSource="{Binding OpcUaNodes}"
@@ -48,7 +71,7 @@
<!-- 变量列表 -->
<DataGrid
Grid.Row="0"
Grid.Row="1"
Grid.Column="1"
AutoGenerateColumns="False"
IsReadOnly="True"

View File

@@ -10,16 +10,10 @@ namespace DMS.WPF.Views.Dialogs;
/// </summary>
public partial class ImportOpcUaDialog : ContentDialog
{
public ImportOpcUaDialogViewModel ViewModel
{
get => (ImportOpcUaDialogViewModel)DataContext;
set => DataContext = value;
}
public ImportOpcUaDialog(ImportOpcUaDialogViewModel viewModel)
public ImportOpcUaDialog()
{
InitializeComponent();
ViewModel = viewModel;
}
private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)