完成从OPCServer导入变量的基础功能
This commit is contained in:
@@ -29,6 +29,7 @@ public class DbVariableData
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 节点ID,用于标识变量在设备或系统中的唯一路径。
|
/// 节点ID,用于标识变量在设备或系统中的唯一路径。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[SugarColumn(IsNullable = true)]
|
||||||
public string S7Address { get; set; } = String.Empty;
|
public string S7Address { get; set; } = String.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -94,7 +95,8 @@ public class DbVariableData
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 轮询级别,例如1秒、5秒等。
|
/// 轮询级别,例如1秒、5秒等。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SugarColumn(ColumnDataType = "varchar(20)", SqlParameterDbType = typeof(EnumToStringConvert))]
|
|
||||||
|
[SugarColumn(ColumnDataType = "varchar(20)",IsNullable =true, SqlParameterDbType = typeof(EnumToStringConvert))]
|
||||||
public PollLevelType PollLevelType { get; set; }
|
public PollLevelType PollLevelType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
73
Models/OpcUaNode.cs
Normal file
73
Models/OpcUaNode.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Opc.Ua;
|
||||||
|
|
||||||
|
namespace PMSWPF.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 表示OPC UA节点,用于构建节点树。
|
||||||
|
/// </summary>
|
||||||
|
public partial class OpcUaNode : ObservableObject
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 节点的显示名称。
|
||||||
|
/// </summary>
|
||||||
|
public string DisplayName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点的唯一标识符。
|
||||||
|
/// </summary>
|
||||||
|
public NodeId NodeId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点的类型(例如,文件夹、变量)。
|
||||||
|
/// </summary>
|
||||||
|
public NodeType NodeType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 子节点集合。
|
||||||
|
/// </summary>
|
||||||
|
[ObservableProperty]
|
||||||
|
private ObservableCollection<OpcUaNode> _children;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 指示节点是否已加载子节点。
|
||||||
|
/// </summary>
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isLoaded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 指示节点是否正在加载子节点。
|
||||||
|
/// </summary>
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isLoading;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点的完整路径(可选,用于调试或显示)。
|
||||||
|
/// </summary>
|
||||||
|
public string Path { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造函数。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="displayName">显示名称。</param>
|
||||||
|
/// <param name="nodeId">节点ID。</param>
|
||||||
|
/// <param name="nodeType">节点类型。</param>
|
||||||
|
public OpcUaNode(string displayName, NodeId nodeId, NodeType nodeType)
|
||||||
|
{
|
||||||
|
DisplayName = displayName;
|
||||||
|
NodeId = nodeId;
|
||||||
|
NodeType = nodeType;
|
||||||
|
Children = new ObservableCollection<OpcUaNode>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// OPC UA节点类型枚举。
|
||||||
|
/// </summary>
|
||||||
|
public enum NodeType
|
||||||
|
{
|
||||||
|
Folder,
|
||||||
|
Object,
|
||||||
|
Variable
|
||||||
|
}
|
||||||
58
PMSWPF.OpcUaClient.Config.xml
Normal file
58
PMSWPF.OpcUaClient.Config.xml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<ApplicationConfiguration
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:ua="http://opcfoundation.org/UA/2008/02/Types.xsd"
|
||||||
|
xmlns="http://opcfoundation.org/UA/SDK/Configuration.xsd"
|
||||||
|
>
|
||||||
|
<ApplicationName>PMSWPF OPC UA Client</ApplicationName>
|
||||||
|
<ApplicationUri>urn:{System.Net.Dns.GetHostName()}:PMSWPF.OpcUaClient</ApplicationUri>
|
||||||
|
<ApplicationType>0</ApplicationType>
|
||||||
|
|
||||||
|
<SecurityConfiguration>
|
||||||
|
<ApplicationCertificate>
|
||||||
|
<StoreType>Directory</StoreType>
|
||||||
|
<StorePath>%CommonApplicationData%/OPC Foundation/CertificateStores/MachineDefault</StorePath>
|
||||||
|
<SubjectName>CN=PMSWPF OPC UA Client, O=OPC Foundation, OU=UA Applications</SubjectName>
|
||||||
|
</ApplicationCertificate>
|
||||||
|
<TrustedIssuerCertificates>
|
||||||
|
<StoreType>Directory</StoreType>
|
||||||
|
<StorePath>%CommonApplicationData%/OPC Foundation/CertificateStores/UA Certificate Authorities</StorePath>
|
||||||
|
</TrustedIssuerCertificates>
|
||||||
|
<TrustedPeerCertificates>
|
||||||
|
<StoreType>Directory</StoreType>
|
||||||
|
<StorePath>%CommonApplicationData%/OPC Foundation/CertificateStores/UA Applications</StorePath>
|
||||||
|
</TrustedPeerCertificates>
|
||||||
|
<RejectedCertificateStore>
|
||||||
|
<StoreType>Directory</StoreType>
|
||||||
|
<StorePath>%CommonApplicationData%/OPC Foundation/CertificateStores/RejectedCertificates</StorePath>
|
||||||
|
</RejectedCertificateStore>
|
||||||
|
<AutoAcceptUntrustedCertificates>true</AutoAcceptUntrustedCertificates>
|
||||||
|
</SecurityConfiguration>
|
||||||
|
|
||||||
|
<TransportQuotas>
|
||||||
|
<OperationTimeout>15000</OperationTimeout>
|
||||||
|
<MaxMessageSize>4194304</MaxMessageSize>
|
||||||
|
<MaxArrayLength>65535</MaxArrayLength>
|
||||||
|
<MaxStringLength>65535</MaxStringLength>
|
||||||
|
<MaxByteStringLength>4194304</MaxByteStringLength>
|
||||||
|
</TransportQuotas>
|
||||||
|
|
||||||
|
<ClientConfiguration>
|
||||||
|
<DefaultSessionTimeout>60000</DefaultSessionTimeout>
|
||||||
|
<WellKnownDiscoveryUrls />
|
||||||
|
<DiscoveryServers />
|
||||||
|
<EndpointUrls />
|
||||||
|
<LocaleIds>
|
||||||
|
<LocaleId>en-US</LocaleId>
|
||||||
|
</LocaleIds>
|
||||||
|
<MinSubscriptionLifetime>10000</MinSubscriptionLifetime>
|
||||||
|
</ClientConfiguration>
|
||||||
|
|
||||||
|
<TraceConfiguration>
|
||||||
|
<OutputFilePath>./Logs/OpcUaClient.log</OutputFilePath>
|
||||||
|
<DeleteOnLoad>true</DeleteOnLoad>
|
||||||
|
<TraceMasks>1023</TraceMasks>
|
||||||
|
</TraceConfiguration>
|
||||||
|
|
||||||
|
<Extensions />
|
||||||
|
</ApplicationConfiguration>
|
||||||
@@ -37,6 +37,11 @@
|
|||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="PMSWPF.OpcUaClient.Config.xml">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="Data\Repositories\BaseRepositories.cs" />
|
<Compile Remove="Data\Repositories\BaseRepositories.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -177,10 +177,14 @@ public class DialogService :IDialogService
|
|||||||
var vm = new MqttSelectionDialogViewModel();
|
var vm = new MqttSelectionDialogViewModel();
|
||||||
var dialog = new MqttSelectionDialog(vm);
|
var dialog = new MqttSelectionDialog(vm);
|
||||||
var result = await dialog.ShowAsync();
|
var result = await dialog.ShowAsync();
|
||||||
if (result == ContentDialogResult.Primary)
|
return result == ContentDialogResult.Primary ? vm.SelectedMqtt : null;
|
||||||
{
|
}
|
||||||
return vm.SelectedMqtt;
|
|
||||||
}
|
public async Task<List<VariableData>> ShowOpcUaImportDialog()
|
||||||
return null;
|
{
|
||||||
|
var vm= new OpcUaImportDialogViewModel();
|
||||||
|
var dialog = new OpcUaImportDialog(vm);
|
||||||
|
var result = await dialog.ShowAsync();
|
||||||
|
return result == ContentDialogResult.Primary ? vm.GetSelectedVariables().ToList() : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,4 +22,5 @@ public interface IDialogService
|
|||||||
ContentDialog ShowProcessingDialog(string title, string message);
|
ContentDialog ShowProcessingDialog(string title, string message);
|
||||||
Task<PollLevelType?> ShowPollLevelDialog(PollLevelType pollLevelType);
|
Task<PollLevelType?> ShowPollLevelDialog(PollLevelType pollLevelType);
|
||||||
Task<Mqtt?> ShowMqttSelectionDialog();
|
Task<Mqtt?> ShowMqttSelectionDialog();
|
||||||
|
Task<List<VariableData>> ShowOpcUaImportDialog();
|
||||||
}
|
}
|
||||||
340
ViewModels/Dialogs/OpcUaImportDialogViewModel.cs
Normal file
340
ViewModels/Dialogs/OpcUaImportDialogViewModel.cs
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using Opc.Ua;
|
||||||
|
using Opc.Ua.Client;
|
||||||
|
using Opc.Ua.Configuration;
|
||||||
|
using PMSWPF.Enums;
|
||||||
|
using PMSWPF.Helper;
|
||||||
|
using PMSWPF.Models;
|
||||||
|
|
||||||
|
namespace PMSWPF.ViewModels.Dialogs;
|
||||||
|
|
||||||
|
public partial class OpcUaImportDialogViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _endpointUrl = "opc.tcp://127.0.0.1:4855"; // 默认值
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private ObservableCollection<OpcUaNode> _opcUaNodes;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private ObservableCollection<VariableData> _selectedNodeVariables;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _selectAllVariables;
|
||||||
|
|
||||||
|
private Session _session;
|
||||||
|
|
||||||
|
public OpcUaImportDialogViewModel()
|
||||||
|
{
|
||||||
|
OpcUaNodes = new ObservableCollection<OpcUaNode>();
|
||||||
|
SelectedNodeVariables = new ObservableCollection<VariableData>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task Connect()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 断开现有连接
|
||||||
|
if (_session != null && _session.Connected)
|
||||||
|
{
|
||||||
|
_session.Close();
|
||||||
|
_session.Dispose();
|
||||||
|
_session = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*ApplicationInstance application = new ApplicationInstance
|
||||||
|
{
|
||||||
|
ApplicationName = "PMSWPF OPC UA Client",
|
||||||
|
ApplicationType = ApplicationType.Client,
|
||||||
|
ConfigSectionName = "PMSWPF.OpcUaClient"
|
||||||
|
};
|
||||||
|
|
||||||
|
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 }
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// bool haveAppCertificate = await application.CheckApplicationInstanceCertificate(false, 0);
|
||||||
|
// if (!haveAppCertificate)
|
||||||
|
// {
|
||||||
|
// throw new Exception("Application instance certificate invalid!");
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// EndpointDescription selectedEndpoint
|
||||||
|
// = CoreClientUtils.SelectEndpoint(application.ApplicationConfiguration, EndpointUrl, false);
|
||||||
|
// EndpointConfiguration endpointConfiguration
|
||||||
|
// = EndpointConfiguration.Create(application.ApplicationConfiguration);
|
||||||
|
// ConfiguredEndpoint configuredEndpoint
|
||||||
|
// = new ConfiguredEndpoint(null, selectedEndpoint, endpointConfiguration);
|
||||||
|
// 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 }
|
||||||
|
// };
|
||||||
|
|
||||||
|
bool haveAppCertificate = await application.CheckApplicationInstanceCertificate(false, 0);
|
||||||
|
if (!haveAppCertificate)
|
||||||
|
{
|
||||||
|
throw new Exception("Application instance certificate invalid!");
|
||||||
|
}
|
||||||
|
|
||||||
|
EndpointDescription selectedEndpoint
|
||||||
|
= CoreClientUtils.SelectEndpoint(application.ApplicationConfiguration, EndpointUrl, false);
|
||||||
|
EndpointConfiguration endpointConfiguration
|
||||||
|
= EndpointConfiguration.Create(application.ApplicationConfiguration);
|
||||||
|
ConfiguredEndpoint configuredEndpoint
|
||||||
|
= new ConfiguredEndpoint(null, selectedEndpoint, endpointConfiguration);
|
||||||
|
|
||||||
|
_session = await Session.Create(
|
||||||
|
config,
|
||||||
|
configuredEndpoint,
|
||||||
|
false,
|
||||||
|
"PMSWPF OPC UA Session",
|
||||||
|
60000,
|
||||||
|
new UserIdentity(new AnonymousIdentityToken()),
|
||||||
|
null);
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 1. 创建应用程序配置
|
||||||
|
var application = new ApplicationInstance
|
||||||
|
{
|
||||||
|
ApplicationName = "OpcUADemoClient",
|
||||||
|
ApplicationType = ApplicationType.Client,
|
||||||
|
ConfigSectionName = "Opc.Ua.Client"
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 验证并检查证书
|
||||||
|
await config.Validate(ApplicationType.Client);
|
||||||
|
await application.CheckApplicationInstanceCertificate(false, 0);
|
||||||
|
|
||||||
|
// 2. 查找并选择端点 (将 useSecurity 设置为 false 以进行诊断)
|
||||||
|
var selectedEndpoint = CoreClientUtils.SelectEndpoint(EndpointUrl, false);
|
||||||
|
|
||||||
|
_session = await Session.Create(
|
||||||
|
config,
|
||||||
|
new ConfiguredEndpoint(null, selectedEndpoint, EndpointConfiguration.Create(config)),
|
||||||
|
false,
|
||||||
|
"PMSWPF OPC UA Session",
|
||||||
|
60000,
|
||||||
|
new UserIdentity(new AnonymousIdentityToken()),
|
||||||
|
null);
|
||||||
|
|
||||||
|
NotificationHelper.ShowSuccess($"已连接到 OPC UA 服务器: {EndpointUrl}");
|
||||||
|
|
||||||
|
// 浏览根节点
|
||||||
|
await BrowseNodes(OpcUaNodes, ObjectIds.ObjectsFolder);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
NlogHelper.Error($"连接 OPC UA 服务器失败: {EndpointUrl} - {ex.Message}", ex);
|
||||||
|
NotificationHelper.ShowError($"连接 OPC UA 服务器失败: {EndpointUrl} - {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 处理来自服务器的数据变化通知
|
||||||
|
/// </summary>
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task BrowseNodes(ObservableCollection<OpcUaNode> nodes, NodeId parentNodeId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Opc.Ua.ReferenceDescriptionCollection references;
|
||||||
|
byte[] continuationPoint = null;
|
||||||
|
|
||||||
|
_session.Browse(
|
||||||
|
null, // RequestHeader
|
||||||
|
new ViewDescription(),
|
||||||
|
parentNodeId,
|
||||||
|
0u,
|
||||||
|
BrowseDirection.Forward,
|
||||||
|
Opc.Ua.ReferenceTypeIds.HierarchicalReferences,
|
||||||
|
true,
|
||||||
|
(uint)Opc.Ua.NodeClass.Object | (uint)Opc.Ua.NodeClass.Variable,
|
||||||
|
out continuationPoint,
|
||||||
|
out references
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach (var rd in references)
|
||||||
|
{
|
||||||
|
NodeType nodeType = NodeType.Folder; // 默认是文件夹
|
||||||
|
if ((rd.NodeClass & NodeClass.Variable) != 0)
|
||||||
|
{
|
||||||
|
nodeType = NodeType.Variable;
|
||||||
|
}
|
||||||
|
else if ((rd.NodeClass & NodeClass.Object) != 0)
|
||||||
|
{
|
||||||
|
nodeType = NodeType.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
var opcUaNode = new OpcUaNode(rd.DisplayName.Text, (NodeId)rd.NodeId, nodeType);
|
||||||
|
nodes.Add(opcUaNode);
|
||||||
|
|
||||||
|
// 如果是文件夹或对象,添加一个虚拟子节点,用于懒加载
|
||||||
|
if (nodeType == NodeType.Folder || nodeType == NodeType.Object)
|
||||||
|
{
|
||||||
|
opcUaNode.Children.Add(new OpcUaNode("Loading...", NodeId.Null, NodeType.Folder)); // 虚拟节点
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
NlogHelper.Error($"浏览 OPC UA 节点失败: {parentNodeId} - {ex.Message}", ex);
|
||||||
|
NotificationHelper.ShowError($"浏览 OPC UA 节点失败: {parentNodeId} - {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LoadNodeVariables(OpcUaNode node)
|
||||||
|
{
|
||||||
|
if (node.NodeType == NodeType.Variable)
|
||||||
|
{
|
||||||
|
// 如果是变量节点,直接显示它
|
||||||
|
SelectedNodeVariables.Clear();
|
||||||
|
SelectedNodeVariables.Add(new VariableData
|
||||||
|
{
|
||||||
|
Name = node.DisplayName,
|
||||||
|
NodeId = node.NodeId.ToString(),
|
||||||
|
OpcUaNodeId = node.NodeId.ToString(),
|
||||||
|
ProtocolType = ProtocolType.OpcUA,
|
||||||
|
IsActive = true // 默认选中
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.IsLoaded || node.IsLoading)
|
||||||
|
{
|
||||||
|
return; // 已经加载或正在加载
|
||||||
|
}
|
||||||
|
|
||||||
|
node.IsLoading = true;
|
||||||
|
node.Children.Clear(); // 清除虚拟节点
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Opc.Ua.ReferenceDescriptionCollection references;
|
||||||
|
byte[] continuationPoint = null;
|
||||||
|
|
||||||
|
_session.Browse(
|
||||||
|
null, // RequestHeader
|
||||||
|
new ViewDescription(),
|
||||||
|
node.NodeId,
|
||||||
|
0u,
|
||||||
|
BrowseDirection.Forward,
|
||||||
|
Opc.Ua.ReferenceTypeIds.HierarchicalReferences,
|
||||||
|
true,
|
||||||
|
(uint)Opc.Ua.NodeClass.Object | (uint)Opc.Ua.NodeClass.Variable,
|
||||||
|
out continuationPoint,
|
||||||
|
out references
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach (var rd in references)
|
||||||
|
{
|
||||||
|
NodeType nodeType = NodeType.Folder;
|
||||||
|
if ((rd.NodeClass & NodeClass.Variable) != 0)
|
||||||
|
{
|
||||||
|
nodeType = NodeType.Variable;
|
||||||
|
}
|
||||||
|
else if ((rd.NodeClass & NodeClass.Object) != 0)
|
||||||
|
{
|
||||||
|
nodeType = NodeType.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
var opcUaNode = new OpcUaNode(rd.DisplayName.Text, (NodeId)rd.NodeId, nodeType);
|
||||||
|
node.Children.Add(opcUaNode);
|
||||||
|
|
||||||
|
if (nodeType == NodeType.Folder || nodeType == NodeType.Object)
|
||||||
|
{
|
||||||
|
opcUaNode.Children.Add(new OpcUaNode("Loading...", NodeId.Null, NodeType.Folder)); // 虚拟节点
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是变量,添加到右侧列表
|
||||||
|
if (nodeType == NodeType.Variable)
|
||||||
|
{
|
||||||
|
SelectedNodeVariables.Add(new VariableData
|
||||||
|
{
|
||||||
|
Name = opcUaNode.DisplayName,
|
||||||
|
NodeId = opcUaNode.NodeId.ToString(),
|
||||||
|
OpcUaNodeId = opcUaNode.NodeId.ToString(),
|
||||||
|
ProtocolType = ProtocolType.OpcUA,
|
||||||
|
IsActive = true // 默认选中
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node.IsLoaded = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
NlogHelper.Error($"加载 OPC UA 节点变量失败: {node.NodeId} - {ex.Message}", ex);
|
||||||
|
NotificationHelper.ShowError($"加载 OPC UA 节点变量失败: {node.NodeId} - {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
node.IsLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<VariableData> GetSelectedVariables()
|
||||||
|
{
|
||||||
|
return new ObservableCollection<VariableData>(SelectedNodeVariables.Where(v => v.IsActive));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,6 +44,9 @@ partial class VariableTableViewModel : ViewModelBase
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _isS7ProtocolSelected;
|
private bool _isS7ProtocolSelected;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isOpcUaProtocolSelected;
|
||||||
|
|
||||||
public VariableTableViewModel(IDialogService dialogService)
|
public VariableTableViewModel(IDialogService dialogService)
|
||||||
{
|
{
|
||||||
_dialogService = dialogService;
|
_dialogService = dialogService;
|
||||||
@@ -85,6 +88,7 @@ partial class VariableTableViewModel : ViewModelBase
|
|||||||
public override void OnLoaded()
|
public override void OnLoaded()
|
||||||
{
|
{
|
||||||
IsS7ProtocolSelected = VariableTable.ProtocolType == ProtocolType.S7;
|
IsS7ProtocolSelected = VariableTable.ProtocolType == ProtocolType.S7;
|
||||||
|
IsOpcUaProtocolSelected = VariableTable.ProtocolType == ProtocolType.OpcUA;
|
||||||
|
|
||||||
if (VariableTable.DataVariables != null)
|
if (VariableTable.DataVariables != null)
|
||||||
{
|
{
|
||||||
@@ -223,6 +227,44 @@ partial class VariableTableViewModel : ViewModelBase
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task ImportFromOpcUaServer()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var importedVariables = await _dialogService.ShowOpcUaImportDialog();
|
||||||
|
if (importedVariables == null || !importedVariables.Any())
|
||||||
|
{
|
||||||
|
return; // 用户取消或没有选择任何变量
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentDialog processingDialog = _dialogService.ShowProcessingDialog("正在处理...", "正在导入OPC UA变量,请稍等片刻....");
|
||||||
|
|
||||||
|
foreach (var variableData in importedVariables)
|
||||||
|
{
|
||||||
|
variableData.CreateTime = DateTime.Now;
|
||||||
|
variableData.VariableTableId = VariableTable.Id;
|
||||||
|
variableData.ProtocolType = ProtocolType.OpcUA; // 确保协议类型正确
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插入数据库
|
||||||
|
var resVarDataCount = await _varDataRepository.AddAsync(importedVariables);
|
||||||
|
|
||||||
|
// 更新界面
|
||||||
|
variableTable.DataVariables = await _varDataRepository.GetAllAsync();
|
||||||
|
_dataVariables = new ObservableCollection<VariableData>(variableTable.DataVariables);
|
||||||
|
processingDialog?.Hide();
|
||||||
|
|
||||||
|
string msgSuccess = $"成功导入OPC UA变量:{resVarDataCount}个。";
|
||||||
|
NlogHelper.Info(msgSuccess);
|
||||||
|
NotificationHelper.ShowSuccess(msgSuccess);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
NotificationHelper.ShowError($"从OPC UA服务器导入变量的过程中发生了不可预期的错误:{e.Message}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async void AddVarData(VariableTable variableTable)
|
private async void AddVarData(VariableTable variableTable)
|
||||||
|
|||||||
64
Views/Dialogs/OpcUaImportDialog.xaml
Normal file
64
Views/Dialogs/OpcUaImportDialog.xaml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<controls:ContentDialog x:Class="PMSWPF.Views.Dialogs.OpcUaImportDialog"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:controls="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||||
|
xmlns:vm="clr-namespace:PMSWPF.ViewModels.Dialogs"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DataContext="{d:DesignInstance vm:OpcUaImportDialogViewModel}"
|
||||||
|
Title="从OPC UA服务器导入变量"
|
||||||
|
PrimaryButtonText="导入"
|
||||||
|
SecondaryButtonText="取消"
|
||||||
|
PrimaryButtonClick="ContentDialog_PrimaryButtonClick"
|
||||||
|
SecondaryButtonClick="ContentDialog_SecondaryButtonClick"
|
||||||
|
Width="800"
|
||||||
|
Height="600">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<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" Grid.Column="0" ItemsSource="{Binding OpcUaNodes}" Margin="0,0,10,0" SelectedItemChanged="TreeView_SelectedItemChanged">
|
||||||
|
<TreeView.ItemTemplate>
|
||||||
|
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
|
||||||
|
<TextBlock Text="{Binding DisplayName}" />
|
||||||
|
</HierarchicalDataTemplate>
|
||||||
|
</TreeView.ItemTemplate>
|
||||||
|
</TreeView>
|
||||||
|
|
||||||
|
<!-- 变量列表 -->
|
||||||
|
<DataGrid Grid.Row="1" Grid.Column="1" ItemsSource="{Binding SelectedNodeVariables}" AutoGenerateColumns="False" IsReadOnly="True">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTemplateColumn>
|
||||||
|
<DataGridTemplateColumn.HeaderTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<CheckBox IsChecked="{Binding DataContext.SelectAllVariables, RelativeSource={RelativeSource AncestorType=DataGrid}}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.HeaderTemplate>
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<CheckBox IsChecked="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
<DataGridTextColumn Header="名称" Binding="{Binding DisplayName}" />
|
||||||
|
<DataGridTextColumn Header="节点ID" Binding="{Binding NodeId}" />
|
||||||
|
<DataGridTextColumn Header="数据类型" Binding="{Binding DataType}" />
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</Grid>
|
||||||
|
</controls:ContentDialog>
|
||||||
44
Views/Dialogs/OpcUaImportDialog.xaml.cs
Normal file
44
Views/Dialogs/OpcUaImportDialog.xaml.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using iNKORE.UI.WPF.Modern.Controls;
|
||||||
|
using PMSWPF.Models;
|
||||||
|
using PMSWPF.ViewModels.Dialogs;
|
||||||
|
|
||||||
|
namespace PMSWPF.Views.Dialogs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// OpcUaImportDialog.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class OpcUaImportDialog : ContentDialog
|
||||||
|
{
|
||||||
|
public OpcUaImportDialogViewModel ViewModel
|
||||||
|
{
|
||||||
|
get => (OpcUaImportDialogViewModel)DataContext;
|
||||||
|
set => DataContext = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OpcUaImportDialog(OpcUaImportDialogViewModel viewModel)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
ViewModel = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||||
|
{
|
||||||
|
// 在这里处理导入逻辑,例如获取选中的变量
|
||||||
|
// ViewModel.ImportSelectedVariables();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ContentDialog_SecondaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||||
|
{
|
||||||
|
// 处理取消逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||||
|
{
|
||||||
|
if (e.NewValue is OpcUaNode selectedNode)
|
||||||
|
{
|
||||||
|
await ViewModel.LoadNodeVariables(selectedNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -97,6 +97,15 @@
|
|||||||
|
|
||||||
</ui:AppBarButton>
|
</ui:AppBarButton>
|
||||||
|
|
||||||
|
<ui:AppBarButton Command="{Binding ImportFromOpcUaServerCommand}"
|
||||||
|
Label="从OPC UA服务器导入"
|
||||||
|
Visibility="{Binding IsOpcUaProtocolSelected, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||||
|
<ui:AppBarButton.Icon>
|
||||||
|
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Import}" />
|
||||||
|
</ui:AppBarButton.Icon>
|
||||||
|
|
||||||
|
</ui:AppBarButton>
|
||||||
|
|
||||||
|
|
||||||
<ui:AppBarButton x:Name="ShareButton"
|
<ui:AppBarButton x:Name="ShareButton"
|
||||||
Label="Share">
|
Label="Share">
|
||||||
@@ -191,6 +200,13 @@
|
|||||||
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Import}" />
|
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Import}" />
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem Header="从OPC UA服务器导入"
|
||||||
|
Command="{Binding ImportFromOpcUaServerCommand}"
|
||||||
|
Visibility="{Binding IsOpcUaProtocolSelected, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Import}" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
<MenuItem Header="修改轮询频率"
|
<MenuItem Header="修改轮询频率"
|
||||||
Click="ChangePollLevel_Click">
|
Click="ChangePollLevel_Click">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
|
|||||||
Reference in New Issue
Block a user