1,重新梳理了代码,将使用多线程,并实现了批量读取变量

2,添加OpcUa更新方式的修改对话框
3,修复了一些已知的Bug
4,删除了不必要的函数
This commit is contained in:
2025-07-13 16:22:07 +08:00
parent 82634f46c0
commit 6f16a1c4e4
22 changed files with 877 additions and 704 deletions

View File

@@ -1,103 +0,0 @@
using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Configuration;
namespace PMSWPF.Helper;
public static class OpcUaServiceHelper
{
/// <summary>
/// 创建并配置 OPC UA 会话。
/// </summary>
/// <param name="endpointUrl">OPC UA 服务器的终结点 URL。</param>
/// <returns>创建的 Session 对象,如果失败则返回 null。</returns>
public static async Task<Session> CreateOpcUaSessionAsync(string endpointUrl)
{
try
{
// 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);
var 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}");
return session;
}
catch (Exception ex)
{
NotificationHelper.ShowError($"连接 OPC UA 服务器失败: {endpointUrl} - {ex.Message}", ex);
return null;
}
}
}

184
Helper/ServiceHelper.cs Normal file
View File

@@ -0,0 +1,184 @@
using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Configuration;
using PMSWPF.Enums;
namespace PMSWPF.Helper;
public static class ServiceHelper
{
// 定义不同轮询级别的间隔时间。
public static Dictionary<PollLevelType, TimeSpan> PollingIntervals = new Dictionary<PollLevelType, TimeSpan>
{
{
PollLevelType.TenMilliseconds,
TimeSpan.FromMilliseconds(
(int)PollLevelType.TenMilliseconds)
},
{
PollLevelType.HundredMilliseconds,
TimeSpan.FromMilliseconds(
(int)PollLevelType
.HundredMilliseconds)
},
{
PollLevelType.FiveHundredMilliseconds,
TimeSpan.FromMilliseconds(
(int)PollLevelType
.FiveHundredMilliseconds)
},
{
PollLevelType.OneSecond,
TimeSpan.FromMilliseconds(
(int)PollLevelType.OneSecond)
},
{
PollLevelType.FiveSeconds,
TimeSpan.FromMilliseconds(
(int)PollLevelType.FiveSeconds)
},
{
PollLevelType.TenSeconds,
TimeSpan.FromMilliseconds(
(int)PollLevelType.TenSeconds)
},
{
PollLevelType.TwentySeconds,
TimeSpan.FromMilliseconds(
(int)PollLevelType.TwentySeconds)
},
{
PollLevelType.ThirtySeconds,
TimeSpan.FromMilliseconds(
(int)PollLevelType.ThirtySeconds)
},
{
PollLevelType.OneMinute,
TimeSpan.FromMilliseconds(
(int)PollLevelType.OneMinute)
},
{
PollLevelType.ThreeMinutes,
TimeSpan.FromMilliseconds(
(int)PollLevelType.ThreeMinutes)
},
{
PollLevelType.FiveMinutes,
TimeSpan.FromMilliseconds(
(int)PollLevelType.FiveMinutes)
},
{
PollLevelType.TenMinutes,
TimeSpan.FromMilliseconds(
(int)PollLevelType.TenMinutes)
},
{
PollLevelType.ThirtyMinutes,
TimeSpan.FromMilliseconds(
(int)PollLevelType.ThirtyMinutes)
}
};
/// <summary>
/// 创建并配置 OPC UA 会话。
/// </summary>
/// <param name="endpointUrl">OPC UA 服务器的终结点 URL。</param>
/// <returns>创建的 Session 对象,如果失败则返回 null。</returns>
public static Session CreateOpcUaSession(string endpointUrl)
{
return CreateOpcUaSessionAsync(endpointUrl)
.GetAwaiter()
.GetResult();
}
/// <summary>
/// 创建并配置 OPC UA 会话。
/// </summary>
/// <param name="endpointUrl">OPC UA 服务器的终结点 URL。</param>
/// <returns>创建的 Session 对象,如果失败则返回 null。</returns>
public static async Task<Session> CreateOpcUaSessionAsync(string endpointUrl)
{
try
{
// 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);
var 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}");
return session;
}
catch (Exception ex)
{
NotificationHelper.ShowError($"连接 OPC UA 服务器失败: {endpointUrl} - {ex.Message}", ex);
return null;
}
}
}

96
Helper/ThemeHelper.cs Normal file
View File

@@ -0,0 +1,96 @@
using System;
using System.Linq;
using System.Windows;
using iNKORE.UI.WPF.Modern;
using Microsoft.Win32;
using PMSWPF.Config;
namespace PMSWPF.Helper;
public static class ThemeHelper
{
public static void ApplyTheme(string themeName)
{
ApplicationTheme theme;
if (themeName == "跟随系统")
{
theme = IsSystemInDarkTheme() ? ApplicationTheme.Dark : ApplicationTheme.Light;
}
else
{
theme = themeName switch
{
"浅色" => ApplicationTheme.Light,
"深色" => ApplicationTheme.Dark,
_ => IsSystemInDarkTheme() ? ApplicationTheme.Dark : ApplicationTheme.Light
};
}
// Apply theme for iNKORE controls
ThemeManager.Current.ApplicationTheme = theme;
// Apply theme for HandyControl
UpdateHandyControlTheme(theme);
}
public static void InitializeTheme()
{
var settings = ConnectionSettings.Load();
ApplyTheme(settings.Theme);
// Listen for system theme changes
SystemEvents.UserPreferenceChanged += (s, e) =>
{
if (e.Category == UserPreferenceCategory.General && ConnectionSettings.Load().Theme == "跟随系统")
{
Application.Current.Dispatcher.Invoke(() => { ApplyTheme("跟随系统"); });
}
};
}
private static void UpdateHandyControlTheme(ApplicationTheme theme)
{
var dictionaries = Application.Current.Resources.MergedDictionaries;
// Find and remove the existing HandyControl skin dictionary
var existingSkin = dictionaries.FirstOrDefault(d =>
d.Source != null && d.Source.OriginalString.Contains("HandyControl;component/Themes/Skin"));
if (existingSkin != null)
{
dictionaries.Remove(existingSkin);
}
// Determine the new skin URI
string skinUri = theme == ApplicationTheme.Dark
? "pack://application:,,,/HandyControl;component/Themes/SkinDark.xaml"
: "pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml";
// Add the new skin dictionary
dictionaries.Add(new ResourceDictionary { Source = new Uri(skinUri, UriKind.Absolute) });
// To force refresh of dynamic resources, remove and re-add the main theme dictionary
var existingTheme = dictionaries.FirstOrDefault(d => d.Source != null && d.Source.OriginalString.Contains("HandyControl;component/Themes/Theme.xaml"));
if (existingTheme != null)
{
dictionaries.Remove(existingTheme);
dictionaries.Add(existingTheme);
}
}
private static bool IsSystemInDarkTheme()
{
try
{
const string keyName = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
const string valueName = "AppsUseLightTheme";
var value = Registry.GetValue(keyName, valueName, 1);
return value is 0;
}
catch
{
// Default to light theme if registry access fails
return false;
}
}
}