本次提交对 OPC UA 服务的接口进行了重构,主要变更如下:
1. 简化接口: OpcUaService 的 SubscribeToNode 和 UnsubscribeFromNode 方法的参数类型从 OpcUaNode 对象更改为 string 类型的节点ID。这使得上层服务在调用时无需构造完整的 OpcUaNode 对象,降低了接口的复杂性。 2. 更新实现: OpcUaService 和 OpcUaServiceManager 的内部实现已更新,以兼容新的基于字符串的接口。 3. 优化变量更新: OpcUaServiceManager 中的 OnVariableChanged 事件处理逻辑被修改,现在能够更细粒度地处理单个变量的激活状态和轮询间隔的变化,避免了不必要的整个设备订阅的重新加载。
This commit is contained in:
@@ -37,32 +37,32 @@ namespace DMS.Infrastructure.Interfaces.Services
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 订阅单个节点的数据变化
|
/// 订阅单个节点的数据变化
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="node">要订阅的节点</param>
|
/// <param name="nodeId">要订阅的节点ID</param>
|
||||||
/// <param name="onDataChange">数据变化时的回调方法</param>
|
/// <param name="onDataChange">数据变化时的回调方法</param>
|
||||||
/// <param name="publishingInterval">发布间隔(毫秒)</param>
|
/// <param name="publishingInterval">发布间隔(毫秒)</param>
|
||||||
/// <param name="samplingInterval">采样间隔(毫秒)</param>
|
/// <param name="samplingInterval">采样间隔(毫秒)</param>
|
||||||
void SubscribeToNode(OpcUaNode node, Action<OpcUaNode> onDataChange, int publishingInterval = 1000, int samplingInterval = 500);
|
void SubscribeToNode(string nodeId, Action<OpcUaNode> onDataChange, int publishingInterval = 1000, int samplingInterval = 500);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 订阅多个节点的数据变化
|
/// 订阅多个节点的数据变化
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="nodes">要订阅的节点列表</param>
|
/// <param name="nodeIds">要订阅的节点ID列表</param>
|
||||||
/// <param name="onDataChange">数据变化时的回调方法</param>
|
/// <param name="onDataChange">数据变化时的回调方法</param>
|
||||||
/// <param name="publishingInterval">发布间隔(毫秒)</param>
|
/// <param name="publishingInterval">发布间隔(毫秒)</param>
|
||||||
/// <param name="samplingInterval">采样间隔(毫秒)</param>
|
/// <param name="samplingInterval">采样间隔(毫秒)</param>
|
||||||
void SubscribeToNode(List<OpcUaNode> nodes, Action<OpcUaNode> onDataChange, int publishingInterval = 1000, int samplingInterval = 500);
|
void SubscribeToNode(List<string> nodeIds, Action<OpcUaNode> onDataChange, int publishingInterval = 1000, int samplingInterval = 500);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 取消订阅单个节点
|
/// 取消订阅单个节点
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="node">要取消订阅的节点</param>
|
/// <param name="nodeId">要取消订阅的节点的ID</param>
|
||||||
void UnsubscribeFromNode(OpcUaNode node);
|
void UnsubscribeFromNode(string nodeId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 取消订阅多个节点
|
/// 取消订阅多个节点
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="nodes">要取消订阅的节点列表</param>
|
/// <param name="nodeIds">要取消订阅的节点ID列表</param>
|
||||||
void UnsubscribeFromNode(List<OpcUaNode> nodes);
|
void UnsubscribeFromNode(List<string> nodeIds);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取当前已订阅的所有节点
|
/// 获取当前已订阅的所有节点
|
||||||
|
|||||||
@@ -274,17 +274,17 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SubscribeToNode(OpcUaNode node, Action<OpcUaNode> onDataChange, int publishingInterval = 1000, int samplingInterval = 500)
|
public void SubscribeToNode(string nodeId, Action<OpcUaNode> onDataChange, int publishingInterval = 1000, int samplingInterval = 500)
|
||||||
{
|
{
|
||||||
_logger?.LogDebug("正在订阅单个节点: {NodeId} ({DisplayName}),发布间隔: {PublishingInterval}ms,采样间隔: {SamplingInterval}ms",
|
_logger?.LogDebug("正在订阅单个节点: {NodeId},发布间隔: {PublishingInterval}ms,采样间隔: {SamplingInterval}ms",
|
||||||
node.NodeId, node.DisplayName, publishingInterval, samplingInterval);
|
nodeId, publishingInterval, samplingInterval);
|
||||||
SubscribeToNode(new List<OpcUaNode> { node }, onDataChange, publishingInterval, samplingInterval);
|
SubscribeToNode(new List<string> { nodeId }, onDataChange, publishingInterval, samplingInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SubscribeToNode(List<OpcUaNode> nodes, Action<OpcUaNode> onDataChange, int publishingInterval = 1000, int samplingInterval = 500)
|
public void SubscribeToNode(List<string> nodeIds, Action<OpcUaNode> onDataChange, int publishingInterval = 1000, int samplingInterval = 500)
|
||||||
{
|
{
|
||||||
_logger?.LogDebug("正在订阅 {Count} 个节点,发布间隔: {PublishingInterval}ms,采样间隔: {SamplingInterval}ms",
|
_logger?.LogDebug("正在订阅 {Count} 个节点,发布间隔: {PublishingInterval}ms,采样间隔: {SamplingInterval}ms",
|
||||||
nodes?.Count ?? 0, publishingInterval, samplingInterval);
|
nodeIds?.Count ?? 0, publishingInterval, samplingInterval);
|
||||||
|
|
||||||
// 检查会话是否已连接
|
// 检查会话是否已连接
|
||||||
if (!IsConnected)
|
if (!IsConnected)
|
||||||
@@ -294,36 +294,64 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查节点列表是否有效
|
// 检查节点列表是否有效
|
||||||
if (nodes == null || !nodes.Any())
|
if (nodeIds == null || !nodeIds.Any())
|
||||||
{
|
{
|
||||||
_logger?.LogWarning("节点列表为null或为空,无法订阅");
|
_logger?.LogWarning("节点ID列表为null或为空,无法订阅");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保订阅对象存在
|
// 确保订阅对象存在
|
||||||
EnsureSubscriptionExists(publishingInterval);
|
// 如果还没有订阅对象,则基于会话的默认设置创建一个新的订阅
|
||||||
|
if (_subscription == null)
|
||||||
|
{
|
||||||
|
_subscription = new Subscription(_session.DefaultSubscription)
|
||||||
|
{
|
||||||
|
// 设置服务器向客户端发送通知的速率(毫秒)
|
||||||
|
PublishingInterval = publishingInterval
|
||||||
|
};
|
||||||
|
// 在会话中添加订阅
|
||||||
|
_session.AddSubscription(_subscription);
|
||||||
|
// 在服务器上创建订阅
|
||||||
|
_subscription.Create();
|
||||||
|
}
|
||||||
|
// 如果客户端请求的发布间隔与现有订阅不同,则修改订阅
|
||||||
|
else if (_subscription.PublishingInterval != publishingInterval)
|
||||||
|
{
|
||||||
|
_subscription.PublishingInterval = publishingInterval;
|
||||||
|
}
|
||||||
|
|
||||||
// 创建一个用于存放待添加监视项的列表
|
// 创建一个用于存放待添加监视项的列表
|
||||||
var itemsToAdd = new List<MonitoredItem>();
|
var itemsToAdd = new List<MonitoredItem>();
|
||||||
|
|
||||||
// 遍历所有请求订阅的节点
|
// 遍历所有请求订阅的节点ID
|
||||||
foreach (var node in nodes)
|
foreach (var nodeIdStr in nodeIds)
|
||||||
{
|
{
|
||||||
// 如果节点已经存在于我们的跟踪列表中,则跳过,避免重复订阅
|
try
|
||||||
if (_subscribedNodes.ContainsKey(node.NodeId))
|
|
||||||
{
|
{
|
||||||
_logger?.LogDebug("节点 {NodeId} ({DisplayName}) 已经被订阅,跳过重复订阅", node.NodeId, node.DisplayName);
|
var nodeId = new NodeId(nodeIdStr);
|
||||||
continue;
|
// 如果节点已经存在于我们的跟踪列表中,则跳过,避免重复订阅
|
||||||
|
if (_subscribedNodes.ContainsKey(nodeId))
|
||||||
|
{
|
||||||
|
_logger?.LogDebug("节点 {NodeId} 已经被订阅,跳过重复订阅", nodeIdStr);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建一个临时的OpcUaNode对象用于订阅
|
||||||
|
var node = new OpcUaNode { NodeId = nodeId, DisplayName = nodeIdStr };
|
||||||
|
|
||||||
|
// 为每个节点创建一个监视项
|
||||||
|
var monitoredItem = CreateMonitoredItem(node, onDataChange, samplingInterval);
|
||||||
|
|
||||||
|
// 将创建的监视项添加到待添加列表
|
||||||
|
itemsToAdd.Add(monitoredItem);
|
||||||
|
// 将节点添加到我们的跟踪字典中
|
||||||
|
_subscribedNodes.TryAdd(node.NodeId, node);
|
||||||
|
_logger?.LogDebug("节点 {NodeId} 已添加到订阅列表", nodeIdStr);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger?.LogError(ex, "创建节点 {NodeId} 的监视项时发生错误", nodeIdStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为每个节点创建一个监视项
|
|
||||||
var monitoredItem = CreateMonitoredItem(node, onDataChange, samplingInterval);
|
|
||||||
|
|
||||||
// 将创建的监视项添加到待添加列表
|
|
||||||
itemsToAdd.Add(monitoredItem);
|
|
||||||
// 将节点添加到我们的跟踪字典中
|
|
||||||
_subscribedNodes.TryAdd(node.NodeId, node);
|
|
||||||
_logger?.LogDebug("节点 {NodeId} ({DisplayName}) 已添加到订阅列表", node.NodeId, node.DisplayName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果有新的监视项要添加
|
// 如果有新的监视项要添加
|
||||||
@@ -344,31 +372,6 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 确保订阅对象存在
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="publishingInterval">发布间隔</param>
|
|
||||||
private void EnsureSubscriptionExists(int publishingInterval)
|
|
||||||
{
|
|
||||||
// 如果还没有订阅对象,则基于会话的默认设置创建一个新的订阅
|
|
||||||
if (_subscription == null)
|
|
||||||
{
|
|
||||||
_subscription = new Subscription(_session.DefaultSubscription)
|
|
||||||
{
|
|
||||||
// 设置服务器向客户端发送通知的速率(毫秒)
|
|
||||||
PublishingInterval = publishingInterval
|
|
||||||
};
|
|
||||||
// 在会话中添加订阅
|
|
||||||
_session.AddSubscription(_subscription);
|
|
||||||
// 在服务器上创建订阅
|
|
||||||
_subscription.Create();
|
|
||||||
}
|
|
||||||
// 如果客户端请求的发布间隔与现有订阅不同,则修改订阅
|
|
||||||
else if (_subscription.PublishingInterval != publishingInterval)
|
|
||||||
{
|
|
||||||
_subscription.PublishingInterval = publishingInterval;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建监视项
|
/// 创建监视项
|
||||||
@@ -414,67 +417,74 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
return monitoredItem;
|
return monitoredItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UnsubscribeFromNode(OpcUaNode node)
|
public void UnsubscribeFromNode(string nodeId)
|
||||||
{
|
|
||||||
_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)
|
|
||||||
{
|
|
||||||
_logger?.LogWarning("订阅对象为null,无法取消订阅");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nodes == null || !nodes.Any())
|
|
||||||
{
|
|
||||||
_logger?.LogWarning("节点列表为null或为空,无法取消订阅");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var itemsToRemove = new List<MonitoredItem>();
|
|
||||||
// 遍历所有请求取消订阅的节点
|
|
||||||
foreach (var node in nodes)
|
|
||||||
{
|
|
||||||
// 在当前订阅中查找与节点ID匹配的监视项
|
|
||||||
var item = _subscription.MonitoredItems.FirstOrDefault(m => m.StartNodeId.Equals(node.NodeId));
|
|
||||||
if (item != null)
|
|
||||||
{
|
{
|
||||||
_logger?.LogDebug("找到节点 {NodeId} ({DisplayName}) 的监视项,准备移除", node.NodeId, node.DisplayName);
|
_logger?.LogDebug("正在取消订阅节点: {NodeId}", nodeId);
|
||||||
// 如果找到,则添加到待移除列表
|
UnsubscribeFromNode(new List<string> { nodeId });
|
||||||
itemsToRemove.Add(item);
|
|
||||||
// 从我们的跟踪字典中移除该节点
|
|
||||||
_subscribedNodes.Remove(node.NodeId);
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
public void UnsubscribeFromNode(List<string> nodeIds)
|
||||||
{
|
{
|
||||||
_logger?.LogDebug("节点 {NodeId} ({DisplayName}) 未在监视项中找到,可能已经取消订阅", node.NodeId, node.DisplayName);
|
_logger?.LogDebug("正在取消订阅 {Count} 个节点", nodeIds?.Count ?? 0);
|
||||||
|
|
||||||
|
// 检查订阅对象和节点列表是否有效
|
||||||
|
if (_subscription == null)
|
||||||
|
{
|
||||||
|
_logger?.LogWarning("订阅对象为null,无法取消订阅");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodeIds == null || !nodeIds.Any())
|
||||||
|
{
|
||||||
|
_logger?.LogWarning("节点ID列表为null或为空,无法取消订阅");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var itemsToRemove = new List<MonitoredItem>();
|
||||||
|
// 遍历所有请求取消订阅的节点ID
|
||||||
|
foreach (var nodeIdStr in nodeIds)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var nodeId = new NodeId(nodeIdStr);
|
||||||
|
// 在当前订阅中查找与节点ID匹配的监视项
|
||||||
|
var item = _subscription.MonitoredItems.FirstOrDefault(m => m.StartNodeId.Equals(nodeId));
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
_logger?.LogDebug("找到节点 {NodeId} 的监视项,准备移除", nodeIdStr);
|
||||||
|
// 如果找到,则添加到待移除列表
|
||||||
|
itemsToRemove.Add(item);
|
||||||
|
// 从我们的跟踪字典中移除该节点
|
||||||
|
_subscribedNodes.Remove(nodeId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger?.LogDebug("节点 {NodeId} 未在监视项中找到,可能已经取消订阅", nodeIdStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger?.LogError(ex, "解析节点ID '{NodeIdStr}' 时发生错误,无法取消订阅", nodeIdStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有需要移除的监视项
|
||||||
|
if (itemsToRemove.Any())
|
||||||
|
{
|
||||||
|
_logger?.LogDebug("批量移除 {Count} 个监视项", itemsToRemove.Count);
|
||||||
|
|
||||||
|
// 从订阅中批量移除监视项
|
||||||
|
_subscription.RemoveItems(itemsToRemove);
|
||||||
|
// 将更改应用到服务器
|
||||||
|
_subscription.ApplyChanges();
|
||||||
|
|
||||||
|
_logger?.LogInformation("已成功取消订阅 {Count} 个节点", itemsToRemove.Count);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger?.LogDebug("没有找到需要移除的监视项");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 如果有需要移除的监视项
|
|
||||||
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()
|
public List<OpcUaNode> GetSubscribedNodes()
|
||||||
{
|
{
|
||||||
var subscribedNodes = _subscribedNodes.Values.ToList();
|
var subscribedNodes = _subscribedNodes.Values.ToList();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using DMS.Application.Interfaces;
|
|||||||
using DMS.Application.Models;
|
using DMS.Application.Models;
|
||||||
using DMS.Core.Enums;
|
using DMS.Core.Enums;
|
||||||
using DMS.Core.Events;
|
using DMS.Core.Events;
|
||||||
|
using DMS.Core.Models;
|
||||||
using DMS.Infrastructure.Configuration;
|
using DMS.Infrastructure.Configuration;
|
||||||
using DMS.Infrastructure.Interfaces.Services;
|
using DMS.Infrastructure.Interfaces.Services;
|
||||||
using DMS.Infrastructure.Models;
|
using DMS.Infrastructure.Models;
|
||||||
@@ -65,6 +66,7 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
{
|
{
|
||||||
await DisconnectDeviceAsync(e.DeviceId, CancellationToken.None);
|
await DisconnectDeviceAsync(e.DeviceId, CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Core.Enums.DeviceStateType.Connection:
|
case Core.Enums.DeviceStateType.Connection:
|
||||||
@@ -108,14 +110,14 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
if (device.Protocol != Core.Enums.ProtocolType.OpcUa)
|
if (device.Protocol != Core.Enums.ProtocolType.OpcUa)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("设备 {DeviceId} ({DeviceName}) 不是OPC UA协议,跳过加载",
|
_logger.LogInformation("设备 {DeviceId} ({DeviceName}) 不是OPC UA协议,跳过加载",
|
||||||
device.Id, device.Name);
|
device.Id, device.Name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("处理设备添加事件: {DeviceId} ({DeviceName})",
|
_logger.LogInformation("处理设备添加事件: {DeviceId} ({DeviceName})",
|
||||||
device.Id, device.Name);
|
device.Id, device.Name);
|
||||||
|
|
||||||
// 添加设备到监控列表
|
// 添加设备到监控列表
|
||||||
AddDevice(device);
|
AddDevice(device);
|
||||||
@@ -133,7 +135,7 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "连接新添加的设备 {DeviceId} ({DeviceName}) 时发生错误",
|
_logger.LogError(ex, "连接新添加的设备 {DeviceId} ({DeviceName}) 时发生错误",
|
||||||
device.Id, device.Name);
|
device.Id, device.Name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -141,7 +143,7 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "处理设备添加事件时发生错误: {DeviceId} ({DeviceName})",
|
_logger.LogError(ex, "处理设备添加事件时发生错误: {DeviceId} ({DeviceName})",
|
||||||
device?.Id, device?.Name);
|
device?.Id, device?.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,14 +162,14 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
if (device.Protocol != Core.Enums.ProtocolType.OpcUa)
|
if (device.Protocol != Core.Enums.ProtocolType.OpcUa)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("设备 {DeviceId} ({DeviceName}) 不是OPC UA协议,跳过更新",
|
_logger.LogInformation("设备 {DeviceId} ({DeviceName}) 不是OPC UA协议,跳过更新",
|
||||||
device.Id, device.Name);
|
device.Id, device.Name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("处理设备更新事件: {DeviceId} ({DeviceName})",
|
_logger.LogInformation("处理设备更新事件: {DeviceId} ({DeviceName})",
|
||||||
device.Id, device.Name);
|
device.Id, device.Name);
|
||||||
|
|
||||||
// 先移除旧设备配置
|
// 先移除旧设备配置
|
||||||
if (_deviceContexts.TryGetValue(device.Id, out var oldContext))
|
if (_deviceContexts.TryGetValue(device.Id, out var oldContext))
|
||||||
@@ -182,7 +184,7 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "断开旧设备 {DeviceId} ({DeviceName}) 连接时发生错误",
|
_logger.LogError(ex, "断开旧设备 {DeviceId} ({DeviceName}) 连接时发生错误",
|
||||||
device.Id, device.Name);
|
device.Id, device.Name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -203,7 +205,7 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "连接更新后的设备 {DeviceId} ({DeviceName}) 时发生错误",
|
_logger.LogError(ex, "连接更新后的设备 {DeviceId} ({DeviceName}) 时发生错误",
|
||||||
device.Id, device.Name);
|
device.Id, device.Name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -211,7 +213,7 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "处理设备更新事件时发生错误: {DeviceId} ({DeviceName})",
|
_logger.LogError(ex, "处理设备更新事件时发生错误: {DeviceId} ({DeviceName})",
|
||||||
device?.Id, device?.Name);
|
device?.Id, device?.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,21 +235,6 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取需要订阅的变量列表
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">设备上下文</param>
|
|
||||||
/// <returns>需要订阅的变量列表</returns>
|
|
||||||
private List<VariableDto> GetSubscribableVariables(DeviceContext context)
|
|
||||||
{
|
|
||||||
if (context?.Variables == null)
|
|
||||||
return new List<VariableDto>();
|
|
||||||
|
|
||||||
// 返回所有激活且OPC UA更新类型不是None的变量
|
|
||||||
return context.Variables.Values
|
|
||||||
.Where(v => v.IsActive )
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 处理批量导入变量事件
|
/// 处理批量导入变量事件
|
||||||
@@ -265,11 +252,13 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
_logger.LogInformation("处理批量导入变量事件,共 {Count} 个变量", e.Count);
|
_logger.LogInformation("处理批量导入变量事件,共 {Count} 个变量", e.Count);
|
||||||
|
|
||||||
// 更新相关设备的变量表
|
// 更新相关设备的变量表
|
||||||
var deviceIds = e.Variables.Select(v => v.VariableTable.DeviceId).Distinct();
|
var deviceIds = e.Variables.Select(v => v.VariableTable.DeviceId)
|
||||||
|
.Distinct();
|
||||||
foreach (var deviceId in deviceIds)
|
foreach (var deviceId in deviceIds)
|
||||||
{
|
{
|
||||||
// 获取设备的变量表信息
|
// 获取设备的变量表信息
|
||||||
var variablesForDevice = e.Variables.Where(v => v.VariableTable.DeviceId == deviceId).ToList();
|
var variablesForDevice = e.Variables.Where(v => v.VariableTable.DeviceId == deviceId)
|
||||||
|
.ToList();
|
||||||
if (variablesForDevice.Any())
|
if (variablesForDevice.Any())
|
||||||
{
|
{
|
||||||
// 更新设备上下文中的变量
|
// 更新设备上下文中的变量
|
||||||
@@ -441,7 +430,9 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
}
|
}
|
||||||
|
|
||||||
_eventService.RaiseDeviceStateChanged(
|
_eventService.RaiseDeviceStateChanged(
|
||||||
this, new DeviceStateChangedEventArgs(context.Device.Id, context.Device.Name, context.IsConnected, Core.Enums.DeviceStateType.Connection));
|
this,
|
||||||
|
new DeviceStateChangedEventArgs(context.Device.Id, context.Device.Name, context.IsConnected,
|
||||||
|
Core.Enums.DeviceStateType.Connection));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -450,7 +441,9 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
context.IsConnected = false;
|
context.IsConnected = false;
|
||||||
context.Device.IsRunning = false;
|
context.Device.IsRunning = false;
|
||||||
_eventService.RaiseDeviceStateChanged(
|
_eventService.RaiseDeviceStateChanged(
|
||||||
this, new DeviceStateChangedEventArgs(context.Device.Id, context.Device.Name, false, Core.Enums.DeviceStateType.Connection));
|
this,
|
||||||
|
new DeviceStateChangedEventArgs(context.Device.Id, context.Device.Name, false,
|
||||||
|
Core.Enums.DeviceStateType.Connection));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -473,7 +466,9 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
context.IsConnected = false;
|
context.IsConnected = false;
|
||||||
context.Device.IsRunning = false;
|
context.Device.IsRunning = false;
|
||||||
_eventService.RaiseDeviceStateChanged(
|
_eventService.RaiseDeviceStateChanged(
|
||||||
this, new DeviceStateChangedEventArgs(context.Device.Id, context.Device.Name, false, Core.Enums.DeviceStateType.Connection));
|
this,
|
||||||
|
new DeviceStateChangedEventArgs(context.Device.Id, context.Device.Name, false,
|
||||||
|
Core.Enums.DeviceStateType.Connection));
|
||||||
_logger.LogInformation("设备 {DeviceName} 连接已断开", context.Device.Name);
|
_logger.LogInformation("设备 {DeviceName} 连接已断开", context.Device.Name);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -493,20 +488,12 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 获取需要订阅的变量
|
|
||||||
var subscribableVariables = GetSubscribableVariables(context);
|
|
||||||
|
|
||||||
if (!subscribableVariables.Any())
|
|
||||||
{
|
|
||||||
_logger.LogInformation("设备 {DeviceName} 没有需要订阅的变量", context.Device.Name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("正在为设备 {DeviceName} 设置订阅,需要订阅的变量数: {VariableCount}",
|
_logger.LogInformation("正在为设备 {DeviceName} 设置订阅,需要订阅的变量数: {VariableCount}",
|
||||||
context.Device.Name, subscribableVariables.Count);
|
context.Device.Name, context.Variables.Count);
|
||||||
|
|
||||||
// 按PollingInterval对变量进行分组
|
// 按PollingInterval对变量进行分组
|
||||||
var variablesByPollingInterval = subscribableVariables
|
var variablesByPollingInterval = context.Variables.Values
|
||||||
.GroupBy(v => v.PollingInterval)
|
.GroupBy(v => v.PollingInterval)
|
||||||
.ToDictionary(g => g.Key, g => g.ToList());
|
.ToDictionary(g => g.Key, g => g.ToList());
|
||||||
|
|
||||||
@@ -520,11 +507,11 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
"为设备 {DeviceName} 设置PollingInterval {PollingInterval} 的订阅,变量数: {VariableCount}",
|
"为设备 {DeviceName} 设置PollingInterval {PollingInterval} 的订阅,变量数: {VariableCount}",
|
||||||
context.Device.Name, pollingInterval, variables.Count);
|
context.Device.Name, pollingInterval, variables.Count);
|
||||||
|
|
||||||
var opcUaNodes = variables
|
var opcUaNodeIds = variables
|
||||||
.Select(v => new OpcUaNode { NodeId = v.OpcUaNodeId })
|
.Select(v => v.OpcUaNodeId)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
context.OpcUaService.SubscribeToNode(opcUaNodes, HandleDataChanged,
|
context.OpcUaService.SubscribeToNode(opcUaNodeIds, HandleDataChanged,
|
||||||
pollingInterval, pollingInterval);
|
pollingInterval, pollingInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,7 +525,6 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 处理数据变化
|
/// 处理数据变化
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -546,7 +532,8 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
{
|
{
|
||||||
if (opcUaNode?.Value == null)
|
if (opcUaNode?.Value == null)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("HandleDataChanged: 接收到空节点或空值,节点ID: {NodeId}", opcUaNode?.NodeId?.ToString() ?? "Unknown");
|
_logger.LogDebug("HandleDataChanged: 接收到空节点或空值,节点ID: {NodeId}",
|
||||||
|
opcUaNode?.NodeId?.ToString() ?? "Unknown");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,13 +546,15 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
{
|
{
|
||||||
if (context.Variables.TryGetValue(opcUaNode.NodeId.ToString(), out var variable))
|
if (context.Variables.TryGetValue(opcUaNode.NodeId.ToString(), out var variable))
|
||||||
{
|
{
|
||||||
_logger.LogDebug("HandleDataChanged: 找到变量 {VariableName} (ID: {VariableId}) 与节点 {NodeId} 对应,设备: {DeviceName}",
|
// _logger.LogDebug(
|
||||||
variable.Name, variable.Id, opcUaNode.NodeId, context.Device.Name);
|
// "HandleDataChanged: 找到变量 {VariableName} (ID: {VariableId}) 与节点 {NodeId} 对应,设备: {DeviceName}",
|
||||||
|
// variable.Name, variable.Id, opcUaNode.NodeId, context.Device.Name);
|
||||||
|
|
||||||
// 推送到数据处理队列
|
// 推送到数据处理队列
|
||||||
await _dataProcessingService.EnqueueAsync(new VariableContext(variable, opcUaNode.Value?.ToString()));
|
await _dataProcessingService.EnqueueAsync(
|
||||||
|
new VariableContext(variable, opcUaNode.Value?.ToString()));
|
||||||
|
|
||||||
_logger.LogDebug("HandleDataChanged: 变量 {VariableName} 的值已推送到数据处理队列", variable.Name);
|
// _logger.LogDebug("HandleDataChanged: 变量 {VariableName} 的值已推送到数据处理队列", variable.Name);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -573,7 +562,8 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "处理数据变化时发生错误 - 节点ID: {NodeId}, 值: {Value}, 错误信息: {ErrorMessage}",
|
_logger.LogError(ex, "处理数据变化时发生错误 - 节点ID: {NodeId}, 值: {Value}, 错误信息: {ErrorMessage}",
|
||||||
opcUaNode?.NodeId?.ToString() ?? "Unknown", opcUaNode?.Value?.ToString() ?? "null", ex.Message);
|
opcUaNode?.NodeId?.ToString() ?? "Unknown", opcUaNode?.Value?.ToString() ?? "null",
|
||||||
|
ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -633,6 +623,7 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
await Task.WhenAll(disconnectTasks);
|
await Task.WhenAll(disconnectTasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 释放资源
|
/// 释放资源
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -667,12 +658,12 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 处理变量变更事件
|
/// 处理变量变更事件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnVariableChanged(object? sender, VariableChangedEventArgs e)
|
private async void OnVariableChanged(object? sender, VariableChangedEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogDebug("处理变量变更事件: 变量ID={VariableId}, 变更类型={ChangeType}, 变更属性={PropertyType}",
|
_logger.LogDebug("处理变量变更事件: 变量ID={VariableId}, 变更类型={ChangeType}, 变更属性={PropertyType}",
|
||||||
e.Variable.Id, e.ChangeType, e.PropertyType);
|
e.Variable.Id, e.ChangeType, e.PropertyType);
|
||||||
|
|
||||||
// 根据变更类型和属性类型进行相应处理
|
// 根据变更类型和属性类型进行相应处理
|
||||||
switch (e.ChangeType)
|
switch (e.ChangeType)
|
||||||
@@ -684,57 +675,59 @@ namespace DMS.Infrastructure.Services.OpcUa
|
|||||||
case VariablePropertyType.OpcUaNodeId:
|
case VariablePropertyType.OpcUaNodeId:
|
||||||
case VariablePropertyType.OpcUaUpdateType:
|
case VariablePropertyType.OpcUaUpdateType:
|
||||||
case VariablePropertyType.PollingInterval:
|
case VariablePropertyType.PollingInterval:
|
||||||
// 重新设置设备的订阅
|
|
||||||
if (_deviceContexts.TryGetValue(e.Variable.VariableTable.DeviceId, out var context))
|
if (_deviceContexts.TryGetValue(e.Variable.VariableTable.DeviceId, out var context))
|
||||||
{
|
{
|
||||||
_ = Task.Run(async () =>
|
if (context.Variables.TryGetValue(e.Variable.OpcUaNodeId,out var variableDto))
|
||||||
{
|
{
|
||||||
try
|
if (variableDto.IsActive)
|
||||||
{
|
{
|
||||||
await SetupSubscriptionsAsync(context, CancellationToken.None);
|
context.OpcUaService.UnsubscribeFromNode(e.Variable.OpcUaNodeId);
|
||||||
_logger.LogInformation("已更新设备 {DeviceId} 的订阅,因为变量 {VariableId} 的OPC UA属性发生了变化",
|
context.OpcUaService.SubscribeToNode(e.Variable.OpcUaNodeId,HandleDataChanged,e.Variable.PollingInterval,e.Variable.PollingInterval);
|
||||||
e.Variable.VariableTable.DeviceId, e.Variable.Id);
|
_logger.LogInformation($"OpcUa变量节点:{e.Variable.OpcUaNodeId},的轮询时间修改为:{e.Variable.PollingInterval}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
}
|
||||||
_logger.LogError(ex, "更新设备 {DeviceId} 订阅时发生错误", e.Variable.VariableTable.DeviceId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case VariablePropertyType.IsActive:
|
case VariablePropertyType.IsActive:
|
||||||
// 变量激活状态变化,更新变量列表
|
// 变量激活状态变化
|
||||||
if (_deviceContexts.TryGetValue(e.Variable.VariableTable.DeviceId, out var context2))
|
if (_deviceContexts.TryGetValue(e.Variable.VariableTable.DeviceId, out var context2))
|
||||||
{
|
{
|
||||||
if (e.Variable.IsActive)
|
if (e.Variable.IsActive)
|
||||||
{
|
{
|
||||||
// 添加变量到监控列表
|
// 添加变量到监控列表并重新订阅
|
||||||
context2.Variables.AddOrUpdate(e.Variable.OpcUaNodeId, e.Variable, (key, oldValue) => e.Variable);
|
if (context2.Variables.TryAdd(e.Variable.OpcUaNodeId, e.Variable))
|
||||||
|
{
|
||||||
|
context2.OpcUaService.SubscribeToNode(e.Variable.OpcUaNodeId,HandleDataChanged,e.Variable.PollingInterval,e.Variable.PollingInterval);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 从监控列表中移除变量
|
// 从监控列表中移除变量并取消订阅
|
||||||
context2.Variables.Remove(e.Variable.OpcUaNodeId, out _);
|
if (context2.Variables.TryRemove(e.Variable.OpcUaNodeId, out _))
|
||||||
|
{
|
||||||
|
context2.OpcUaService.UnsubscribeFromNode(e.Variable.OpcUaNodeId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ActionChangeType.Deleted:
|
case ActionChangeType.Deleted:
|
||||||
// 变量被删除时,从设备上下文的变量列表中移除
|
// 变量被删除时,取消订阅并从设备上下文的变量列表中移除
|
||||||
if (_deviceContexts.TryGetValue(e.Variable.VariableTable.DeviceId, out var context3))
|
// await UnsubscribeVariableAsync(e.Variable);
|
||||||
{
|
|
||||||
context3.Variables.Remove(e.Variable.OpcUaNodeId, out _);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "处理变量变更事件时发生错误: 变量ID={VariableId}, 变更类型={ChangeType}",
|
_logger.LogError(ex, "处理变量变更事件时发生错误: 变量ID={VariableId}, 变更类型={ChangeType}",
|
||||||
e.Variable.Id, e.ChangeType);
|
e.Variable.Id, e.ChangeType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user