feat: 为多个服务添加日志记录功能并改进批量导入变量处理
- 为OpcUaServiceManager添加日志记录功能,并实现OnBatchImportVariables事件处理器 - 为OpcUaService添加全面的日志记录,包括连接、断开、读写节点等操作 - 为DataEventService添加日志记录功能,跟踪数据加载和变量值变更事件 - 为S7ServiceManager添加OnBatchImportVariables事件处理器,处理批量导入变量事件 - 在VariableTableViewModel中改进变量添加逻辑,确保变量被正确添加到数据存储中 - 在VariableManagementService中完善变量表信息的填充逻辑
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user