#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 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> ReadNodeAsync(List 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 result = new List(); 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> BrowseNodesAsync(string startingNodeId = null) { if (_session == null || !_session.Connected) throw new Exception("未连接到 OPC UA 服务器"); var nodes = new List(); 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; } } }