diff --git a/.idea/.idea.PMSWPF/.idea/.gitignore b/.idea/.idea.PMSWPF/.idea/.gitignore
new file mode 100644
index 0000000..0b5e731
--- /dev/null
+++ b/.idea/.idea.PMSWPF/.idea/.gitignore
@@ -0,0 +1,13 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# Rider 忽略的文件
+/.idea.PMSWPF.iml
+/projectSettingsUpdater.xml
+/modules.xml
+/contentModel.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/.idea.PMSWPF/.idea/encodings.xml b/.idea/.idea.PMSWPF/.idea/encodings.xml
new file mode 100644
index 0000000..df87cf9
--- /dev/null
+++ b/.idea/.idea.PMSWPF/.idea/encodings.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.PMSWPF/.idea/indexLayout.xml b/.idea/.idea.PMSWPF/.idea/indexLayout.xml
new file mode 100644
index 0000000..7b08163
--- /dev/null
+++ b/.idea/.idea.PMSWPF/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.PMSWPF/.idea/vcs.xml b/.idea/.idea.PMSWPF/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/.idea.PMSWPF/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Data/Entities/DbDevice.cs b/Data/Entities/DbDevice.cs
index cd28670..ceb9b3f 100644
--- a/Data/Entities/DbDevice.cs
+++ b/Data/Entities/DbDevice.cs
@@ -79,6 +79,12 @@ public class DbDevice
[SugarColumn(ColumnDataType = "varchar(20)", SqlParameterDbType = typeof(EnumToStringConvert))]
public ProtocolType ProtocolType { get; set; }
+ ///
+ /// OPC UA Endpoint URL。
+ ///
+ [SugarColumn(IsNullable = true)]
+ public string? OpcUaEndpointUrl { get; set; }
+
///
/// 设备关联的变量表列表。
///
diff --git a/Data/Repositories/DeviceRepository.cs b/Data/Repositories/DeviceRepository.cs
index 478bbb4..8847a2f 100644
--- a/Data/Repositories/DeviceRepository.cs
+++ b/Data/Repositories/DeviceRepository.cs
@@ -89,7 +89,7 @@ public class DeviceRepository
///
/// 设备ID。
/// 对应的DbDevice对象。
- public async Task GetById(int id)
+ public async Task GetById(int id)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
@@ -99,7 +99,7 @@ public class DeviceRepository
.FirstAsync(p => p.Id == id);
stopwatch.Stop();
NlogHelper.Info($"根据ID '{id}' 获取设备耗时:{stopwatch.ElapsedMilliseconds}ms");
- return result;
+ return result.CopyTo();
}
}
@@ -252,4 +252,6 @@ public class DeviceRepository
await _menuRepository.AddVarTableMenu(addDevice, addDeviceMenuId, db);
return addDevice.CopyTo();
}
+
+
}
\ No newline at end of file
diff --git a/Data/Repositories/VarDataRepository.cs b/Data/Repositories/VarDataRepository.cs
index 7403905..78cab22 100644
--- a/Data/Repositories/VarDataRepository.cs
+++ b/Data/Repositories/VarDataRepository.cs
@@ -46,6 +46,8 @@ public class VarDataRepository
using (var _db = DbContext.GetInstance())
{
var result = await _db.Queryable()
+ .Includes(d => d.VariableTable)
+ .Includes(d => d.VariableTable.Device)
.ToListAsync();
stopwatch.Stop();
NlogHelper.Info($"获取所有VariableData耗时:{stopwatch.ElapsedMilliseconds}ms");
diff --git a/Models/Device.cs b/Models/Device.cs
index bf99298..e681023 100644
--- a/Models/Device.cs
+++ b/Models/Device.cs
@@ -88,6 +88,12 @@ public partial class Device : ObservableObject
///
public ProtocolType ProtocolType { get; set; }
+ ///
+ /// OPC UA Endpoint URL。
+ ///
+ [ObservableProperty]
+ private string? opcUaEndpointUrl;
+
///
/// 设备关联的变量表列表。
///
diff --git a/Models/VariableData.cs b/Models/VariableData.cs
index 5ace03a..3ca0349 100644
--- a/Models/VariableData.cs
+++ b/Models/VariableData.cs
@@ -154,6 +154,11 @@ public partial class VariableData : ObservableObject
///
public int VariableTableId { get; set; }
+ ///
+ /// 关联的变量表实体。
+ ///
+ public VariableTable? VariableTable { get; set; }
+
///
/// 关联的MQTT配置列表。
///
diff --git a/Services/DataServices.cs b/Services/DataServices.cs
index 801da7d..8244896 100644
--- a/Services/DataServices.cs
+++ b/Services/DataServices.cs
@@ -193,6 +193,16 @@ public partial class DataServices : ObservableRecipient, IRecipient
return await _varDataRepository.GetAllAsync();
}
+ ///
+ /// 异步根据ID获取设备数据。
+ ///
+ /// 设备ID。
+ /// 设备对象,如果不存在则为null。
+ public async Task GetDeviceByIdAsync(int id)
+ {
+ return await _deviceRepository.GetById(id);
+ }
+
///
/// 异步加载变量数据。
///
diff --git a/Services/OpcUaBackgroundService.cs b/Services/OpcUaBackgroundService.cs
index b670eff..f855362 100644
--- a/Services/OpcUaBackgroundService.cs
+++ b/Services/OpcUaBackgroundService.cs
@@ -151,8 +151,13 @@ namespace PMSWPF.Services
{
if (_opcUaVariables.TryGetValue(id, out var variable))
{
- // 断开与该变量相关的 OPC UA 会话。
- await DisconnectOpcUaSession(variable.OpcUaEndpointUrl);
+ // 获取关联的设备信息
+ var device = await _dataServices.GetDeviceByIdAsync(variable.VariableTable.DeviceId??0);
+ if (device != null)
+ {
+ // 断开与该变量相关的 OPC UA 会话。
+ await DisconnectOpcUaSession(device.OpcUaEndpointUrl);
+ }
_opcUaVariables.Remove(id);
}
}
@@ -160,20 +165,28 @@ namespace PMSWPF.Services
// 处理新增或更新的变量。
foreach (var variable in opcUaVariables)
{
+ // 获取关联的设备信息
+ var device = await _dataServices.GetDeviceByIdAsync(variable.VariableTable.DeviceId??0);
+ if (device == null)
+ {
+ NlogHelper.Warn($"变量 '{variable.Name}' (ID: {variable.Id}) 关联的设备不存在。");
+ continue;
+ }
+
if (!_opcUaVariables.ContainsKey(variable.Id))
{
// 如果是新变量,则添加到字典并建立连接和订阅。
_opcUaVariables.Add(variable.Id, variable);
- await ConnectAndSubscribeOpcUa(variable);
+ await ConnectAndSubscribeOpcUa(variable, device);
}
else
{
// 如果变量已存在,则更新其信息。
_opcUaVariables[variable.Id] = variable;
// 如果终结点 URL 对应的会话已断开,则尝试重新连接。
- if (_opcUaSessions.ContainsKey(variable.OpcUaEndpointUrl) && !_opcUaSessions[variable.OpcUaEndpointUrl].Connected)
+ if (_opcUaSessions.ContainsKey(device.OpcUaEndpointUrl) && !_opcUaSessions[device.OpcUaEndpointUrl].Connected)
{
- await ConnectAndSubscribeOpcUa(variable);
+ await ConnectAndSubscribeOpcUa(variable, device);
}
}
}
@@ -183,10 +196,11 @@ namespace PMSWPF.Services
/// 连接到 OPC UA 服务器并订阅指定的变量。
///
/// 要订阅的变量信息。
- private async Task ConnectAndSubscribeOpcUa(VariableData variable)
+ /// 变量所属的设备信息。
+ private async Task ConnectAndSubscribeOpcUa(VariableData variable, Device device)
{
NlogHelper.Info($"正在为变量 '{variable.Name}' 连接和订阅 OPC UA 服务器...");
- if (string.IsNullOrEmpty(variable.OpcUaEndpointUrl) || string.IsNullOrEmpty(variable.OpcUaNodeId))
+ if (string.IsNullOrEmpty(device.OpcUaEndpointUrl) || string.IsNullOrEmpty(variable.OpcUaNodeId))
{
NlogHelper.Warn($"OPC UA variable {variable.Name} has invalid EndpointUrl or NodeId.");
return;
@@ -194,9 +208,9 @@ namespace PMSWPF.Services
Session session = null;
// 检查是否已存在到该终结点的活动会话。
- if (_opcUaSessions.TryGetValue(variable.OpcUaEndpointUrl, out session) && session.Connected)
+ if (_opcUaSessions.TryGetValue(device.OpcUaEndpointUrl, out session) && session.Connected)
{
- NlogHelper.Info($"Already connected to OPC UA endpoint: {variable.OpcUaEndpointUrl}");
+ NlogHelper.Info($"Already connected to OPC UA endpoint: {device.OpcUaEndpointUrl}");
}
else
{
@@ -221,12 +235,12 @@ namespace PMSWPF.Services
}
// 4. 发现服务器提供的终结点。
- DiscoveryClient discoveryClient = DiscoveryClient.Create(new Uri(variable.OpcUaEndpointUrl));
- EndpointDescriptionCollection endpoints = discoveryClient.GetEndpoints(new Opc.Ua.StringCollection { variable.OpcUaEndpointUrl });
+ 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, variable.OpcUaEndpointUrl, false);
+ EndpointDescription selectedEndpoint = CoreClientUtils.SelectEndpoint(application.ApplicationConfiguration, device.OpcUaEndpointUrl, false);
EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(application.ApplicationConfiguration);
ConfiguredEndpoint configuredEndpoint = new ConfiguredEndpoint(null, selectedEndpoint, endpointConfiguration);
@@ -240,9 +254,9 @@ namespace PMSWPF.Services
new UserIdentity(new AnonymousIdentityToken()), // 使用匿名用户身份
null);
- _opcUaSessions[variable.OpcUaEndpointUrl] = session;
- NlogHelper.Info($"Connected to OPC UA server: {variable.OpcUaEndpointUrl}");
- NotificationHelper.ShowSuccess($"已连接到 OPC UA 服务器: {variable.OpcUaEndpointUrl}");
+ _opcUaSessions[device.OpcUaEndpointUrl] = session;
+ NlogHelper.Info($"Connected to OPC UA server: {device.OpcUaEndpointUrl}");
+ NotificationHelper.ShowSuccess($"已连接到 OPC UA 服务器: {device.OpcUaEndpointUrl}");
// 6. 创建订阅。
Subscription subscription = new Subscription(session.DefaultSubscription);
@@ -250,7 +264,7 @@ namespace PMSWPF.Services
session.AddSubscription(subscription);
subscription.Create();
- _opcUaSubscriptions[variable.OpcUaEndpointUrl] = subscription;
+ _opcUaSubscriptions[device.OpcUaEndpointUrl] = subscription;
// 7. 创建监控项并添加到订阅中。
MonitoredItem monitoredItem = new MonitoredItem(subscription.DefaultItem);
@@ -268,8 +282,8 @@ namespace PMSWPF.Services
}
catch (Exception ex)
{
- NlogHelper.Error($"连接或订阅 OPC UA 服务器失败: {variable.OpcUaEndpointUrl} - {ex.Message}", ex);
- NotificationHelper.ShowError($"连接或订阅 OPC UA 服务器失败: {variable.OpcUaEndpointUrl} - {ex.Message}", ex);
+ NlogHelper.Error($"连接或订阅 OPC UA 服务器失败: {device.OpcUaEndpointUrl} - {ex.Message}", ex);
+ NotificationHelper.ShowError($"连接或订阅 OPC UA 服务器失败: {device.OpcUaEndpointUrl} - {ex.Message}", ex);
}
}
}