Files
KinginfoGateway/framework/Foundation/ThingsGateway.Foundation.Adapter.OPCUA/OPCUAClient/OPCUAClient.cs
2023-10-26 19:16:24 +08:00

1221 lines
41 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Newtonsoft.Json.Linq;
using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Client.ComplexTypes;
using Opc.Ua.Configuration;
//修改自https://github.com/dathlin/OpcUaHelper 与OPC基金会net库
namespace ThingsGateway.Foundation.Adapter.OPCUA;
/// <summary>
/// 订阅委托
/// </summary>
/// <param name="value"></param>
public delegate void DataChangedEventHandler((VariableNode variableNode, DataValue dataValue, JToken jToken) value);
/// <summary>
/// OPCUAClient
/// </summary>
public class OPCUAClient : IDisposable
{
#region
/// <summary>
/// 当前配置
/// </summary>
public OPCNode OPCNode;
/// <summary>
/// ProductUri
/// </summary>
public string ProductUri = "https://diego2098.gitee.io/thingsgateway-docs/";
/// <summary>
/// 当前保存的变量名称列表
/// </summary>
public List<string> Variables = new();
/// <summary>
/// 当前的变量名称/OPC变量节点
/// </summary>
private readonly Dictionary<string, VariableNode> _variableDicts = new();
private readonly object checkLock = new();
/// <summary>
/// 当前的订阅组,组名称/组
/// </summary>
private readonly Dictionary<string, Subscription> dic_subscriptions = new();
private readonly ApplicationInstance m_application = new();
private readonly ApplicationConfiguration m_configuration;
private SessionReconnectHandler m_reConnectHandler;
private EventHandler m_ReconnectComplete;
private EventHandler m_ReconnectStarting;
private EventHandler<KeepAliveEventArgs> m_KeepAliveComplete;
private EventHandler<bool> m_ConnectComplete;
private EventHandler<OpcUaStatusEventArgs> m_OpcStatusChange;
private ISession m_session;
/// <summary>
/// 默认的构造函数实例化一个新的OPC UA类
/// </summary>
public OPCUAClient()
{
var certificateValidator = new CertificateValidator();
certificateValidator.CertificateValidation += CertificateValidation;
// 构建应用程序配置
m_configuration = new ApplicationConfiguration
{
ApplicationName = OPCUAName,
ApplicationType = ApplicationType.Client,
CertificateValidator = certificateValidator,
ApplicationUri = Utils.Format(@"urn:{0}:{1}", System.Net.Dns.GetHostName(), OPCUAName),
ProductUri = ProductUri,
ServerConfiguration = new ServerConfiguration
{
MaxSubscriptionCount = 100000,
MaxMessageQueueSize = 1000000,
MaxNotificationQueueSize = 1000000,
MaxPublishRequestCount = 10000000,
},
SecurityConfiguration = new SecurityConfiguration
{
UseValidatedCertificates = true,
AutoAcceptUntrustedCertificates = true,//自动接受证书
RejectSHA1SignedCertificates = false,
MinimumCertificateKeySize = 1024,
SuppressNonceValidationErrors = true,
ApplicationCertificate = new CertificateIdentifier
{
StoreType = CertificateStoreType.X509Store,
StorePath = "CurrentUser\\" + OPCUAName,
SubjectName = $"CN={OPCUAName}, C=CN, S=GUANGZHOU, O=ThingsGateway, DC=" + System.Net.Dns.GetHostName(),
},
TrustedIssuerCertificates = new CertificateTrustList
{
StoreType = CertificateStoreType.Directory,
StorePath = AppContext.BaseDirectory + @"OPCUAClientCertificate\pki\trustedIssuer",
},
TrustedPeerCertificates = new CertificateTrustList
{
StoreType = CertificateStoreType.Directory,
StorePath = AppContext.BaseDirectory + @"OPCUAClientCertificate\pki\trustedPeer",
},
RejectedCertificateStore = new CertificateStoreIdentifier
{
StoreType = CertificateStoreType.Directory,
StorePath = AppContext.BaseDirectory + @"OPCUAClientCertificate\pki\rejected",
},
UserIssuerCertificates = new CertificateTrustList
{
StoreType = CertificateStoreType.Directory,
StorePath = AppContext.BaseDirectory + @"OPCUAClientCertificate\pki\issuerUser",
},
TrustedUserCertificates = new CertificateTrustList
{
StoreType = CertificateStoreType.Directory,
StorePath = AppContext.BaseDirectory + @"OPCUAClientCertificate\pki\trustedUser",
}
},
TransportQuotas = new TransportQuotas
{
OperationTimeout = 6000000,
MaxStringLength = int.MaxValue,
MaxByteStringLength = int.MaxValue,
MaxArrayLength = 65535,
MaxMessageSize = 419430400,
MaxBufferSize = 65535,
ChannelLifetime = -1,
SecurityTokenLifetime = -1
},
ClientConfiguration = new ClientConfiguration
{
DefaultSessionTimeout = -1,
MinSubscriptionLifetime = -1,
},
DisableHiResClock = true
};
certificateValidator.Update(m_configuration);
m_configuration.Validate(ApplicationType.Client);
m_application.ApplicationConfiguration = m_configuration;
}
/// <summary>
/// 订阅
/// </summary>
public event DataChangedEventHandler DataChangedHandler;
/// <summary>
/// 配置信息
/// </summary>
public ApplicationConfiguration AppConfig => m_configuration;
/// <summary>
/// 连接状态
/// </summary>
public bool Connected => m_session?.Connected == true;
/// <summary>
/// OPCUAClient
/// </summary>
public string OPCUAName { get; set; } = "ThingsGateway";
/// <summary>
/// SessionReconnectHandler
/// </summary>
public SessionReconnectHandler ReConnectHandler => m_reConnectHandler;
/// <summary>
/// Raised when a good keep alive from the server arrives.
/// </summary>
public event EventHandler<KeepAliveEventArgs> KeepAliveComplete
{
add { m_KeepAliveComplete += value; }
remove { m_KeepAliveComplete -= value; }
}
/// <summary>
/// Raised when a reconnect operation starts.
/// </summary>
public event EventHandler ReconnectStarting
{
add { m_ReconnectStarting += value; }
remove { m_ReconnectStarting -= value; }
}
/// <summary>
/// Raised when a reconnect operation completes.
/// </summary>
public event EventHandler ReconnectComplete
{
add { m_ReconnectComplete += value; }
remove { m_ReconnectComplete -= value; }
}
/// <summary>
/// Raised after successfully connecting to or disconnecing from a server.
/// </summary>
public event EventHandler<bool> ConnectComplete
{
add { m_ConnectComplete += value; }
remove { m_ConnectComplete -= value; }
}
/// <summary>
/// Raised after the client status change
/// </summary>
public event EventHandler<OpcUaStatusEventArgs> OpcStatusChange
{
add { m_OpcStatusChange += value; }
remove { m_OpcStatusChange -= value; }
}
/// <summary>
/// 当前活动会话。
/// </summary>
public ISession Session => m_session;
#endregion
#region
/// <summary>
/// 新增订阅需要指定订阅组名称订阅的tag名数组
/// </summary>
public async Task AddSubscriptionAsync(string subscriptionName, string[] items, bool loadType = true)
{
Subscription m_subscription = new(m_session.DefaultSubscription)
{
PublishingEnabled = true,
PublishingInterval = 0,
KeepAliveCount = uint.MaxValue,
LifetimeCount = uint.MaxValue,
MaxNotificationsPerPublish = uint.MaxValue,
Priority = 100,
DisplayName = subscriptionName
};
List<MonitoredItem> monitoredItems = new();
var variableNodes = loadType ? await ReadNodesAsync(items) : null;
for (int i = 0; i < items.Length; i++)
{
try
{
var item = new MonitoredItem
{
StartNodeId = loadType ? variableNodes[i].NodeId : items[i],
AttributeId = Attributes.Value,
DisplayName = items[i],
Filter = OPCNode.DeadBand == 0 ? null : new DataChangeFilter() { DeadbandValue = OPCNode.DeadBand, DeadbandType = (int)DeadbandType.Absolute, Trigger = DataChangeTrigger.StatusValue },
SamplingInterval = OPCNode?.UpdateRate ?? 1000,
};
item.Notification += Callback;
monitoredItems.Add(item);
}
catch (Exception ex)
{
UpdateStatus(3, DateTime.Now, $"初始化{items[i]}变量订阅失败,错误原因:{ex}");
}
}
m_subscription.AddItems(monitoredItems);
m_session.AddSubscription(m_subscription);
m_subscription.Create();
foreach (var item in m_subscription.MonitoredItems.Where(a => a.Status.Error != null && StatusCode.IsBad(a.Status.Error.StatusCode)))
{
item.Filter = OPCNode.DeadBand == 0 ? null : new DataChangeFilter() { DeadbandValue = OPCNode.DeadBand, DeadbandType = (int)DeadbandType.None, Trigger = DataChangeTrigger.StatusValue };
}
m_subscription.ApplyChanges();
var isError = m_subscription.MonitoredItems.Any(a => a.Status.Error != null && StatusCode.IsBad(a.Status.Error.StatusCode));
if (isError)
{
UpdateStatus(3, DateTime.Now, $"创建以下变量订阅失败:{Environment.NewLine}{m_subscription.MonitoredItems.Where(
a => a.Status.Error != null && StatusCode.IsBad(a.Status.Error.StatusCode))
.Select(a => $"{a.StartNodeId.ToString()}{a.Status.Error.ToString()}").ToJsonString()}");
}
lock (dic_subscriptions)
{
if (dic_subscriptions.ContainsKey(subscriptionName))
{
// remove
dic_subscriptions[subscriptionName].Delete(true);
m_session.RemoveSubscription(dic_subscriptions[subscriptionName]);
try { dic_subscriptions[subscriptionName].Dispose(); } catch { }
dic_subscriptions[subscriptionName] = m_subscription;
}
else
{
dic_subscriptions.Add(subscriptionName, m_subscription);
}
}
}
/// <summary>
/// 移除所有的订阅消息
/// </summary>
public void RemoveAllSubscription()
{
lock (dic_subscriptions)
{
foreach (var item in dic_subscriptions)
{
item.Value.Delete(true);
m_session.RemoveSubscription(item.Value);
try { item.Value.Dispose(); } catch { }
}
dic_subscriptions.Clear();
}
}
/// <summary>
/// 移除订阅消息
/// </summary>
/// <param name="subscriptionName">组名称</param>
public void RemoveSubscription(string subscriptionName)
{
lock (dic_subscriptions)
{
if (dic_subscriptions.ContainsKey(subscriptionName))
{
// remove
dic_subscriptions[subscriptionName].Delete(true);
m_session.RemoveSubscription(dic_subscriptions[subscriptionName]);
try { dic_subscriptions[subscriptionName].Dispose(); } catch { }
dic_subscriptions.RemoveWhere(a => a.Key == subscriptionName);
}
}
}
private void Callback(MonitoredItem monitoreditem, MonitoredItemNotificationEventArgs monitoredItemNotificationEventArgs)
{
try
{
if (m_session != null)
{
var variableNode = ReadNode(monitoreditem.StartNodeId.ToString(), false);
foreach (var value in monitoreditem.DequeueValues())
{
if (value.Value != null)
{
var data = JsonUtils.Encode(m_session.MessageContext, TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable), value.Value);
if (data == null && value.Value != null)
{
UpdateStatus(3, DateTime.Now, $"{monitoreditem.StartNodeId}转换出错原始值String为{value.Value}");
var data1 = JsonUtils.Encode(m_session.MessageContext, TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable), value.Value);
}
DataChangedHandler?.Invoke((variableNode, value, data));
}
else
{
var data = JValue.CreateNull();
DataChangedHandler?.Invoke((variableNode, value, data));
}
}
}
}
catch (Exception ex)
{
UpdateStatus(3, DateTime.Now, $"{monitoreditem.StartNodeId}订阅处理错误,错误原因:" + ex);
}
}
#endregion
#region
/// <summary>
/// 浏览一个节点的引用
/// </summary>
/// <param name="tag">节点值</param>
/// <returns>引用节点描述</returns>
public async Task<ReferenceDescription[]> BrowseNodeReferenceAsync(string tag)
{
NodeId sourceId = new(tag);
// 该节点可以读取到方法
BrowseDescription nodeToBrowse1 = new()
{
NodeId = sourceId,
BrowseDirection = BrowseDirection.Forward,
ReferenceTypeId = ReferenceTypeIds.Aggregates,
IncludeSubtypes = true,
NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable | NodeClass.Method),
ResultMask = (uint)BrowseResultMask.All
};
// find all nodes organized by the node.
BrowseDescription nodeToBrowse2 = new()
{
NodeId = sourceId,
BrowseDirection = BrowseDirection.Forward,
ReferenceTypeId = ReferenceTypeIds.Organizes,
IncludeSubtypes = true,
NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable),
ResultMask = (uint)BrowseResultMask.All
};
BrowseDescriptionCollection nodesToBrowse = new()
{
nodeToBrowse1,
nodeToBrowse2
};
// fetch references from the server.
ReferenceDescriptionCollection references = await FormUtils.BrowseAsync(m_session, nodesToBrowse, false);
return references.ToArray();
}
/// <summary>
/// 调用服务器的方法
/// </summary>
/// <param name="tagParent">方法的父节点tag</param>
/// <param name="tag">方法的节点tag</param>
/// <param name="args">传递的参数</param>
/// <returns>输出的结果值</returns>
public object[] CallMethodByNodeId(string tagParent, string tag, params object[] args)
{
if (m_session == null)
{
return null;
}
IList<object> outputArguments = m_session.Call(
new NodeId(tagParent),
new NodeId(tag),
args);
return outputArguments.ToArray();
}
/// <summary>
/// 读取历史数据
/// </summary>
/// <param name="tag">节点的索引</param>
/// <param name="start">开始时间</param>
/// <param name="end">结束时间</param>
/// <param name="count">读取的个数</param>
/// <param name="containBound">是否包含边界</param>
/// <param name="cancellationToken">cancellationToken</param>
/// <returns>读取的数据列表</returns>
public async Task<List<DataValue>> ReadHistoryRawDataValues(string tag, DateTime start, DateTime end, uint count = 1, bool containBound = false, CancellationToken cancellationToken = default)
{
HistoryReadValueId m_nodeToContinue = new()
{
NodeId = new NodeId(tag),
};
ReadRawModifiedDetails m_details = new()
{
StartTime = start,
EndTime = end,
NumValuesPerNode = count,
IsReadModified = false,
ReturnBounds = containBound
};
HistoryReadValueIdCollection nodesToRead = new()
{
m_nodeToContinue
};
var result = await m_session.HistoryReadAsync(
null,
new ExtensionObject(m_details),
TimestampsToReturn.Both,
false,
nodesToRead,
cancellationToken);
var results = result.Results;
var diagnosticInfos = result.DiagnosticInfos;
ClientBase.ValidateResponse(results, nodesToRead);
ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead);
if (StatusCode.IsBad(results[0].StatusCode))
{
throw new ServiceResultException(results[0].StatusCode);
}
HistoryData values = ExtensionObject.ToEncodeable(results[0].HistoryData) as HistoryData;
return values.DataValues;
}
#endregion
#region
private ComplexTypeSystem typeSystem;
/// <summary>
/// 连接到服务器
/// </summary>
public async Task ConnectAsync()
{
await ConnectAsync(OPCNode.OPCUrl);
}
/// <summary>
/// 断开连接。
/// </summary>
public void Disconnect()
{
PrivateDisconnect();
// disconnect any existing session.
if (m_session != null)
{
m_session = null;
}
}
/// <summary>
/// Creates a new session.
/// </summary>
/// <returns>The new session object.</returns>
private async Task<ISession> ConnectAsync(string serverUrl)
{
PrivateDisconnect();
if (m_configuration == null)
{
throw new ArgumentNullException("未初始化配置");
}
var useSecurity = OPCNode?.IsUseSecurity ?? true;
EndpointDescription endpointDescription = CoreClientUtils.SelectEndpoint(m_configuration, serverUrl, useSecurity, 10000);
EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(m_configuration);
ConfiguredEndpoint endpoint = new(null, endpointDescription, endpointConfiguration);
UserIdentity userIdentity;
if (!string.IsNullOrEmpty(OPCNode.UserName))
{
userIdentity = new UserIdentity(OPCNode.UserName, OPCNode.Password);
}
else
{
userIdentity = new UserIdentity(new AnonymousIdentityToken());
}
//创建本地证书
await m_application.CheckApplicationInstanceCertificate(true, 0, 1200);
m_session = await Opc.Ua.Client.Session.Create(
m_configuration,
endpoint,
false,
OPCNode.CheckDomain,
(string.IsNullOrEmpty(OPCUAName)) ? m_configuration.ApplicationName : OPCUAName,
60000,
userIdentity,
Array.Empty<string>()
).ConfigureAwait(false);
typeSystem = new ComplexTypeSystem(m_session);
m_session.KeepAliveInterval = OPCNode.KeepAliveInterval == 0 ? 60000 : OPCNode.KeepAliveInterval;
m_session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive);
// raise an event.
DoConnectComplete(true);
UpdateStatus(2, DateTime.UtcNow, "Connected");
//如果是订阅模式,连接时添加订阅组
if (OPCNode.ActiveSubscribe)
await AddSubscriptionAsync(Guid.NewGuid().ToString(), Variables.ToArray(), OPCNode.LoadType);
return m_session;
}
private void PrivateDisconnect()
{
bool state = m_session?.Connected == true;
if (m_reConnectHandler != null)
{
try { m_reConnectHandler.Dispose(); } catch { }
m_reConnectHandler = null;
}
if (m_session != null)
{
m_session.KeepAlive -= Session_KeepAlive;
m_session.Close(10000);
}
if (state)
{
UpdateStatus(2, DateTime.UtcNow, "Disconnected");
DoConnectComplete(false);
}
}
#endregion
#region /
/// <summary>
/// 从服务器读取值
/// </summary>
public async Task<List<(string, DataValue, JToken)>> ReadJTokenValueAsync(string[] tags, CancellationToken cancellationToken = default)
{
var result = await ReadJTokenValueAsync(tags.Select(a => new NodeId(a)).ToArray(), cancellationToken);
return result;
}
/// <summary>
/// 异步写opc标签
/// </summary>
public async Task<Dictionary<string, Tuple<bool, string>>> WriteNodeAsync(Dictionary<string, JToken> writeInfoLists, CancellationToken cancellationToken = default)
{
Dictionary<string, Tuple<bool, string>> results = new();
try
{
WriteValueCollection valuesToWrite = new();
foreach (var item in writeInfoLists)
{
WriteValue valueToWrite = new()
{
NodeId = new NodeId(item.Key),
AttributeId = Attributes.Value,
};
var variableNode = await ReadNodeAsync(item.Key, false, cancellationToken);
var dataValue = JsonUtils.Decode(
m_session.MessageContext,
variableNode.DataType,
TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable),
item.Value.CalculateActualValueRank(),
item.Value
);
valueToWrite.Value = dataValue;
valuesToWrite.Add(valueToWrite);
}
var result = await m_session.WriteAsync(
requestHeader: null,
nodesToWrite: valuesToWrite, cancellationToken);
ClientBase.ValidateResponse(result.Results, valuesToWrite);
ClientBase.ValidateDiagnosticInfos(result.DiagnosticInfos, valuesToWrite);
var keys = writeInfoLists.Keys.ToList();
for (int i = 0; i < keys.Count; i++)
{
if (!StatusCode.IsGood(result.Results[i]))
results.Add(keys[i], Tuple.Create(true, result.Results[i].ToString()));
else
{
results.Add(keys[i], Tuple.Create(false, "成功"));
}
}
return results;
}
catch (Exception ex)
{
var keys = writeInfoLists.Keys.ToList();
foreach (var item in keys)
{
results.Add(item, Tuple.Create(true, ex.Message));
}
return results;
}
}
/// <summary>
/// 从服务器读取值
/// </summary>
private async Task<List<(string, DataValue, JToken)>> ReadJTokenValueAsync(NodeId[] nodeIds, CancellationToken cancellationToken = default)
{
if (m_session == null)
{
throw new("服务器未初始化连接");
}
ReadValueIdCollection nodesToRead = new();
for (int i = 0; i < nodeIds.Length; i++)
{
nodesToRead.Add(new ReadValueId()
{
NodeId = nodeIds[i],
AttributeId = Attributes.Value
});
}
// 读取当前的值
var result = await m_session.ReadAsync(
null,
0,
TimestampsToReturn.Neither,
nodesToRead,
cancellationToken);
var results = result.Results;
var diagnosticInfos = result.DiagnosticInfos;
ClientBase.ValidateResponse(results, nodesToRead);
ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead);
List<(string, DataValue, JToken)> jTokens = new();
for (int i = 0; i < results.Count; i++)
{
var variableNode = await ReadNodeAsync(nodeIds[i].ToString(), false, cancellationToken);
var type = TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable);
var jToken = JsonUtils.Encode(m_session.MessageContext, type, results[i].Value);
jTokens.Add((variableNode.NodeId.ToString(), results[i], jToken));
}
return jTokens.ToList();
}
/// <summary>
/// 从服务器或缓存读取节点
/// </summary>
private async Task<VariableNode> ReadNodeAsync(string nodeIdStr, bool isOnlyServer = true, CancellationToken cancellationToken = default)
{
if (!isOnlyServer)
{
if (_variableDicts.TryGetValue(nodeIdStr, out var value))
{
return value;
}
}
NodeId nodeToRead = new(nodeIdStr);
var node = (VariableNode)await m_session.ReadNodeAsync(nodeToRead, NodeClass.Variable, false, cancellationToken);
if (OPCNode.LoadType)
await typeSystem.LoadType(node.DataType).ConfigureAwait(false);
_variableDicts.AddOrUpdate(nodeIdStr, node);
return node;
}
/// <summary>
/// 从服务器或缓存读取节点
/// </summary>
private VariableNode ReadNode(string nodeIdStr, bool isOnlyServer = true)
{
if (!isOnlyServer)
{
if (_variableDicts.TryGetValue(nodeIdStr, out var value))
{
return value;
}
}
NodeId nodeToRead = new(nodeIdStr);
var node = (VariableNode)m_session.ReadNode(nodeToRead, NodeClass.Variable, false);
_variableDicts.AddOrUpdate(nodeIdStr, node);
return node;
}
/// <summary>
/// 从服务器读取节点
/// </summary>
private async Task<List<Node>> ReadNodesAsync(string[] nodeIdStrs, CancellationToken cancellationToken = default)
{
List<NodeId> nodeIds = new List<NodeId>();
foreach (var item in nodeIdStrs)
{
NodeId nodeToRead = new(item);
nodeIds.Add(nodeToRead);
}
(IList<Node>, IList<ServiceResult>) nodes = await m_session.ReadNodesAsync(nodeIds, NodeClass.Variable, false, cancellationToken);
for (int i = 0; i < nodes.Item1.Count; i++)
{
if (StatusCode.IsGood(nodes.Item2[i].StatusCode))
{
var node = ((VariableNode)nodes.Item1[i]);
await typeSystem.LoadType(node.DataType).ConfigureAwait(false);
_variableDicts.AddOrUpdate(nodeIdStrs[i], node);
}
else
{
UpdateStatus(3, DateTime.Now, $"获取服务器节点信息失败{nodes.Item2[i]}");
}
}
return nodes.Item1.ToList();
}
#endregion
#region
/// <summary>
/// 读取一个节点的所有属性
/// </summary>
public async Task<List<OPCNodeAttribute>> ReadNoteAttributeAsync(string tag, uint attributesId, CancellationToken cancellationToken = default)
{
BrowseDescriptionCollection nodesToBrowse = new();
ReadValueIdCollection nodesToRead = new();
NodeId sourceId = new(tag);
ReadValueId nodeToRead = new()
{
NodeId = sourceId,
AttributeId = attributesId
};
nodesToRead.Add(nodeToRead);
BrowseDescription nodeToBrowse = new()
{
NodeId = sourceId,
BrowseDirection = BrowseDirection.Forward,
ReferenceTypeId = ReferenceTypeIds.HasProperty,
IncludeSubtypes = true,
NodeClassMask = 0,
ResultMask = (uint)BrowseResultMask.All
};
nodesToBrowse.Add(nodeToBrowse);
var result1 = await ReadNoteAttributeAsync(nodesToBrowse, nodesToRead, cancellationToken);
var result2 = result1.Values.FirstOrDefault();
return result2;
}
/// <summary>
/// 读取节点的所有属性
/// </summary>
public async Task<Dictionary<string, List<OPCNodeAttribute>>> ReadNoteAttributeAsync(List<string> tags, CancellationToken cancellationToken)
{
BrowseDescriptionCollection nodesToBrowse = new();
ReadValueIdCollection nodesToRead = new();
foreach (var tag in tags)
{
NodeId sourceId = new(tag);
for (uint ii = Attributes.NodeClass; ii <= Attributes.UserExecutable; ii++)
{
ReadValueId nodeToRead = new()
{
NodeId = sourceId,
AttributeId = ii
};
nodesToRead.Add(nodeToRead);
}
BrowseDescription nodeToBrowse = new()
{
NodeId = sourceId,
BrowseDirection = BrowseDirection.Forward,
ReferenceTypeId = ReferenceTypeIds.HasProperty,
IncludeSubtypes = true,
NodeClassMask = 0,
ResultMask = (uint)BrowseResultMask.All
};
nodesToBrowse.Add(nodeToBrowse);
}
return await ReadNoteAttributeAsync(nodesToBrowse, nodesToRead, cancellationToken);
}
/// <summary>
/// 读取一个节点的所有属性
/// </summary>
/// <param name="tag">节点信息</param>
/// <returns>节点的特性值</returns>
public OPCNodeAttribute[] ReadNoteAttributes(string tag)
{
NodeId sourceId = new(tag);
ReadValueIdCollection nodesToRead = new();
for (uint ii = Attributes.NodeClass; ii <= Attributes.UserExecutable; ii++)
{
ReadValueId nodeToRead = new()
{
NodeId = sourceId,
AttributeId = ii
};
nodesToRead.Add(nodeToRead);
}
int startOfProperties = nodesToRead.Count;
// find all of the pror of the node.
BrowseDescription nodeToBrowse1 = new()
{
NodeId = sourceId,
BrowseDirection = BrowseDirection.Forward,
ReferenceTypeId = ReferenceTypeIds.HasProperty,
IncludeSubtypes = true,
NodeClassMask = 0,
ResultMask = (uint)BrowseResultMask.All
};
BrowseDescriptionCollection nodesToBrowse = new()
{
nodeToBrowse1
};
// fetch property references from the server.
ReferenceDescriptionCollection references = FormUtils.Browse(m_session, nodesToBrowse, false);
if (references == null)
{
return Array.Empty<OPCNodeAttribute>();
}
for (int ii = 0; ii < references.Count; ii++)
{
// ignore external references.
if (references[ii].NodeId.IsAbsolute)
{
continue;
}
ReadValueId nodeToRead = new()
{
NodeId = (NodeId)references[ii].NodeId,
AttributeId = Attributes.Value
};
nodesToRead.Add(nodeToRead);
}
// read all values.
m_session.Read(
null,
0,
TimestampsToReturn.Neither,
nodesToRead,
out DataValueCollection results,
out DiagnosticInfoCollection diagnosticInfos);
ClientBase.ValidateResponse(results, nodesToRead);
ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead);
// process results.
List<OPCNodeAttribute> nodeAttribute = new();
for (int ii = 0; ii < results.Count; ii++)
{
OPCNodeAttribute item = new();
// process attribute value.
if (ii < startOfProperties)
{
// ignore attributes which are invalid for the node.
if (results[ii].StatusCode == StatusCodes.BadAttributeIdInvalid)
{
continue;
}
// get the name of the attribute.
item.Name = Attributes.GetBrowseName(nodesToRead[ii].AttributeId);
// display any unexpected error.
if (StatusCode.IsBad(results[ii].StatusCode))
{
item.Type = Utils.Format("{0}", Attributes.GetDataTypeId(nodesToRead[ii].AttributeId));
item.Value = Utils.Format("{0}", results[ii].StatusCode);
}
// display the value.
else
{
TypeInfo typeInfo = TypeInfo.Construct(results[ii].Value);
item.Type = typeInfo.BuiltInType.ToString();
if (typeInfo.ValueRank >= ValueRanks.OneOrMoreDimensions)
{
item.Type += "[]";
}
item.Value = results[ii].Value;//Utils.Format("{0}", results[ii].Value);
}
}
// process property value.
else
{
// ignore properties which are invalid for the node.
if (results[ii].StatusCode == StatusCodes.BadNodeIdUnknown)
{
continue;
}
// get the name of the property.
item.Name = Utils.Format("{0}", references[ii - startOfProperties]);
// display any unexpected error.
if (StatusCode.IsBad(results[ii].StatusCode))
{
item.Type = String.Empty;
item.Value = Utils.Format("{0}", results[ii].StatusCode);
}
// display the value.
else
{
TypeInfo typeInfo = TypeInfo.Construct(results[ii].Value);
item.Type = typeInfo.BuiltInType.ToString();
if (typeInfo.ValueRank >= ValueRanks.OneOrMoreDimensions)
{
item.Type += "[]";
}
item.Value = results[ii].Value;
}
}
nodeAttribute.Add(item);
}
return nodeAttribute.ToArray();
}
#endregion
/// <inheritdoc/>
public void Dispose()
{
Disconnect();
}
#region
private void CertificateValidation(CertificateValidator sender, CertificateValidationEventArgs eventArgs)
{
if (ServiceResult.IsGood(eventArgs.Error))
eventArgs.Accept = true;
else if (eventArgs.Error.StatusCode.Code == StatusCodes.BadCertificateUntrusted)
eventArgs.Accept = true;
else
throw new Exception(string.Format("验证证书失败,错误代码:{0}: {1}", eventArgs.Error.Code, eventArgs.Error.AdditionalInfo));
}
private async Task<Dictionary<string, List<OPCNodeAttribute>>> ReadNoteAttributeAsync(BrowseDescriptionCollection nodesToBrowse, ReadValueIdCollection nodesToRead, CancellationToken cancellationToken)
{
int startOfProperties = nodesToRead.Count;
ReferenceDescriptionCollection references = await FormUtils.BrowseAsync(m_session, nodesToBrowse, false, cancellationToken);
if (references == null)
{
throw new("浏览失败");
}
for (int ii = 0; ii < references.Count; ii++)
{
if (references[ii].NodeId.IsAbsolute)
{
continue;
}
ReadValueId nodeToRead = new()
{
NodeId = (NodeId)references[ii].NodeId,
AttributeId = Attributes.Value
};
nodesToRead.Add(nodeToRead);
}
var result = await m_session.ReadAsync(
null,
0,
TimestampsToReturn.Neither,
nodesToRead, cancellationToken);
ClientBase.ValidateResponse(result.Results, nodesToRead);
ClientBase.ValidateDiagnosticInfos(result.DiagnosticInfos, nodesToRead);
Dictionary<string, List<OPCNodeAttribute>> nodeAttributes = new();
for (int ii = 0; ii < result.Results.Count; ii++)
{
DataValue nodeValue = result.Results[ii];
var nodeToRead = nodesToRead[ii];
OPCNodeAttribute item = new();
if (ii < startOfProperties)
{
if (nodeValue.StatusCode == StatusCodes.BadAttributeIdInvalid)
{
continue;
}
item.Name = Attributes.GetBrowseName(nodesToRead[ii].AttributeId);
if (StatusCode.IsBad(nodeValue.StatusCode))
{
item.Type = Utils.Format("{0}", Attributes.GetDataTypeId(nodesToRead[ii].AttributeId));
item.Value = Utils.Format("{0}", nodeValue.StatusCode);
}
else
{
TypeInfo typeInfo = TypeInfo.Construct(nodeValue.Value);
item.Type = typeInfo.BuiltInType.ToString();
if (typeInfo.ValueRank >= ValueRanks.OneOrMoreDimensions)
{
item.Type += "[]";
}
if (item.Name == nameof(Attributes.NodeClass))
{
item.Value = ((NodeClass)nodeValue.Value).ToString();
}
else if (item.Name == nameof(Attributes.EventNotifier))
{
item.Value = ((EventNotifierType)nodeValue.Value).ToString();
}
else
item.Value = nodeValue.Value;
}
}
if (nodeAttributes.ContainsKey(nodeToRead.NodeId.ToString()))
{
nodeAttributes[nodeToRead.NodeId.ToString()].Add(item);
}
else
{
nodeAttributes.Add(nodeToRead.NodeId.ToString(), new() { item });
}
}
return nodeAttributes;
}
/// <summary>
/// 连接处理器连接事件处理完成。
/// </summary>
private void Server_ReconnectComplete(object sender, EventArgs e)
{
try
{
if (!Object.ReferenceEquals(sender, m_reConnectHandler))
{
return;
}
m_session = m_reConnectHandler.Session;
m_reConnectHandler.Dispose();
m_reConnectHandler = null;
// raise any additional notifications.
m_ReconnectComplete?.Invoke(this, e);
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// Report the client status
/// </summary>
/// <param name="logLevel">Whether the status represents an error. </param>
/// <param name="time">The time associated with the status.</param>
/// <param name="status">The status message.</param>
/// <param name="args">Arguments used to format the status message.</param>
private void UpdateStatus(int logLevel, DateTime time, string status, params object[] args)
{
m_OpcStatusChange?.Invoke(this, new OpcUaStatusEventArgs()
{
LogLevel = logLevel,
Time = time.ToLocalTime(),
Text = String.Format(status, args),
});
}
private void Session_KeepAlive(ISession session, KeepAliveEventArgs e)
{
lock (checkLock)
{
if (!Object.ReferenceEquals(session, m_session))
{
return;
}
if (ServiceResult.IsBad(e.Status))
{
if (m_session.KeepAliveInterval <= 0)
{
UpdateStatus(3, e.CurrentTime, "Communication Error ({0})", e.Status);
return;
}
UpdateStatus(3, e.CurrentTime, "Reconnecting in {0}s", m_session.KeepAliveInterval / 1000);
if (m_reConnectHandler == null)
{
m_ReconnectStarting?.Invoke(this, e);
m_reConnectHandler = new SessionReconnectHandler();
m_reConnectHandler.BeginReconnect(m_session, m_session.KeepAliveInterval, Server_ReconnectComplete);
}
return;
}
// update status.
UpdateStatus(0, e.CurrentTime, "Session_KeepAlive Connected [{0}]", session.Endpoint.EndpointUrl);
// raise any additional notifications.
m_KeepAliveComplete?.Invoke(this, e);
}
}
/// <summary>
/// Raises the connect complete event on the main GUI thread.
/// </summary>
private void DoConnectComplete(bool state)
{
m_ConnectComplete?.Invoke(this, state);
}
#endregion
}