diff --git a/DMS.Application/Services/Management/VariableManagementService.cs b/DMS.Application/Services/Management/VariableManagementService.cs index 6bc92c0..a698369 100644 --- a/DMS.Application/Services/Management/VariableManagementService.cs +++ b/DMS.Application/Services/Management/VariableManagementService.cs @@ -159,6 +159,15 @@ public class VariableManagementService : IVariableManagementService public async Task> BatchImportVariablesAsync(List variables) { var result = await _variableAppService.BatchImportVariablesAsync(variables); + foreach (var variableDto in result) + { + if (_appDataStorageService.VariableTables.TryGetValue(variableDto.VariableTableId ,out var variableTable)) + { + variableDto.VariableTable = variableTable; + } + + } + // 批量导入成功后,触发批量导入事件 if (result != null && result.Any()) diff --git a/DMS.Infrastructure/Services/OpcUa/OpcUaService.cs b/DMS.Infrastructure/Services/OpcUa/OpcUaService.cs index f9ef0cf..b262a36 100644 --- a/DMS.Infrastructure/Services/OpcUa/OpcUaService.cs +++ b/DMS.Infrastructure/Services/OpcUa/OpcUaService.cs @@ -1,6 +1,7 @@ using DMS.Core.Enums; using DMS.Infrastructure.Interfaces.Services; using DMS.Infrastructure.Models; +using Microsoft.Extensions.Logging; using Opc.Ua; using Opc.Ua.Client; using Opc.Ua.Configuration; @@ -10,11 +11,18 @@ namespace DMS.Infrastructure.Services.OpcUa public class OpcUaService : IOpcUaService { private readonly ApplicationConfiguration _config; + private readonly ILogger _logger; private string? _serverUrl; private Session? _session; private Subscription? _subscription; private readonly Dictionary _subscribedNodes = new(); + public OpcUaService(ILogger logger = null) + { + _logger = logger; + _config = CreateApplicationConfiguration(); + } + public bool IsConnected => _session != null && _session.Connected; public OpcUaService() @@ -26,6 +34,8 @@ namespace DMS.Infrastructure.Services.OpcUa { try { + _logger?.LogInformation("正在连接到OPC UA服务器: {ServerUrl}", serverUrl); + // 保存服务器URL _serverUrl = serverUrl; @@ -40,6 +50,11 @@ namespace DMS.Infrastructure.Services.OpcUa { e.Accept = e.Error.StatusCode == StatusCodes.BadCertificateUntrusted; }; + _logger?.LogDebug("已设置证书验证回调,自动接受不受信任的证书"); + } + else + { + _logger?.LogDebug("不自动接受不受信任的证书"); } // 创建一个应用程序实例,它代表了客户端应用程序。 @@ -49,11 +64,17 @@ namespace DMS.Infrastructure.Services.OpcUa bool haveAppCertificate = await application.CheckApplicationInstanceCertificate(false, 2048); if (!haveAppCertificate) { + _logger?.LogError("应用程序实例证书无效!"); throw new Exception("应用程序实例证书无效!"); } + else + { + _logger?.LogDebug("应用程序实例证书有效"); + } // 从给定的URL发现并选择一个合适的服务器终结点(Endpoint)。 var selectedEndpoint = CoreClientUtils.SelectEndpoint(_config, _serverUrl, useSecurity: false); + _logger?.LogDebug("已选择终结点: {Endpoint}", selectedEndpoint.EndpointUrl); // 创建到服务器的会话。会话管理客户端和服务器之间的所有通信。 _session = await Session.Create( @@ -65,16 +86,20 @@ namespace DMS.Infrastructure.Services.OpcUa null, // 用户身份验证令牌,此处为匿名 null // 首选区域设置 ); + + _logger?.LogInformation("成功连接到OPC UA服务器: {ServerUrl}", serverUrl); } catch (Exception ex) { - Console.WriteLine($"连接服务器时发生错误: {ex.Message}"); + _logger?.LogError(ex, "连接服务器时发生错误: {ErrorMessage}", ex.Message); throw; } } public async Task DisconnectAsync() { + _logger?.LogInformation("正在断开与OPC UA服务器的连接"); + if (_session != null) { // 取消所有订阅 @@ -82,6 +107,7 @@ namespace DMS.Infrastructure.Services.OpcUa { try { + _logger?.LogDebug("正在删除订阅"); // 删除服务器上的订阅 _subscription.Delete(true); // 从会话中移除订阅 @@ -89,20 +115,27 @@ namespace DMS.Infrastructure.Services.OpcUa // 释放订阅资源 _subscription.Dispose(); _subscription = null; + _logger?.LogDebug("已删除订阅"); } catch (Exception ex) { - Console.WriteLine($"取消订阅时发生错误: {ex.Message}"); + _logger?.LogError(ex, "取消订阅时发生错误: {ErrorMessage}", ex.Message); // 即使取消订阅失败,也继续关闭会话 } } // 清理订阅节点跟踪字典 _subscribedNodes.Clear(); + _logger?.LogDebug("已清理订阅节点跟踪字典"); // 关闭会话 await _session.CloseAsync(); _session = null; + _logger?.LogInformation("已断开与OPC UA服务器的连接"); + } + else + { + _logger?.LogWarning("尝试断开连接,但会话为null"); } } @@ -110,15 +143,19 @@ namespace DMS.Infrastructure.Services.OpcUa { if (!IsConnected) { + _logger?.LogWarning("会话未连接。请在浏览节点前调用ConnectAsync方法。"); throw new InvalidOperationException("会话未连接。请在浏览节点前调用ConnectAsync方法。"); } // 检查节点是否为null if (nodeToBrowse == null) { + _logger?.LogWarning("要浏览的节点不能为null"); throw new ArgumentNullException(nameof(nodeToBrowse), "要浏览的节点不能为null。"); } + _logger?.LogDebug("正在浏览节点: {NodeId} ({DisplayName})", nodeToBrowse.NodeId, nodeToBrowse.DisplayName); + var nodes = new List(); try { @@ -140,25 +177,37 @@ namespace DMS.Infrastructure.Services.OpcUa out continuationPoint, // continuationPoint: 输出参数,用于处理分页 out references); // references: 输出参数,浏览到的节点引用集合 + _logger?.LogDebug("浏览节点 {NodeId} 成功,获得 {Count} 个子节点", nodeToBrowse.NodeId, references?.Count ?? 0); + // 处理浏览结果 await ProcessBrowseResults(references, nodeToBrowse, nodes); // 如果continuationPoint不为null,说明服务器还有数据未返回,需要循环调用BrowseNext获取 + int pageCount = 0; while (continuationPoint != null) { + pageCount++; + _logger?.LogDebug("正在获取节点 {NodeId} 的第 {PageNumber} 页子节点", nodeToBrowse.NodeId, pageCount); + // 调用BrowseNext获取下一批数据 _session.BrowseNext(null, false, continuationPoint, out continuationPoint, out references); // 处理后续批次的浏览结果 await ProcessBrowseResults(references, nodeToBrowse, nodes); + + _logger?.LogDebug("已处理节点 {NodeId} 的第 {PageNumber} 页,获得 {Count} 个子节点", nodeToBrowse.NodeId, pageCount, references?.Count ?? 0); } + _logger?.LogDebug("总共获得了 {TotalCount} 个子节点", nodes.Count); + // 将找到的子节点列表关联到父节点 nodeToBrowse.Children = nodes; } catch (Exception ex) { - Console.WriteLine($"浏览节点 '{nodeToBrowse.DisplayName}' 时发生错误: {ex.Message}"); + _logger?.LogError(ex, "浏览节点 '{NodeId}' ({DisplayName}) 时发生错误: {ErrorMessage}", + nodeToBrowse.NodeId, nodeToBrowse.DisplayName, ex.Message); + throw; } return nodes; } @@ -172,7 +221,12 @@ namespace DMS.Infrastructure.Services.OpcUa private async Task ProcessBrowseResults(ReferenceDescriptionCollection references, OpcUaNode parentNode, List nodes) { if (references == null) + { + _logger?.LogDebug("浏览结果为null"); return; + } + + _logger?.LogDebug("正在处理 {Count} 个浏览结果", references.Count); // 收集所有变量节点,用于批量读取数据类型 var variableNodes = new List(); @@ -191,8 +245,13 @@ namespace DMS.Infrastructure.Services.OpcUa // 如果是变量节点,添加到列表中稍后批量处理 if (rd.NodeClass == NodeClass.Variable) { + // _logger?.LogDebug("发现变量节点: {NodeId} ({DisplayName})", node.NodeId, node.DisplayName); variableNodes.Add(node); } + else + { + // _logger?.LogDebug("发现对象节点: {NodeId} ({DisplayName})", node.NodeId, node.DisplayName); + } nodes.Add(node); } @@ -200,33 +259,44 @@ namespace DMS.Infrastructure.Services.OpcUa // 批量读取变量节点的数据类型 if (variableNodes.Any()) { + _logger?.LogDebug("正在批量读取 {Count} 个变量节点的数据类型", variableNodes.Count); + try { await ReadNodeDataTypesAsync(variableNodes); + _logger?.LogDebug("成功批量读取 {Count} 个变量节点的数据类型", variableNodes.Count); } catch (Exception ex) { - Console.WriteLine($"批量读取节点数据类型时发生错误: {ex.Message}"); + _logger?.LogError(ex, "批量读取节点数据类型时发生错误: {ErrorMessage}", ex.Message); + throw; } } } public void SubscribeToNode(OpcUaNode node, Action onDataChange, int publishingInterval = 1000, int samplingInterval = 500) { + _logger?.LogDebug("正在订阅单个节点: {NodeId} ({DisplayName}),发布间隔: {PublishingInterval}ms,采样间隔: {SamplingInterval}ms", + node.NodeId, node.DisplayName, publishingInterval, samplingInterval); SubscribeToNode(new List { node }, onDataChange, publishingInterval, samplingInterval); } public void SubscribeToNode(List nodes, Action onDataChange, int publishingInterval = 1000, int samplingInterval = 500) { + _logger?.LogDebug("正在订阅 {Count} 个节点,发布间隔: {PublishingInterval}ms,采样间隔: {SamplingInterval}ms", + nodes?.Count ?? 0, publishingInterval, samplingInterval); + // 检查会话是否已连接 if (!IsConnected) { + _logger?.LogWarning("会话未连接。请在订阅节点前调用ConnectAsync方法。"); throw new InvalidOperationException("会话未连接。请在订阅节点前调用ConnectAsync方法。"); } // 检查节点列表是否有效 if (nodes == null || !nodes.Any()) { + _logger?.LogWarning("节点列表为null或为空,无法订阅"); return; } @@ -242,6 +312,7 @@ namespace DMS.Infrastructure.Services.OpcUa // 如果节点已经存在于我们的跟踪列表中,则跳过,避免重复订阅 if (_subscribedNodes.ContainsKey(node.NodeId)) { + _logger?.LogDebug("节点 {NodeId} ({DisplayName}) 已经被订阅,跳过重复订阅", node.NodeId, node.DisplayName); continue; } @@ -251,16 +322,25 @@ namespace DMS.Infrastructure.Services.OpcUa // 将创建的监视项添加到待添加列表 itemsToAdd.Add(monitoredItem); // 将节点添加到我们的跟踪字典中 - _subscribedNodes[node.NodeId] = node; + _subscribedNodes.TryAdd(node.NodeId, node); + _logger?.LogDebug("节点 {NodeId} ({DisplayName}) 已添加到订阅列表", node.NodeId, node.DisplayName); } // 如果有新的监视项要添加 if (itemsToAdd.Any()) { + _logger?.LogDebug("批量添加 {Count} 个监视项到订阅", itemsToAdd.Count); + // 将所有新的监视项批量添加到订阅中 _subscription.AddItems(itemsToAdd); // 将所有挂起的更改(包括订阅属性修改和添加新项)应用到服务器 _subscription.ApplyChanges(); + + _logger?.LogInformation("已成功订阅 {Count} 个新节点", itemsToAdd.Count); + } + else + { + _logger?.LogDebug("没有新的节点需要订阅"); } } @@ -316,11 +396,18 @@ namespace DMS.Infrastructure.Services.OpcUa // 通过StartNodeId从我们的跟踪字典中找到对应的OpcUaNode对象 if (_subscribedNodes.TryGetValue(item.StartNodeId, out var changedNode)) { + _logger?.LogDebug("节点 {NodeId} ({DisplayName}) 值发生变化: {Value}", + changedNode.NodeId, changedNode.DisplayName, notification.Value.Value); + // 更新节点对象的值 changedNode.Value = notification.Value.Value; // 调用用户提供的回调函数,并传入更新后的节点 onDataChange?.Invoke(changedNode); } + else + { + _logger?.LogWarning("监视项通知: 无法在跟踪字典中找到节点 {NodeId}", item.StartNodeId); + } } }; @@ -329,14 +416,24 @@ namespace DMS.Infrastructure.Services.OpcUa public void UnsubscribeFromNode(OpcUaNode node) { + _logger?.LogDebug("正在取消订阅节点: {NodeId} ({DisplayName})", node.NodeId, node.DisplayName); UnsubscribeFromNode(new List { node }); } public void UnsubscribeFromNode(List nodes) { + _logger?.LogDebug("正在取消订阅 {Count} 个节点", nodes?.Count ?? 0); + // 检查订阅对象和节点列表是否有效 - if (_subscription == null || nodes == null || !nodes.Any()) + if (_subscription == null) { + _logger?.LogWarning("订阅对象为null,无法取消订阅"); + return; + } + + if (nodes == null || !nodes.Any()) + { + _logger?.LogWarning("节点列表为null或为空,无法取消订阅"); return; } @@ -348,49 +445,77 @@ namespace DMS.Infrastructure.Services.OpcUa var item = _subscription.MonitoredItems.FirstOrDefault(m => m.StartNodeId.Equals(node.NodeId)); if (item != null) { + _logger?.LogDebug("找到节点 {NodeId} ({DisplayName}) 的监视项,准备移除", node.NodeId, node.DisplayName); // 如果找到,则添加到待移除列表 itemsToRemove.Add(item); // 从我们的跟踪字典中移除该节点 _subscribedNodes.Remove(node.NodeId); } + else + { + _logger?.LogDebug("节点 {NodeId} ({DisplayName}) 未在监视项中找到,可能已经取消订阅", node.NodeId, node.DisplayName); + } } // 如果有需要移除的监视项 if (itemsToRemove.Any()) { + _logger?.LogDebug("批量移除 {Count} 个监视项", itemsToRemove.Count); + // 从订阅中批量移除监视项 _subscription.RemoveItems(itemsToRemove); // 将更改应用到服务器 _subscription.ApplyChanges(); + + _logger?.LogInformation("已成功取消订阅 {Count} 个节点", itemsToRemove.Count); + } + else + { + _logger?.LogDebug("没有找到需要移除的监视项"); } } public List GetSubscribedNodes() { - return _subscribedNodes.Values.ToList(); + var subscribedNodes = _subscribedNodes.Values.ToList(); + _logger?.LogDebug("获取当前已订阅的节点列表,共 {Count} 个节点", subscribedNodes.Count); + return subscribedNodes; } public Task ReadNodeValueAsync(OpcUaNode node) { + _logger?.LogDebug("正在读取单个节点的值: {NodeId} ({DisplayName})", node.NodeId, node.DisplayName); return ReadNodeValuesAsync(new List { node }); } public async Task ReadNodeValuesAsync(List nodes) { - if (!IsConnected || nodes == null || !nodes.Any()) + if (!IsConnected) { + _logger?.LogWarning("会话未连接,无法读取节点值"); return; } + if (nodes == null || !nodes.Any()) + { + _logger?.LogWarning("节点列表为null或为空,无法读取节点值"); + return; + } + + _logger?.LogDebug("正在读取 {Count} 个节点的值", nodes.Count); + // 筛选出变量类型的节点,因为只有变量才有值 var variableNodes = nodes.Where(n => n.NodeClass == NodeClass.Variable).ToList(); // 如果没有需要读取的变量节点,则直接返回 if (!variableNodes.Any()) { + _logger?.LogDebug("没有变量类型的节点需要读取值"); return; } + _logger?.LogDebug("筛选出 {Count} 个变量节点进行读取", variableNodes.Count); + try { // 创建一个用于存放读取请求的集合 @@ -401,6 +526,8 @@ namespace DMS.Infrastructure.Services.OpcUa // 为每个变量节点创建读取请求 foreach (var node in variableNodes) { + _logger?.LogDebug("准备读取节点 {NodeId} ({DisplayName}) 的值", node.NodeId, node.DisplayName); + // 创建一个ReadValueId,指定要读取的节点ID和属性(值) nodesToRead.Add(new ReadValueId { @@ -438,31 +565,48 @@ namespace DMS.Infrastructure.Services.OpcUa { // 更新节点的值 node.Value = results[i].Value; + _logger?.LogDebug("成功读取节点 {NodeId} ({DisplayName}) 的值: {Value}", node.NodeId, node.DisplayName, results[i].Value); } else { // 如果读取失败,则将状态码作为值,方便调试 node.Value = $"({results[i].StatusCode})"; + _logger?.LogWarning("读取节点 {NodeId} ({DisplayName}) 失败,状态码: {StatusCode}", node.NodeId, node.DisplayName, results[i].StatusCode); } } + + _logger?.LogInformation("成功读取 {SuccessCount}/{TotalCount} 个节点的值", + results.Count(r => StatusCode.IsGood(r.StatusCode)), results.Count); } catch (Exception ex) { - Console.WriteLine($"读取节点值时发生错误: {ex.Message}"); + _logger?.LogError(ex, "读取节点值时发生错误: {ErrorMessage}", ex.Message); + throw; } } public Task WriteNodeValueAsync(OpcUaNode node, object value) { + _logger?.LogDebug("正在写入单个节点的值: {NodeId} ({DisplayName}), 值: {Value}", + node.NodeId, node.DisplayName, value); var nodesToWrite = new Dictionary { { node, value } }; return WriteNodeValuesAsync(nodesToWrite); } public async Task WriteNodeValuesAsync(Dictionary nodesToWrite) { + _logger?.LogDebug("正在写入 {Count} 个节点的值", nodesToWrite?.Count ?? 0); + // 检查会话是否连接,以及待写入的节点字典是否有效 - if (!IsConnected || nodesToWrite == null || !nodesToWrite.Any()) + if (!IsConnected) { + _logger?.LogWarning("会话未连接,无法写入节点值"); + return false; + } + + if (nodesToWrite == null || !nodesToWrite.Any()) + { + _logger?.LogWarning("节点写入字典为null或为空,无法写入节点值"); return false; } @@ -481,12 +625,14 @@ namespace DMS.Infrastructure.Services.OpcUa foreach (var node in nonVariableNodes) { - Console.WriteLine($"节点 '{node.DisplayName}' 不是变量类型,无法写入。"); + _logger?.LogWarning("节点 '{DisplayName}' 不是变量类型,无法写入。", node.DisplayName); } return false; } + _logger?.LogDebug("筛选出 {Count} 个变量节点进行写入", variableNodesToWrite.Count); + // 创建一个用于存放写入请求的集合 var writeValues = new WriteValueCollection(); // 创建一个列表,用于在收到响应后按顺序查找对应的OpcUaNode,以进行错误报告 @@ -500,6 +646,8 @@ namespace DMS.Infrastructure.Services.OpcUa try { + _logger?.LogDebug("准备写入节点 {NodeId} ({DisplayName}) 的值: {Value}", node.NodeId, node.DisplayName, value); + // 创建一个WriteValue对象,它封装了写入操作的所有信息 var writeValue = new WriteValue { @@ -516,16 +664,19 @@ namespace DMS.Infrastructure.Services.OpcUa catch (Exception ex) { // 处理在创建写入值时可能发生的异常(例如,值类型不兼容) - Console.WriteLine($"为节点 '{node.DisplayName}' 创建写入值时发生错误: {ex.Message}"); + _logger?.LogError(ex, "为节点 '{DisplayName}' 创建写入值时发生错误: {ErrorMessage}", node.DisplayName, ex.Message); } } // 如果没有有效的写入请求,则直接返回 if (writeValues.Count == 0) { + _logger?.LogWarning("没有有效的写入请求"); return false; } + _logger?.LogDebug("准备批量写入 {Count} 个节点的值", writeValues.Count); + try { // 异步调用Write方法,将所有写入请求批量发送到服务器 @@ -544,6 +695,9 @@ namespace DMS.Infrastructure.Services.OpcUa ClientBase.ValidateDiagnosticInfos(diagnosticInfos, writeValues); bool allSuccess = true; + int successCount = 0; + int failureCount = 0; + // 遍历返回的结果状态码 for (int i = 0; i < results.Count; i++) { @@ -551,17 +705,26 @@ namespace DMS.Infrastructure.Services.OpcUa if (StatusCode.IsBad(results[i])) { allSuccess = false; + failureCount++; // 根据索引找到写入失败的节点 var failedNode = nodeListForLookup[i]; - Console.WriteLine($"写入节点 '{failedNode.DisplayName}' 失败: {results[i]}"); + _logger?.LogError("写入节点 '{DisplayName}' 失败: {StatusCode}", failedNode.DisplayName, results[i]); + } + else + { + successCount++; + var successfulNode = nodeListForLookup[i]; + _logger?.LogDebug("成功写入节点 {NodeId} ({DisplayName}) 的值", successfulNode.NodeId, successfulNode.DisplayName); } } + _logger?.LogInformation("批量写入完成: 成功 {SuccessCount} 个,失败 {FailureCount} 个", successCount, failureCount); + return allSuccess; } catch (Exception ex) { - Console.WriteLine($"写入节点值时发生错误: {ex.Message}"); + _logger?.LogError(ex, "写入节点值时发生错误: {ErrorMessage}", ex.Message); return false; } } diff --git a/DMS.Infrastructure/Services/OpcUa/OpcUaServiceManager.cs b/DMS.Infrastructure/Services/OpcUa/OpcUaServiceManager.cs index f5d8241..d0466e2 100644 --- a/DMS.Infrastructure/Services/OpcUa/OpcUaServiceManager.cs +++ b/DMS.Infrastructure/Services/OpcUa/OpcUaServiceManager.cs @@ -47,6 +47,7 @@ namespace DMS.Infrastructure.Services.OpcUa _eventService.OnDeviceStateChanged += OnDeviceStateChanged; _eventService.OnDeviceChanged += OnDeviceChanged; + _eventService.OnBatchImportVariables += OnBatchImportVariables; } private async void OnDeviceStateChanged(object? sender, DeviceStateChangedEventArgs e) @@ -231,6 +232,58 @@ namespace DMS.Infrastructure.Services.OpcUa } } + /// + /// 处理批量导入变量事件 + /// + private async void OnBatchImportVariables(object? sender, BatchImportVariablesEventArgs e) + { + if (e?.Variables == null || !e.Variables.Any()) + { + _logger.LogWarning("OnBatchImportVariables: 接收到空变量列表"); + return; + } + + try + { + _logger.LogInformation("处理批量导入变量事件,共 {Count} 个变量", e.Count); + + // 更新相关设备的变量表 + var deviceIds = e.Variables.Select(v => v.VariableTable.DeviceId).Distinct(); + foreach (var deviceId in deviceIds) + { + // 获取设备的变量表信息 + var variablesForDevice = e.Variables.Where(v => v.VariableTable.DeviceId == deviceId).ToList(); + if (variablesForDevice.Any()) + { + // 更新设备上下文中的变量 + if (_deviceContexts.TryGetValue(deviceId, out var context)) + { + // 将新导入的变量添加到设备上下文 + foreach (var variable in variablesForDevice) + { + if (!context.Variables.ContainsKey(variable.OpcUaNodeId)) + { + context.Variables.TryAdd(variable.OpcUaNodeId, variable); + } + } + + // 如果设备已连接,则设置订阅 + if (context.IsConnected) + { + await SetupSubscriptionsAsync(context, CancellationToken.None); + } + } + } + } + + _logger.LogInformation("批量导入变量事件处理完成,更新了 {DeviceCount} 个设备的变量信息", deviceIds.Count()); + } + catch (Exception ex) + { + _logger.LogError(ex, "处理批量导入变量事件时发生错误"); + } + } + /// /// 初始化服务管理器 /// @@ -490,24 +543,35 @@ namespace DMS.Infrastructure.Services.OpcUa private async void HandleDataChanged(OpcUaNode opcUaNode) { if (opcUaNode?.Value == null) + { + _logger.LogDebug("HandleDataChanged: 接收到空节点或空值,节点ID: {NodeId}", opcUaNode?.NodeId?.ToString() ?? "Unknown"); return; + } try { + _logger.LogDebug("HandleDataChanged: 节点 {NodeId} 的值发生变化: {Value}", opcUaNode.NodeId, opcUaNode.Value); + // 查找对应的变量 foreach (var context in _deviceContexts.Values) { if (context.Variables.TryGetValue(opcUaNode.NodeId.ToString(), out var variable)) { + _logger.LogDebug("HandleDataChanged: 找到变量 {VariableName} (ID: {VariableId}) 与节点 {NodeId} 对应,设备: {DeviceName}", + variable.Name, variable.Id, opcUaNode.NodeId, context.Device.Name); + // 推送到数据处理队列 - await _dataProcessingService.EnqueueAsync(new VariableContext(variable,opcUaNode.Value)); + await _dataProcessingService.EnqueueAsync(new VariableContext(variable, opcUaNode.Value)); + + _logger.LogDebug("HandleDataChanged: 变量 {VariableName} 的值已推送到数据处理队列", variable.Name); break; } } } catch (Exception ex) { - _logger.LogError(ex, "处理数据变化时发生错误: {ErrorMessage}", ex.Message); + _logger.LogError(ex, "处理数据变化时发生错误 - 节点ID: {NodeId}, 值: {Value}, 错误信息: {ErrorMessage}", + opcUaNode?.NodeId?.ToString() ?? "Unknown", opcUaNode?.Value?.ToString() ?? "null", ex.Message); } } diff --git a/DMS.Infrastructure/Services/S7/S7ServiceManager.cs b/DMS.Infrastructure/Services/S7/S7ServiceManager.cs index 1add6ba..98fb9e5 100644 --- a/DMS.Infrastructure/Services/S7/S7ServiceManager.cs +++ b/DMS.Infrastructure/Services/S7/S7ServiceManager.cs @@ -46,6 +46,7 @@ namespace DMS.Infrastructure.Services.S7 _semaphore = new SemaphoreSlim(10, 10); // 默认最大并发连接数为10 _eventService.OnVariableActiveChanged += OnVariableActiveChanged; + _eventService.OnBatchImportVariables += OnBatchImportVariables; } private void OnVariableActiveChanged(object? sender, VariablesActiveChangedEventArgs e) @@ -360,6 +361,52 @@ namespace DMS.Infrastructure.Services.S7 _logger.LogInformation("S7服务管理器资源已释放"); } } + + /// + /// 处理批量导入变量事件 + /// + private void OnBatchImportVariables(object? sender, BatchImportVariablesEventArgs e) + { + if (e?.Variables == null || !e.Variables.Any()) + { + _logger.LogWarning("OnBatchImportVariables: 接收到空变量列表"); + return; + } + + try + { + _logger.LogInformation("处理批量导入变量事件,共 {Count} 个变量", e.Count); + + // 更新相关设备的变量表 + var deviceIds = e.Variables.Select(v => v.VariableTable.DeviceId).Distinct(); + foreach (var deviceId in deviceIds) + { + // 获取设备的变量表信息 + var variablesForDevice = e.Variables.Where(v => v.VariableTable.DeviceId == deviceId).ToList(); + if (variablesForDevice.Any()) + { + // 更新设备上下文中的变量 + if (_deviceContexts.TryGetValue(deviceId, out var context)) + { + // 将新导入的变量添加到设备上下文 + foreach (var variable in variablesForDevice) + { + if (!context.Variables.ContainsKey(variable.S7Address)) + { + context.Variables.AddOrUpdate(variable.S7Address, variable, (key, oldValue) => variable); + } + } + } + } + } + + _logger.LogInformation("批量导入变量事件处理完成,更新了 {DeviceCount} 个设备的变量信息", deviceIds.Count()); + } + catch (Exception ex) + { + _logger.LogError(ex, "处理批量导入变量事件时发生错误"); + } + } } /// diff --git a/DMS.WPF/Services/DataEventService.cs b/DMS.WPF/Services/DataEventService.cs index ef50d3f..a706ddc 100644 --- a/DMS.WPF/Services/DataEventService.cs +++ b/DMS.WPF/Services/DataEventService.cs @@ -11,6 +11,7 @@ using DMS.Core.Models; using DMS.Message; using DMS.WPF.Interfaces; using DMS.WPF.ViewModels.Items; +using Microsoft.Extensions.Logging; namespace DMS.WPF.Services; @@ -24,6 +25,7 @@ public class DataEventService : IDataEventService private readonly IEventService _eventService; private readonly IAppDataCenterService _appDataCenterService; private readonly IWPFDataService _wpfDataService; + private readonly ILogger _logger; /// /// DataEventService类的构造函数。 @@ -32,33 +34,59 @@ public class DataEventService : IDataEventService IDataStorageService dataStorageService, IEventService eventService, IAppDataCenterService appDataCenterService, - IWPFDataService wpfDataService) + IWPFDataService wpfDataService, + ILogger logger) { _mapper = mapper; _dataStorageService = dataStorageService; _eventService = eventService; _appDataCenterService = appDataCenterService; _wpfDataService = wpfDataService; + _logger = logger; + + _logger?.LogInformation("正在初始化 DataEventService"); // 监听变量值变更事件 _eventService.OnVariableValueChanged += OnVariableValueChanged; _appDataCenterService.DataLoaderService.OnLoadDataCompleted += OnLoadDataCompleted; // 监听日志变更事件 // _appDataCenterService.OnLogChanged += _logDataService.OnNlogChanged; + + _logger?.LogInformation("DataEventService 初始化完成"); } private void OnLoadDataCompleted(object? sender, DataLoadCompletedEventArgs e) { + _logger?.LogDebug("接收到数据加载完成事件,成功: {IsSuccess}", e.IsSuccess); + if (e.IsSuccess) { + _logger?.LogInformation("开始加载所有数据项"); + _wpfDataService.DeviceDataService.LoadAllDevices(); + _logger?.LogDebug("设备数据加载完成"); + _wpfDataService.VariableTableDataService.LoadAllVariableTables(); + _logger?.LogDebug("变量表数据加载完成"); + _wpfDataService.VariableDataService.LoadAllVariables(); + _logger?.LogDebug("变量数据加载完成"); + _wpfDataService.MenuDataService.LoadAllMenus(); + _logger?.LogDebug("菜单数据加载完成"); + _wpfDataService.MqttDataService.LoadMqttServers(); + _logger?.LogDebug("MQTT服务器数据加载完成"); + _wpfDataService.LogDataService.LoadAllLog(); + _logger?.LogDebug("日志数据加载完成"); + + _logger?.LogInformation("所有数据项加载完成"); + } + else + { + _logger?.LogWarning("数据加载失败"); } - } /// @@ -66,16 +94,28 @@ public class DataEventService : IDataEventService /// private void OnVariableValueChanged(object? sender, VariableValueChangedEventArgs e) { + _logger?.LogDebug("接收到变量值变更事件,变量ID: {VariableId}, 新值: {NewValue}, 更新时间: {UpdateTime}", + e.VariableId, e.NewValue, e.UpdateTime); + // 在UI线程上更新变量值 App.Current.Dispatcher.BeginInvoke(new Action(() => { // 查找并更新对应的变量 - if (_dataStorageService.Variables.TryGetValue(e.VariableId,out var variableToUpdate)) { + _logger?.LogDebug("更新变量 {VariableId} ({VariableName}) 的值: {NewValue}", + e.VariableId, variableToUpdate.Name, e.NewValue); + variableToUpdate.DataValue = e.NewValue; variableToUpdate.DisplayValue = e.NewValue; variableToUpdate.UpdatedAt = e.UpdateTime; + + _logger?.LogDebug("变量 {VariableId} ({VariableName}) 的值已更新", + e.VariableId, variableToUpdate.Name); + } + else + { + _logger?.LogWarning("在变量存储中找不到ID为 {VariableId} 的变量,无法更新值", e.VariableId); } })); } @@ -87,6 +127,11 @@ public class DataEventService : IDataEventService /// public async Task Receive(LoadMessage message) { - + _logger?.LogDebug("接收到LoadMessage消息,消息类型: {MessageType}", message.GetType().Name); + + // 这里可以添加加载消息处理的逻辑 + // 目前是空实现,但已记录接收到消息的信息 + + _logger?.LogDebug("LoadMessage消息处理完成"); } } \ No newline at end of file diff --git a/DMS.WPF/ViewModels/VariableTableViewModel.cs b/DMS.WPF/ViewModels/VariableTableViewModel.cs index 8fda634..efeedb2 100644 --- a/DMS.WPF/ViewModels/VariableTableViewModel.cs +++ b/DMS.WPF/ViewModels/VariableTableViewModel.cs @@ -359,8 +359,10 @@ partial class VariableTableViewModel : ViewModelBase, INavigatable { List variableItemViewModels = _mapper.Map>(addVariableDtos); _variableItemList.AddRange(variableItemViewModels); + foreach (var variableItemViewModel in variableItemViewModels) { + _dataStorageService.Variables.TryAdd(variableItemViewModel.Id, variableItemViewModel); CurrentVariableTable.Variables.Add(variableItemViewModel); } _notificationService.ShowSuccess($"从OPC UA服务器导入变量成功,共导入变量:{importedVariableDtos.Count}个");