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); } } }