|
|
#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;
|
|
|
}
|
|
|
}
|
|
|
} |