完成打开导入OPC变量对话框自动连接服务器

This commit is contained in:
2025-07-09 21:35:51 +08:00
parent 5915a6c87b
commit b1e79a63df
6 changed files with 81 additions and 56 deletions

View File

@@ -180,9 +180,10 @@ public class DialogService :IDialogService
return result == ContentDialogResult.Primary ? vm.SelectedMqtt : null;
}
public async Task<List<VariableData>> ShowOpcUaImportDialog()
public async Task<List<VariableData>> ShowOpcUaImportDialog(string endpointUrl)
{
var vm= new OpcUaImportDialogViewModel();
vm.EndpointUrl = endpointUrl;
var dialog = new OpcUaImportDialog(vm);
var result = await dialog.ShowAsync();
return result == ContentDialogResult.Primary ? vm.GetSelectedVariables().ToList() : null;

View File

@@ -22,5 +22,5 @@ public interface IDialogService
ContentDialog ShowProcessingDialog(string title, string message);
Task<PollLevelType?> ShowPollLevelDialog(PollLevelType pollLevelType);
Task<Mqtt?> ShowMqttSelectionDialog();
Task<List<VariableData>> ShowOpcUaImportDialog();
Task<List<VariableData>> ShowOpcUaImportDialog(string endpointUrl);
}

View File

@@ -216,43 +216,76 @@ namespace PMSWPF.Services
{
try
{
// 1. 创建应用程序实例。
ApplicationInstance application = new ApplicationInstance
{
ApplicationName = "PMSWPF OPC UA Client",
ApplicationType = ApplicationType.Client,
ConfigSectionName = "PMSWPF.OpcUaClient"
};
// 1. 创建应用程序配置
var application = new ApplicationInstance
{
ApplicationName = "OpcUADemoClient",
ApplicationType = ApplicationType.Client,
ConfigSectionName = "Opc.Ua.Client"
};
// 2. 加载应用程序配置。
ApplicationConfiguration config = await application.LoadApplicationConfiguration(false);
var config = new ApplicationConfiguration()
{
ApplicationName = application.ApplicationName,
ApplicationUri = $"urn:{System.Net.Dns.GetHostName()}:OpcUADemoClient",
ApplicationType = application.ApplicationType,
SecurityConfiguration = new SecurityConfiguration
{
ApplicationCertificate = new CertificateIdentifier
{
StoreType = "Directory",
StorePath
= "%CommonApplicationData%/OPC Foundation/CertificateStores/MachineDefault",
SubjectName = application.ApplicationName
},
TrustedIssuerCertificates
= new CertificateTrustList
{
StoreType = "Directory",
StorePath
= "%CommonApplicationData%/OPC Foundation/CertificateStores/UA Certificate Authorities"
},
TrustedPeerCertificates
= new CertificateTrustList
{
StoreType = "Directory",
StorePath
= "%CommonApplicationData%/OPC Foundation/CertificateStores/UA Applications"
},
RejectedCertificateStore
= new CertificateTrustList
{
StoreType = "Directory",
StorePath
= "%CommonApplicationData%/OPC Foundation/CertificateStores/RejectedCertificates"
},
AutoAcceptUntrustedCertificates = true // 自动接受不受信任的证书 (仅用于测试)
},
TransportQuotas = new TransportQuotas { OperationTimeout = 15000 },
ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 },
TraceConfiguration = new TraceConfiguration
{
OutputFilePath = "./Logs/OpcUaClient.log", DeleteOnLoad = true,
TraceMasks = Utils.TraceMasks.Error | Utils.TraceMasks.Security
}
};
application.ApplicationConfiguration = config;
// 3. 检查应用程序实例证书
bool haveAppCertificate = await application.CheckApplicationInstanceCertificate(false, 0);
if (!haveAppCertificate)
{
throw new Exception("Application instance certificate invalid!");
}
// 验证并检查证书
await config.Validate(ApplicationType.Client);
await application.CheckApplicationInstanceCertificate(false, 0);
// 4. 发现服务器提供的终结点。
DiscoveryClient discoveryClient = DiscoveryClient.Create(new Uri(device.OpcUaEndpointUrl));
EndpointDescriptionCollection endpoints = discoveryClient.GetEndpoints(new Opc.Ua.StringCollection { device.OpcUaEndpointUrl });
// 简化处理:选择第一个无安全策略的终结点。在生产环境中应选择合适的安全策略。
// ConfiguredEndpoint configuredEndpoint = new ConfiguredEndpoint(null, endpoints.First(e => e.SecurityMode == MessageSecurityMode.None), config);
EndpointDescription selectedEndpoint = CoreClientUtils.SelectEndpoint(application.ApplicationConfiguration, device.OpcUaEndpointUrl, false);
EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(application.ApplicationConfiguration);
ConfiguredEndpoint configuredEndpoint = new ConfiguredEndpoint(null, selectedEndpoint, endpointConfiguration);
// 2. 查找并选择端点 (将 useSecurity 设置为 false 以进行诊断)
var selectedEndpoint = CoreClientUtils.SelectEndpoint(device.OpcUaEndpointUrl, false);
// 5. 创建会话。
session = await Session.Create(
config,
configuredEndpoint,
false, // 不更新证书
"PMSWPF OPC UA Session", // 会话名称
60000, // 会话超时时间(毫秒)
new UserIdentity(new AnonymousIdentityToken()), // 使用匿名用户身份
null);
session = await Session.Create(
config,
new ConfiguredEndpoint(null, selectedEndpoint, EndpointConfiguration.Create(config)),
false,
"PMSWPF OPC UA Session",
60000,
new UserIdentity(new AnonymousIdentityToken()),
null);
_opcUaSessions[device.OpcUaEndpointUrl] = session;
NlogHelper.Info($"Connected to OPC UA server: {device.OpcUaEndpointUrl}");

View File

@@ -37,6 +37,8 @@ public partial class OpcUaImportDialogViewModel : ObservableObject
{
OpcUaNodes = new ObservableCollection<OpcUaNode>();
SelectedNodeVariables = new ObservableCollection<VariableData>();
// Automatically connect when the ViewModel is created
_ = Connect().ConfigureAwait(false);
}
[RelayCommand]

View File

@@ -232,7 +232,13 @@ partial class VariableTableViewModel : ViewModelBase
{
try
{
var importedVariables = await _dialogService.ShowOpcUaImportDialog();
string opcUaEndpointUrl = VariableTable?.Device?.OpcUaEndpointUrl;
if (string.IsNullOrEmpty(opcUaEndpointUrl))
{
NotificationHelper.ShowError("OPC UA Endpoint URL 未设置。请在设备详情中配置。");
return;
}
var importedVariables = await _dialogService.ShowOpcUaImportDialog(opcUaEndpointUrl);
if (importedVariables == null || !importedVariables.Any())
{
return; // 用户取消或没有选择任何变量

View File

@@ -12,11 +12,9 @@
SecondaryButtonText="取消"
PrimaryButtonClick="ContentDialog_PrimaryButtonClick"
SecondaryButtonClick="ContentDialog_SecondaryButtonClick"
Width="800"
Height="600">
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
@@ -24,23 +22,8 @@
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<!-- 连接设置 -->
<StackPanel Grid.Row="0"
Grid.ColumnSpan="2"
Orientation="Horizontal"
Margin="0,0,0,10">
<TextBlock Text="Endpoint URL:"
VerticalAlignment="Center"
Margin="0,0,5,0" />
<TextBox Text="{Binding EndpointUrl, UpdateSourceTrigger=PropertyChanged}"
Width="300"
Margin="0,0,10,0" />
<Button Content="连接"
Command="{Binding ConnectCommand}" />
</StackPanel>
<!-- 节点树 -->
<TreeView Grid.Row="1"
<TreeView Grid.Row="0"
Grid.Column="0"
ItemsSource="{Binding OpcUaNodes}"
Margin="0,0,10,0"
@@ -67,7 +50,7 @@
</TreeView>
<!-- 变量列表 -->
<DataGrid Grid.Row="1"
<DataGrid Grid.Row="0"
Grid.Column="1"
ItemsSource="{Binding SelectedNodeVariables}"
SelectionChanged="Selector_OnSelectionChanged"