feat: 为多个服务添加日志记录功能并改进批量导入变量处理

- 为OpcUaServiceManager添加日志记录功能,并实现OnBatchImportVariables事件处理器
  - 为OpcUaService添加全面的日志记录,包括连接、断开、读写节点等操作
  - 为DataEventService添加日志记录功能,跟踪数据加载和变量值变更事件
  - 为S7ServiceManager添加OnBatchImportVariables事件处理器,处理批量导入变量事件
  - 在VariableTableViewModel中改进变量添加逻辑,确保变量被正确添加到数据存储中
  - 在VariableManagementService中完善变量表信息的填充逻辑
This commit is contained in:
2025-10-02 11:26:50 +08:00
parent 078d7b3903
commit 6e9f7c4a44
6 changed files with 350 additions and 20 deletions

View File

@@ -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<OpcUaService> _logger;
private string? _serverUrl;
private Session? _session;
private Subscription? _subscription;
private readonly Dictionary<NodeId, OpcUaNode> _subscribedNodes = new();
public OpcUaService(ILogger<OpcUaService> 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<OpcUaNode>();
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<OpcUaNode> nodes)
{
if (references == null)
{
_logger?.LogDebug("浏览结果为null");
return;
}
_logger?.LogDebug("正在处理 {Count} 个浏览结果", references.Count);
// 收集所有变量节点,用于批量读取数据类型
var variableNodes = new List<OpcUaNode>();
@@ -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<OpcUaNode> onDataChange, int publishingInterval = 1000, int samplingInterval = 500)
{
_logger?.LogDebug("正在订阅单个节点: {NodeId} ({DisplayName}),发布间隔: {PublishingInterval}ms采样间隔: {SamplingInterval}ms",
node.NodeId, node.DisplayName, publishingInterval, samplingInterval);
SubscribeToNode(new List<OpcUaNode> { node }, onDataChange, publishingInterval, samplingInterval);
}
public void SubscribeToNode(List<OpcUaNode> nodes, Action<OpcUaNode> 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<OpcUaNode> { node });
}
public void UnsubscribeFromNode(List<OpcUaNode> 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<OpcUaNode> 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<OpcUaNode> { node });
}
public async Task ReadNodeValuesAsync(List<OpcUaNode> 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<bool> WriteNodeValueAsync(OpcUaNode node, object value)
{
_logger?.LogDebug("正在写入单个节点的值: {NodeId} ({DisplayName}), 值: {Value}",
node.NodeId, node.DisplayName, value);
var nodesToWrite = new Dictionary<OpcUaNode, object> { { node, value } };
return WriteNodeValuesAsync(nodesToWrite);
}
public async Task<bool> WriteNodeValuesAsync(Dictionary<OpcUaNode, object> 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;
}
}

View File

@@ -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
}
}
/// <summary>
/// 处理批量导入变量事件
/// </summary>
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, "处理批量导入变量事件时发生错误");
}
}
/// <summary>
/// 初始化服务管理器
/// </summary>
@@ -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);
}
}