You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

263 lines
8.6 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#region << 版 本 注 释 >>
/*--------------------------------------------------------------------
* 版权所有 (c) 2025 WenJY 保留所有权利。
* CLR版本4.0.30319.42000
* 机器名称Mr.Wen's MacBook Pro
* 命名空间Sln.Imm.Daemon.Opc.Impl
* 唯一标识AAD72EB2-53B5-43B2-AD71-77FE72AC816E
*
* 创建者WenJY
* 电子邮箱:
* 创建时间2025-09-22 17:24:43
* 版本V1.0.0
* 描述:
*
*--------------------------------------------------------------------
* 修改人:
* 时间:
* 修改说明:
*
* 版本V1.0.0
*--------------------------------------------------------------------*/
#endregion << 版 本 注 释 >>
using Opc.Ua;
using Opc.Ua.Client;
using Sln.Imm.Daemon.Model.dto;
namespace Sln.Imm.Daemon.Opc.Impl;
public class OpcUaService : IOpcService, IDisposable
{
private ApplicationConfiguration _config;
private Session _session;
private bool _disposed = false;
public OpcUaService()
{
_config = new ApplicationConfiguration()
{
ApplicationName = "OPC UA Client",
ApplicationType = ApplicationType.Client,
SecurityConfiguration = new SecurityConfiguration
{
// 使用最小化的证书配置
ApplicationCertificate = new CertificateIdentifier
{
StoreType = "Directory",
StorePath = "./pki/own" // 使用相对路径
},
TrustedPeerCertificates = new CertificateTrustList
{
StoreType = "Directory",
StorePath = "./pki/trusted" // 使用相对路径
},
TrustedIssuerCertificates = new CertificateTrustList
{
StoreType = "Directory",
StorePath = "./pki/issuers" // 使用相对路径
},
RejectedCertificateStore = new CertificateTrustList
{
StoreType = "Directory",
StorePath = "./pki/rejected" // 使用相对路径
},
AutoAcceptUntrustedCertificates = true // 自动接受证书(仅测试环境)
},
TransportConfigurations = new TransportConfigurationCollection(),
ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 }
};
_config.Validate(ApplicationType.Client).Wait();
}
public async Task<bool> ConnectAsync(string serverUrl)
{
try
{
var endpointDescription = CoreClientUtils.SelectEndpoint(serverUrl, false);
var endpointConfiguration = EndpointConfiguration.Create(_config);
var endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);
_session = await Session.Create(
_config,
endpoint,
false,
false,
_config.ApplicationName,
60000,
new UserIdentity(),
null);
return _session != null && _session.Connected;
}
catch (Exception ex)
{
throw new InvalidOperationException($"连接到 OPC UA 服务器失败: {ex.Message}");
}
}
public async Task DisconnectAsync()
{
if (_session != null && _session.Connected)
{
_session.Close();
_session.Dispose();
_session = null;
}
await Task.CompletedTask;
}
public async Task<List<OpcNode>> ReadNodeAsync(List<string> nodeId)
{
if (_session == null || !_session.Connected)
throw new Exception("未连接到 OPC UA 服务器");
try
{
ReadValueIdCollection nodesToRead = new ReadValueIdCollection();
nodeId.ForEach(x =>
{
ReadValueId nodeToRead = new ReadValueId
{
NodeId = new NodeId(x),
AttributeId = Attributes.Value
};
nodesToRead.Add(nodeToRead);
});
_session.Read(
null,
0,
TimestampsToReturn.Both,
nodesToRead,
out DataValueCollection results,
out DiagnosticInfoCollection diagnosticInfos);
if (results != null && results.Count > 0 && StatusCode.IsGood(results[0].StatusCode))
{
var indexedNodeIds = nodeId.Select((value,index)=>new {Index = index, Value = value});
List<OpcNode> result = new List<OpcNode>();
foreach (var item in indexedNodeIds)
{
var dataValue = results[item.Index];
result.Add(new OpcNode()
{
NodeId = item.Value,
DisplayName = item.Value,
Value = dataValue.Value,
SourceTimestamp = dataValue.SourceTimestamp,
DataType = dataValue.WrappedValue.TypeInfo?.BuiltInType.ToString() ?? "Unknown"
});
}
return result;
}
throw new Exception($"读取节点失败: {StatusCode.LookupSymbolicId((uint)results[0].StatusCode)}");
}
catch (Exception ex)
{
throw new InvalidOperationException($"OPC UA 读取节点时出错:{ex.Message}");
}
}
public async Task WriteNodeAsync(string nodeId, object value)
{
if (_session == null || !_session.Connected)
throw new Exception("未连接到 OPC UA 服务器");
try
{
WriteValueCollection nodesToWrite = new WriteValueCollection();
WriteValue nodeToWrite = new WriteValue
{
NodeId = new NodeId(nodeId),
AttributeId = Attributes.Value,
Value = new DataValue(new Variant(value))
};
nodesToWrite.Add(nodeToWrite);
_session.Write(
null,
nodesToWrite,
out StatusCodeCollection results,
out DiagnosticInfoCollection diagnosticInfos);
if (results == null || results.Count == 0 || !StatusCode.IsGood(results[0]))
{
throw new Exception($"写入节点失败: {StatusCode.LookupSymbolicId((uint)results[0])}");
}
}
catch (Exception ex)
{
throw new InvalidOperationException($"写入节点时出错:{ex.Message}");
}
}
public async Task<List<OpcNode>> BrowseNodesAsync(string startingNodeId = null)
{
if (_session == null || !_session.Connected)
throw new Exception("未连接到 OPC UA 服务器");
var nodes = new List<OpcNode>();
NodeId startingNode = startingNodeId != null ? new NodeId(startingNodeId) : ObjectIds.ObjectsFolder;
try
{
BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection();
BrowseDescription nodeToBrowse = new BrowseDescription
{
NodeId = startingNode,
BrowseDirection = BrowseDirection.Forward,
ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences,
IncludeSubtypes = true,
NodeClassMask = (uint)(NodeClass.Variable | NodeClass.Object),
ResultMask = (uint)BrowseResultMask.All
};
nodesToBrowse.Add(nodeToBrowse);
_session.Browse(
null,
null,
0,
nodesToBrowse,
out BrowseResultCollection results,
out DiagnosticInfoCollection diagnosticInfos);
if (results != null && results.Count > 0)
{
foreach (var reference in results[0].References)
{
nodes.Add(new OpcNode
{
NodeId = reference.NodeId.ToString(),
DisplayName = reference.DisplayName.Text,
DataType = reference.NodeClass.ToString()
});
}
}
return nodes;
}
catch (Exception ex)
{
Console.WriteLine($"浏览节点时出错: {ex.Message}");
throw;
}
}
public void Dispose()
{
if (!_disposed)
{
DisconnectAsync().Wait();
_disposed = true;
}
}
}