临时提交
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
322
DMS.Infrastructure/Services/OpcUaService.cs
Normal file
322
DMS.Infrastructure/Services/OpcUaService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user