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.5 KiB
C#

#region << 版 本 注 释 >>
/*--------------------------------------------------------------------
* (c) 2025 WenJY
* CLR4.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($"读取节点时出错:{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;
}
}
}