完成添加OPC变量

This commit is contained in:
2025-07-09 19:38:36 +08:00
parent 9352d6c073
commit 61636fa227
5 changed files with 144 additions and 42 deletions

View File

@@ -24,6 +24,7 @@ public class DbVariableData
/// <summary> /// <summary>
/// 节点ID用于标识变量在设备或系统中的唯一路径。 /// 节点ID用于标识变量在设备或系统中的唯一路径。
/// </summary> /// </summary>
[SugarColumn(IsNullable = true)]
public string NodeId { get; set; } = String.Empty; public string NodeId { get; set; } = String.Empty;
/// <summary> /// <summary>

View File

@@ -78,6 +78,12 @@ public partial class VariableData : ObservableObject
/// </summary> /// </summary>
public bool IsActive { get; set; } public bool IsActive { get; set; }
/// <summary>
/// 指示变量是否被选中
/// </summary>
[ObservableProperty]
public bool isSelect;
/// <summary> /// <summary>
/// 指示是否需要保存变量数据。 /// 指示是否需要保存变量数据。
/// </summary> /// </summary>

View File

@@ -23,6 +23,8 @@ public partial class OpcUaImportDialogViewModel : ObservableObject
[ObservableProperty] [ObservableProperty]
private ObservableCollection<VariableData> _selectedNodeVariables; private ObservableCollection<VariableData> _selectedNodeVariables;
public List<VariableData> SelectedVariables { get; set; }=new List<VariableData>();
[ObservableProperty] [ObservableProperty]
private bool _selectAllVariables; private bool _selectAllVariables;
@@ -55,7 +57,7 @@ public partial class OpcUaImportDialogViewModel : ObservableObject
}; };
ApplicationConfiguration config = await application.LoadApplicationConfiguration(false); ApplicationConfiguration config = await application.LoadApplicationConfiguration(false);
// var config = new ApplicationConfiguration() // var config = new ApplicationConfiguration()
// { // {
// ApplicationName = application.ApplicationName, // ApplicationName = application.ApplicationName,
@@ -127,33 +129,60 @@ public partial class OpcUaImportDialogViewModel : ObservableObject
null); null);
*/ */
// 1. 创建应用程序配置 // 1. 创建应用程序配置
var application = new ApplicationInstance var application = new ApplicationInstance
{ {
ApplicationName = "OpcUADemoClient", ApplicationName = "OpcUADemoClient",
ApplicationType = ApplicationType.Client, ApplicationType = ApplicationType.Client,
ConfigSectionName = "Opc.Ua.Client" ConfigSectionName = "Opc.Ua.Client"
}; };
var config = new ApplicationConfiguration() var config = new ApplicationConfiguration()
{ {
ApplicationName = application.ApplicationName, ApplicationName = application.ApplicationName,
ApplicationUri = $"urn:{System.Net.Dns.GetHostName()}:OpcUADemoClient", ApplicationUri = $"urn:{System.Net.Dns.GetHostName()}:OpcUADemoClient",
ApplicationType = application.ApplicationType, ApplicationType = application.ApplicationType,
SecurityConfiguration = new SecurityConfiguration SecurityConfiguration = new SecurityConfiguration
{ {
ApplicationCertificate = new CertificateIdentifier { StoreType = "Directory", StorePath = "%CommonApplicationData%/OPC Foundation/CertificateStores/MachineDefault", SubjectName = application.ApplicationName }, ApplicationCertificate = new CertificateIdentifier
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" }, StoreType = "Directory",
RejectedCertificateStore = new CertificateTrustList { StoreType = "Directory", StorePath = "%CommonApplicationData%/OPC Foundation/CertificateStores/RejectedCertificates" }, StorePath
AutoAcceptUntrustedCertificates = true // 自动接受不受信任的证书 (仅用于测试) = "%CommonApplicationData%/OPC Foundation/CertificateStores/MachineDefault",
}, SubjectName = application.ApplicationName
TransportQuotas = new TransportQuotas { OperationTimeout = 15000 }, },
ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 }, TrustedIssuerCertificates
TraceConfiguration = new TraceConfiguration { OutputFilePath = "./Logs/OpcUaClient.log", DeleteOnLoad = true, TraceMasks = Utils.TraceMasks.Error | Utils.TraceMasks.Security } = 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; application.ApplicationConfiguration = config;
// 验证并检查证书 // 验证并检查证书
@@ -162,7 +191,7 @@ public partial class OpcUaImportDialogViewModel : ObservableObject
// 2. 查找并选择端点 (将 useSecurity 设置为 false 以进行诊断) // 2. 查找并选择端点 (将 useSecurity 设置为 false 以进行诊断)
var selectedEndpoint = CoreClientUtils.SelectEndpoint(EndpointUrl, false); var selectedEndpoint = CoreClientUtils.SelectEndpoint(EndpointUrl, false);
_session = await Session.Create( _session = await Session.Create(
config, config,
new ConfiguredEndpoint(null, selectedEndpoint, EndpointConfiguration.Create(config)), new ConfiguredEndpoint(null, selectedEndpoint, EndpointConfiguration.Create(config)),
@@ -183,6 +212,7 @@ public partial class OpcUaImportDialogViewModel : ObservableObject
NotificationHelper.ShowError($"连接 OPC UA 服务器失败: {EndpointUrl} - {ex.Message}", ex); NotificationHelper.ShowError($"连接 OPC UA 服务器失败: {EndpointUrl} - {ex.Message}", ex);
} }
} }
/// <summary> /// <summary>
/// 处理来自服务器的数据变化通知 /// 处理来自服务器的数据变化通知
/// </summary> /// </summary>
@@ -190,7 +220,8 @@ public partial class OpcUaImportDialogViewModel : ObservableObject
{ {
foreach (var value in item.DequeueValues()) foreach (var value in item.DequeueValues())
{ {
Console.WriteLine($"[通知] {item.DisplayName}: {value.Value} | 时间戳: {value.SourceTimestamp.ToLocalTime()} | 状态: {value.StatusCode}"); Console.WriteLine(
$"[通知] {item.DisplayName}: {value.Value} | 时间戳: {value.SourceTimestamp.ToLocalTime()} | 状态: {value.StatusCode}");
} }
} }
@@ -309,13 +340,42 @@ public partial class OpcUaImportDialogViewModel : ObservableObject
// 如果是变量,添加到右侧列表 // 如果是变量,添加到右侧列表
if (nodeType == NodeType.Variable) if (nodeType == NodeType.Variable)
{ {
// Read the DataType attribute
ReadValueId readValueId = new ReadValueId
{
NodeId = opcUaNode.NodeId,
AttributeId = Attributes.DataType,
// You might need to specify IndexRange and DataEncoding if dealing with arrays or specific encodings
};
DataValueCollection results;
DiagnosticInfoCollection diagnosticInfos;
_session.Read(
null, // RequestHeader
0, // MaxAge
TimestampsToReturn.Source,
new ReadValueIdCollection { readValueId },
out results,
out diagnosticInfos
);
string dataType = string.Empty;
if (results != null && results.Count > 0 && results[0].Value != null)
{
// Convert the NodeId of the DataType to a readable string
NodeId dataTypeNodeId = (NodeId)results[0].Value;
dataType = _session.NodeCache.GetDisplayText(dataTypeNodeId);
}
SelectedNodeVariables.Add(new VariableData SelectedNodeVariables.Add(new VariableData
{ {
Name = opcUaNode.DisplayName, Name = opcUaNode.DisplayName,
NodeId = opcUaNode.NodeId.ToString(),
OpcUaNodeId = opcUaNode.NodeId.ToString(), OpcUaNodeId = opcUaNode.NodeId.ToString(),
ProtocolType = ProtocolType.OpcUA, ProtocolType = ProtocolType.OpcUA,
IsActive = true // 默认选中 IsActive = true, // Default selected
DataType = dataType // Assign the read DataType
}); });
} }
} }
@@ -335,6 +395,6 @@ public partial class OpcUaImportDialogViewModel : ObservableObject
public ObservableCollection<VariableData> GetSelectedVariables() public ObservableCollection<VariableData> GetSelectedVariables()
{ {
return new ObservableCollection<VariableData>(SelectedNodeVariables.Where(v => v.IsActive)); return new ObservableCollection<VariableData>(SelectedVariables);
} }
} }

View File

@@ -25,14 +25,26 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- 连接设置 --> <!-- 连接设置 -->
<StackPanel Grid.Row="0" Grid.ColumnSpan="2" Orientation="Horizontal" Margin="0,0,0,10"> <StackPanel Grid.Row="0"
<TextBlock Text="Endpoint URL:" VerticalAlignment="Center" Margin="0,0,5,0" /> Grid.ColumnSpan="2"
<TextBox Text="{Binding EndpointUrl, UpdateSourceTrigger=PropertyChanged}" Width="300" Margin="0,0,10,0" /> Orientation="Horizontal"
<Button Content="连接" Command="{Binding ConnectCommand}" /> 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> </StackPanel>
<!-- 节点树 --> <!-- 节点树 -->
<TreeView Grid.Row="1" Grid.Column="0" ItemsSource="{Binding OpcUaNodes}" Margin="0,0,10,0" SelectedItemChanged="TreeView_SelectedItemChanged"> <TreeView Grid.Row="1"
Grid.Column="0"
ItemsSource="{Binding OpcUaNodes}"
Margin="0,0,10,0"
SelectedItemChanged="TreeView_SelectedItemChanged">
<TreeView.ItemTemplate> <TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}"> <HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding DisplayName}" /> <TextBlock Text="{Binding DisplayName}" />
@@ -41,24 +53,33 @@
</TreeView> </TreeView>
<!-- 变量列表 --> <!-- 变量列表 -->
<DataGrid Grid.Row="1" Grid.Column="1" ItemsSource="{Binding SelectedNodeVariables}" AutoGenerateColumns="False" IsReadOnly="True"> <DataGrid Grid.Row="1"
Grid.Column="1"
ItemsSource="{Binding SelectedNodeVariables}"
SelectionChanged="Selector_OnSelectionChanged"
AutoGenerateColumns="False"
IsReadOnly="True">
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTemplateColumn> <DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderTemplate> <DataGridTemplateColumn.HeaderTemplate>
<DataTemplate> <DataTemplate>
<CheckBox IsChecked="{Binding DataContext.SelectAllVariables, RelativeSource={RelativeSource AncestorType=DataGrid}}" /> <CheckBox
IsChecked="{Binding DataContext.SelectAllVariables, RelativeSource={RelativeSource AncestorType=DataGrid}}" />
</DataTemplate> </DataTemplate>
</DataGridTemplateColumn.HeaderTemplate> </DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate> <DataTemplate>
<CheckBox IsChecked="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged}" /> <CheckBox IsChecked="{Binding IsSelect, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate> </DataTemplate>
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn> </DataGridTemplateColumn>
<DataGridTextColumn Header="名称" Binding="{Binding DisplayName}" /> <DataGridTextColumn Header="名称"
<DataGridTextColumn Header="节点ID" Binding="{Binding NodeId}" /> Binding="{Binding Name}" />
<DataGridTextColumn Header="数据类型" Binding="{Binding DataType}" /> <DataGridTextColumn Header="节点ID"
Binding="{Binding OpcUaNodeId}" />
<DataGridTextColumn Header="数据类型"
Binding="{Binding DataType}" />
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</Grid> </Grid>
</controls:ContentDialog> </controls:ContentDialog>

View File

@@ -1,4 +1,5 @@
using System.Windows; using System.Windows;
using System.Windows.Controls;
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using iNKORE.UI.WPF.Modern.Controls; using iNKORE.UI.WPF.Modern.Controls;
using PMSWPF.Models; using PMSWPF.Models;
@@ -41,4 +42,17 @@ public partial class OpcUaImportDialog : ContentDialog
await ViewModel.LoadNodeVariables(selectedNode); await ViewModel.LoadNodeVariables(selectedNode);
} }
} }
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs args)
{
if (args.AddedItems!=null && args.AddedItems.Count>0)
{
foreach (var item in args.AddedItems)
{
ViewModel.SelectedVariables.Add((VariableData)item);
}
}
}
} }