临时提交

This commit is contained in:
2025-08-25 20:16:57 +08:00
parent 645671e4b9
commit 8290c96b1b
20 changed files with 1024 additions and 448 deletions

View File

@@ -1,7 +1,7 @@
using System.Data;
using System.Reflection;
using DMS.Core.Enums;
using DMS.Core.Interfaces;
using DMS.Core.Interfaces.Services;
using DMS.Core.Models;
using DMS.Infrastructure.Helper;
using NPOI.SS.UserModel;

View File

@@ -7,6 +7,7 @@ using Opc.Ua.Client;
using Microsoft.Extensions.Logging;
using DMS.Application.Interfaces;
using DMS.Core.Interfaces;
using DMS.Infrastructure.Helper;
namespace DMS.Infrastructure.Services;

View File

@@ -1,85 +0,0 @@
using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Configuration;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace DMS.Infrastructure.Services;
public static class OpcUaHelper
{
/// <summary>
/// 创建并配置 OPC UA 会话。
/// </summary>
/// <param name="endpointUrl">OPC UA 服务器的终结点 URL。</param>
/// <param name="stoppingToken"></param>
/// <returns>创建的 Session 对象,如果失败则返回 null。</returns>
public static async Task<Session> CreateOpcUaSessionAsync(string endpointUrl, CancellationToken stoppingToken = default)
{
// 1. 创建应用程序配置
var application = new ApplicationInstance
{
ApplicationName = "OpcUADemoClient",
ApplicationType = ApplicationType.Client,
ConfigSectionName = "Opc.Ua.Client"
};
var config = new ApplicationConfiguration()
{
ApplicationName = application.ApplicationName,
ApplicationUri = $"urn:{System.Net.Dns.GetHostName()}:OpcUADemoClient",
ApplicationType = application.ApplicationType,
SecurityConfiguration = new SecurityConfiguration
{
ApplicationCertificate = new CertificateIdentifier
{
StoreType = "Directory",
StorePath = "%CommonApplicationData%/OPC Foundation/CertificateStores/MachineDefault",
SubjectName = application.ApplicationName
},
TrustedIssuerCertificates = new CertificateTrustList
{
StoreType = "Directory",
StorePath = "%CommonApplicationData%/OPC Foundation/CertificateStores/UA Certificate Authorities"
},
TrustedPeerCertificates = new CertificateTrustList
{
StoreType = "Directory",
StorePath = "%CommonApplicationData%/OPC Foundation/CertificateStores/UA Applications"
},
RejectedCertificateStore = new CertificateTrustList
{
StoreType = "Directory",
StorePath = "%CommonApplicationData%/OPC Foundation/CertificateStores/RejectedCertificates"
},
AutoAcceptUntrustedCertificates = true // 自动接受不受信任的证书 (仅用于测试)
},
TransportQuotas = new TransportQuotas { OperationTimeout = 15000 },
ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 },
TraceConfiguration = new TraceConfiguration
{
OutputFilePath = "./Logs/OpcUaClient.log",
DeleteOnLoad = true,
TraceMasks = Utils.TraceMasks.Error | Utils.TraceMasks.Security
}
};
application.ApplicationConfiguration = config;
// 验证并检查证书
await config.Validate(ApplicationType.Client);
// 2. 查找并选择端点 (将 useSecurity 设置为 false 以进行诊断)
var selectedEndpoint = CoreClientUtils.SelectEndpoint(config, endpointUrl, false);
var session = await Session.Create(
config,
new ConfiguredEndpoint(null, selectedEndpoint, EndpointConfiguration.Create(config)),
false,
"DMS OPC UA Session",
60000,
new UserIdentity(new AnonymousIdentityToken()),
null, stoppingToken);
return session;
}
}

View File

@@ -0,0 +1,322 @@
using DMS.Infrastructure.Interfaces.Services;
using DMS.Infrastructure.Helper;
using Opc.Ua;
using Opc.Ua.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace DMS.Infrastructure.Services
{
public class OpcUaService : IOpcUaService
{
private Session? _session;
private string? _serverUrl;
/// <summary>
/// 创建 OPC UA 会话
/// </summary>
/// <param name="opcUaServerUrl">OPC UA 服务器地址</param>
/// <param name="stoppingToken">取消令牌</param>
/// <returns></returns>
public async Task CreateSession(string opcUaServerUrl, CancellationToken stoppingToken = default)
{
if (string.IsNullOrEmpty(opcUaServerUrl))
{
throw new ArgumentException("OPC UA server URL cannot be null or empty.", nameof(opcUaServerUrl));
}
try
{
_session = await OpcUaHelper.CreateOpcUaSessionAsync(opcUaServerUrl, stoppingToken);
_serverUrl = opcUaServerUrl;
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to create OPC UA session: {ex.Message}", ex);
}
}
/// <summary>
/// 连接到 OPC UA 服务器
/// </summary>
public async Task ConnectAsync(CancellationToken stoppingToken = default)
{
if (string.IsNullOrEmpty(_serverUrl))
{
throw new InvalidOperationException("Server URL is not set. Please call CreateSession first.");
}
// 如果已经连接,直接返回
if (_session?.Connected == true)
{
return;
}
// 重新创建会话
await CreateSession(_serverUrl, stoppingToken);
}
/// <summary>
/// 连接到 OPC UA 服务器(同步版本,用于向后兼容)
/// </summary>
public void Connect()
{
if (string.IsNullOrEmpty(_serverUrl))
{
throw new InvalidOperationException("Server URL is not set. Please call CreateSession first.");
}
// 如果已经连接,直接返回
if (_session?.Connected == true)
{
return;
}
// 检查会话是否存在但未连接
if (_session != null)
{
// 尝试重新激活会话
try
{
_session.Reconnect();
return;
}
catch
{
// 如果重新连接失败,继续到重新创建会话的步骤
}
}
// 如果没有会话或重新连接失败,抛出异常提示需要调用 CreateSession
throw new InvalidOperationException("Session is not created or connection lost. Please call CreateSession first.");
}
/// <summary>
/// 断开 OPC UA 服务器连接
/// </summary>
public void Disconnect()
{
if (_session != null)
{
try
{
_session.Close();
}
catch (Exception ex)
{
// 记录日志但不抛出异常,确保清理工作完成
System.Diagnostics.Debug.WriteLine($"Error closing OPC UA session: {ex.Message}");
}
finally
{
_session = null;
}
}
}
/// <summary>
/// 添加订阅
/// </summary>
/// <param name="subscriptionName">订阅名称</param>
/// <returns>创建的订阅</returns>
public Subscription AddSubscription(string subscriptionName)
{
if (_session == null)
{
throw new InvalidOperationException("Session is not created. Please call CreateSession first.");
}
if (!_session.Connected)
{
throw new InvalidOperationException("Session is not connected. Please call Connect first.");
}
if (string.IsNullOrEmpty(subscriptionName))
{
throw new ArgumentException("Subscription name cannot be null or empty.", nameof(subscriptionName));
}
try
{
var subscription = new Subscription(_session.DefaultSubscription)
{
DisplayName = subscriptionName,
PublishingInterval = 1000,
LifetimeCount = 0,
MaxNotificationsPerPublish = 0,
PublishingEnabled = true,
Priority = 0
};
_session.AddSubscription(subscription);
subscription.Create();
return subscription;
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to add subscription '{subscriptionName}': {ex.Message}", ex);
}
}
/// <summary>
/// 浏览节点
/// </summary>
/// <param name="nodeId">起始节点ID</param>
/// <returns>节点引用列表</returns>
public IList<ReferenceDescription> BrowseNodes(NodeId nodeId)
{
if (_session == null)
{
throw new InvalidOperationException("Session is not created. Please call CreateSession first.");
}
if (!_session.Connected)
{
throw new InvalidOperationException("Session is not connected. Please call Connect first.");
}
if (nodeId == null)
{
throw new ArgumentNullException(nameof(nodeId));
}
try
{
// 使用会话的浏览方法
var response = _session.Browse(
null,
null,
nodeId,
0u,
BrowseDirection.Forward,
ReferenceTypeIds.HierarchicalReferences,
true,
(uint)NodeClass.Unspecified,
out var continuationPoint,
out var references);
return references;
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to browse nodes: {ex.Message}", ex);
}
}
/// <summary>
/// 读取节点值
/// </summary>
/// <param name="nodeId">节点ID</param>
/// <returns>节点值</returns>
public DataValue ReadValue(NodeId nodeId)
{
if (_session == null)
{
throw new InvalidOperationException("Session is not created. Please call CreateSession first.");
}
if (!_session.Connected)
{
throw new InvalidOperationException("Session is not connected. Please call Connect first.");
}
if (nodeId == null)
{
throw new ArgumentNullException(nameof(nodeId));
}
try
{
// 创建读取值集合
var nodesToRead = new ReadValueIdCollection
{
new ReadValueId
{
NodeId = nodeId,
AttributeId = Attributes.Value
}
};
// 执行读取操作
_session.Read(
null,
0,
TimestampsToReturn.Neither,
nodesToRead,
out var results,
out var diagnosticInfos);
return results[0];
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to read value from node '{nodeId}': {ex.Message}", ex);
}
}
/// <summary>
/// 写入节点值
/// </summary>
/// <param name="nodeId">节点ID</param>
/// <param name="value">要写入的值</param>
/// <returns>写入结果</returns>
public StatusCode WriteValue(NodeId nodeId, object value)
{
if (_session == null)
{
throw new InvalidOperationException("Session is not created. Please call CreateSession first.");
}
if (!_session.Connected)
{
throw new InvalidOperationException("Session is not connected. Please call Connect first.");
}
if (nodeId == null)
{
throw new ArgumentNullException(nameof(nodeId));
}
try
{
// 创建写入值集合
var nodesToWrite = new WriteValueCollection
{
new WriteValue
{
NodeId = nodeId,
AttributeId = Attributes.Value,
Value = new DataValue(new Variant(value))
}
};
// 执行写入操作
_session.Write(
null,
nodesToWrite,
out var results,
out var diagnosticInfos);
return results[0];
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to write value to node '{nodeId}': {ex.Message}", ex);
}
}
/// <summary>
/// 检查是否已连接
/// </summary>
/// <returns></returns>
public bool IsConnected()
{
return _session?.Connected == true;
}
}
}