iotgateway/Plugins/Drivers/OPC.UaClient/OpcUaHelper/OpcUaClientHelper.cs

1446 lines
52 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.

using Opc.Ua;
using Opc.Ua.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace OpcUaHelper
{
/// <summary>
/// 一个二次封装了的OPC UA库支持从opc ua服务器读写节点数据批量读写订阅批量订阅历史数据读取方法调用操作。
/// </summary>
public class OpcUaClientHelper
{
#region Constructors
/// <summary>
/// 默认的构造函数实例化一个新的OPC UA类
/// </summary>
public OpcUaClientHelper( )
{
dic_subscriptions = new Dictionary<string, Subscription>( );
var certificateValidator = new CertificateValidator( );
certificateValidator.CertificateValidation += ( sender, 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( "Failed to validate certificate with error code {0}: {1}", eventArgs.Error.Code, eventArgs.Error.AdditionalInfo ) );
};
SecurityConfiguration securityConfigurationcv = new SecurityConfiguration
{
AutoAcceptUntrustedCertificates = true,
RejectSHA1SignedCertificates = false,
MinimumCertificateKeySize = 1024,
};
certificateValidator.Update( securityConfigurationcv );
// Build the application configuration
var configuration = new ApplicationConfiguration
{
ApplicationName = OpcUaName,
ApplicationType = ApplicationType.Client,
CertificateValidator = certificateValidator,
ApplicationUri = "urn:MyClient", //Kepp this syntax
ProductUri = "OpcUaClient",
ServerConfiguration = new ServerConfiguration
{
MaxSubscriptionCount = 100000,
MaxMessageQueueSize = 1000000,
MaxNotificationQueueSize = 1000000,
MaxPublishRequestCount = 10000000,
},
SecurityConfiguration = new SecurityConfiguration
{
AutoAcceptUntrustedCertificates = true,
RejectSHA1SignedCertificates = false,
MinimumCertificateKeySize = 1024,
SuppressNonceValidationErrors = true,
ApplicationCertificate = new CertificateIdentifier
{
StoreType = CertificateStoreType.X509Store,
StorePath = "CurrentUser\\My",
SubjectName = OpcUaName,
},
TrustedIssuerCertificates = new CertificateTrustList
{
StoreType = CertificateStoreType.X509Store,
StorePath = "CurrentUser\\Root",
},
TrustedPeerCertificates = new CertificateTrustList
{
StoreType = CertificateStoreType.X509Store,
StorePath = "CurrentUser\\Root",
}
},
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
};
configuration.Validate( ApplicationType.Client );
m_configuration = configuration;
}
#endregion Constructors
#region Connect And Disconnect
/// <summary>
/// connect to server
/// </summary>
/// <param name="serverUrl">remote url</param>
public async Task ConnectServer( string serverUrl )
{
m_session = await Connect( serverUrl );
}
/// <summary>
/// Creates a new session.
/// </summary>
/// <returns>The new session object.</returns>
private async Task<Session> Connect( string serverUrl )
{
// disconnect from existing session.
Disconnect( );
if (m_configuration == null)
{
throw new ArgumentNullException( "_configuration" );
}
// select the best endpoint.
EndpointDescription endpointDescription = CoreClientUtils.SelectEndpoint( serverUrl, UseSecurity );
EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create( m_configuration );
ConfiguredEndpoint endpoint = new ConfiguredEndpoint( null, endpointDescription, endpointConfiguration );
m_session = await Session.Create(
m_configuration,
endpoint,
false,
false,
(string.IsNullOrEmpty( OpcUaName )) ? m_configuration.ApplicationName : OpcUaName,
60000,
UserIdentity,
new string[] { } );
// set up keep alive callback.
m_session.KeepAlive += new KeepAliveEventHandler( Session_KeepAlive );
// update the client status
m_IsConnected = true;
// raise an event.
DoConnectComplete( null );
// return the new session.
return m_session;
}
/// <summary>
/// Disconnects from the server.
/// </summary>
public void Disconnect( )
{
UpdateStatus( false, DateTime.UtcNow, "Disconnected" );
// stop any reconnect operation.
if (m_reConnectHandler != null)
{
m_reConnectHandler.Dispose( );
m_reConnectHandler = null;
}
// disconnect any existing session.
if (m_session != null)
{
m_session.Close( 10000 );
m_session = null;
}
// update the client status
m_IsConnected = false;
// raise an event.
DoConnectComplete( null );
}
#endregion Connect And Disconnect
#region Event Handlers
/// <summary>
/// Report the client status
/// </summary>
/// <param name="error">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( bool error, DateTime time, string status, params object[] args )
{
m_OpcStatusChange?.Invoke( this, new OpcUaStatusEventArgs( )
{
Error = error,
Time = time.ToLocalTime( ),
Text = String.Format( status, args ),
} );
}
/// <summary>
/// Handles a keep alive event from a session.
/// </summary>
private void Session_KeepAlive( Session session, KeepAliveEventArgs e )
{
try
{
// check for events from discarded sessions.
if (!Object.ReferenceEquals( session, m_session ))
{
return;
}
// start reconnect sequence on communication error.
if (ServiceResult.IsBad( e.Status ))
{
if (m_reconnectPeriod <= 0)
{
UpdateStatus( true, e.CurrentTime, "Communication Error ({0})", e.Status );
return;
}
UpdateStatus( true, e.CurrentTime, "Reconnecting in {0}s", m_reconnectPeriod );
if (m_reConnectHandler == null)
{
m_ReconnectStarting?.Invoke( this, e );
m_reConnectHandler = new SessionReconnectHandler( );
m_reConnectHandler.BeginReconnect( m_session, m_reconnectPeriod * 1000, Server_ReconnectComplete );
}
return;
}
// update status.
UpdateStatus( false, e.CurrentTime, "Connected [{0}]", session.Endpoint.EndpointUrl );
// raise any additional notifications.
m_KeepAliveComplete?.Invoke( this, e );
}
catch (Exception exception)
{
throw;
}
}
/// <summary>
/// Handles a reconnect event complete from the reconnect handler.
/// </summary>
private void Server_ReconnectComplete( object sender, EventArgs e )
{
try
{
// ignore callbacks from discarded objects.
if (!Object.ReferenceEquals( sender, m_reConnectHandler ))
{
return;
}
m_session = (Session)m_reConnectHandler.Session;
m_reConnectHandler.Dispose( );
m_reConnectHandler = null;
// raise any additional notifications.
m_ReconnectComplete?.Invoke( this, e );
}
catch (Exception exception)
{
throw;
}
}
#endregion Event Handlers
#region LogOut Setting
/// <summary>
/// 设置OPC客户端的日志输出
/// </summary>
/// <param name="filePath">完整的文件路径</param>
/// <param name="deleteExisting">是否删除原文件</param>
public void SetLogPathName( string filePath, bool deleteExisting )
{
Utils.SetTraceLog( filePath, deleteExisting );
Utils.SetTraceMask( 515 );
}
#endregion LogOut Setting
#region Public Members
/// <summary>
/// a name of application name show on server
/// </summary>
public string OpcUaName { get; set; } = "Opc Ua Helper";
/// <summary>
/// Whether to use security when connecting.
/// </summary>
public bool UseSecurity
{
get { return m_useSecurity; }
set { m_useSecurity = value; }
}
/// <summary>
/// The user identity to use when creating the session.
/// </summary>
public IUserIdentity UserIdentity { get; set; }
/// <summary>
/// The currently active session.
/// </summary>
public Session Session
{
get { return m_session; }
}
/// <summary>
/// Indicate the connect status
/// </summary>
public bool Connected
{
get { return m_IsConnected; }
}
/// <summary>
/// The number of seconds between reconnect attempts (0 means reconnect is disabled).
/// </summary>
public int ReconnectPeriod
{
get { return m_reconnectPeriod; }
set { m_reconnectPeriod = value; }
}
/// <summary>
/// Raised when a good keep alive from the server arrives.
/// </summary>
public event EventHandler 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 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 ApplicationConfiguration AppConfig => m_configuration;
#endregion Public Members
#region Node Write/Read Support
/// <summary>
/// Read a value node from server
/// </summary>
/// <param name="nodeId">node id</param>
/// <returns>DataValue</returns>
public DataValue ReadNode( NodeId nodeId )
{
ReadValueIdCollection nodesToRead = new ReadValueIdCollection
{
new ReadValueId( )
{
NodeId = nodeId,
AttributeId = Attributes.Value
}
};
// read the current value
m_session.Read(
null,
0,
TimestampsToReturn.Neither,
nodesToRead,
out DataValueCollection results,
out DiagnosticInfoCollection diagnosticInfos );
ClientBase.ValidateResponse( results, nodesToRead );
ClientBase.ValidateDiagnosticInfos( diagnosticInfos, nodesToRead );
return results[0];
}
/// <summary>
/// Read a value node from server
/// </summary>
/// <typeparam name="T">type of value</typeparam>
/// <param name="tag">node id</param>
/// <returns>实际值</returns>
public T ReadNode<T>( string tag )
{
DataValue dataValue = ReadNode( new NodeId( tag ) );
return (T)dataValue.Value;
}
/// <summary>
/// Read a tag asynchronously
/// </summary>
/// <typeparam name="T">The type of tag to read</typeparam>
/// <param name="tag">tag值</param>
/// <returns>The value retrieved from the OPC</returns>
public Task<T> ReadNodeAsync<T>( string tag )
{
ReadValueIdCollection nodesToRead = new ReadValueIdCollection
{
new ReadValueId()
{
NodeId = new NodeId(tag),
AttributeId = Attributes.Value
}
};
// Wrap the ReadAsync logic in a TaskCompletionSource, so we can use C# async/await syntax to call it:
var taskCompletionSource = new TaskCompletionSource<T>( );
m_session.BeginRead(
requestHeader: null,
maxAge: 0,
timestampsToReturn: TimestampsToReturn.Neither,
nodesToRead: nodesToRead,
callback: ar =>
{
DataValueCollection results;
DiagnosticInfoCollection diag;
var response = m_session.EndRead(
result: ar,
results: out results,
diagnosticInfos: out diag );
try
{
CheckReturnValue( response.ServiceResult );
CheckReturnValue( results[0].StatusCode );
var val = results[0];
taskCompletionSource.TrySetResult( (T)val.Value );
}
catch (Exception ex)
{
taskCompletionSource.TrySetException( ex );
}
},
asyncState: null );
return taskCompletionSource.Task;
}
/// <summary>
/// read several value nodes from server
/// </summary>
/// <param name="nodeIds">all NodeIds</param>
/// <returns>all values</returns>
public List<DataValue> ReadNodes( NodeId[] nodeIds )
{
ReadValueIdCollection nodesToRead = new ReadValueIdCollection( );
for (int i = 0; i < nodeIds.Length; i++)
{
nodesToRead.Add( new ReadValueId( )
{
NodeId = nodeIds[i],
AttributeId = Attributes.Value
} );
}
// 读取当前的值
m_session.Read(
null,
0,
TimestampsToReturn.Neither,
nodesToRead,
out DataValueCollection results,
out DiagnosticInfoCollection diagnosticInfos );
ClientBase.ValidateResponse( results, nodesToRead );
ClientBase.ValidateDiagnosticInfos( diagnosticInfos, nodesToRead );
return results.ToList( );
}
/// <summary>
/// read several value nodes from server
/// </summary>
/// <param name="nodeIds">all NodeIds</param>
/// <returns>all values</returns>
public Task<List<DataValue>> ReadNodesAsync( NodeId[] nodeIds )
{
ReadValueIdCollection nodesToRead = new ReadValueIdCollection( );
for (int i = 0; i < nodeIds.Length; i++)
{
nodesToRead.Add( new ReadValueId( )
{
NodeId = nodeIds[i],
AttributeId = Attributes.Value
} );
}
var taskCompletionSource = new TaskCompletionSource<List<DataValue>>( );
// 读取当前的值
m_session.BeginRead(
null,
0,
TimestampsToReturn.Neither,
nodesToRead,
callback: ar =>
{
DataValueCollection results;
DiagnosticInfoCollection diag;
var response = m_session.EndRead(
result: ar,
results: out results,
diagnosticInfos: out diag );
try
{
CheckReturnValue( response.ServiceResult );
taskCompletionSource.TrySetResult( results.ToList( ) );
}
catch (Exception ex)
{
taskCompletionSource.TrySetException( ex );
}
},
asyncState: null );
return taskCompletionSource.Task;
}
/// <summary>
/// read several value nodes from server
/// </summary>
/// <param name="tags">所以的节点数组信息</param>
/// <returns>all values</returns>
public List<T> ReadNodes<T>( string[] tags )
{
List<T> result = new List<T>( );
ReadValueIdCollection nodesToRead = new ReadValueIdCollection( );
for (int i = 0; i < tags.Length; i++)
{
nodesToRead.Add( new ReadValueId( )
{
NodeId = new NodeId( tags[i] ),
AttributeId = Attributes.Value
} );
}
// 读取当前的值
m_session.Read(
null,
0,
TimestampsToReturn.Neither,
nodesToRead,
out DataValueCollection results,
out DiagnosticInfoCollection diagnosticInfos );
ClientBase.ValidateResponse( results, nodesToRead );
ClientBase.ValidateDiagnosticInfos( diagnosticInfos, nodesToRead );
foreach (var item in results)
{
result.Add( (T)item.Value );
}
return result;
}
/// <summary>
/// read several value nodes from server
/// </summary>
/// <param name="tags">all NodeIds</param>
/// <returns>all values</returns>
public Task<List<T>> ReadNodesAsync<T>( string[] tags )
{
ReadValueIdCollection nodesToRead = new ReadValueIdCollection( );
for (int i = 0; i < tags.Length; i++)
{
nodesToRead.Add( new ReadValueId( )
{
NodeId = new NodeId( tags[i] ),
AttributeId = Attributes.Value
} );
}
var taskCompletionSource = new TaskCompletionSource<List<T>>( );
// 读取当前的值
m_session.BeginRead(
null,
0,
TimestampsToReturn.Neither,
nodesToRead,
callback: ar =>
{
DataValueCollection results;
DiagnosticInfoCollection diag;
var response = m_session.EndRead(
result: ar,
results: out results,
diagnosticInfos: out diag );
try
{
CheckReturnValue( response.ServiceResult );
List<T> result = new List<T>( );
foreach (var item in results)
{
result.Add( (T)item.Value );
}
taskCompletionSource.TrySetResult( result );
}
catch (Exception ex)
{
taskCompletionSource.TrySetException( ex );
}
},
asyncState: null );
return taskCompletionSource.Task;
}
/// <summary>
/// write a note to server(you should use try catch)
/// </summary>
/// <typeparam name="T">The type of tag to write on</typeparam>
/// <param name="tag">节点名称</param>
/// <param name="value">值</param>
/// <returns>if success True,otherwise False</returns>
public bool WriteNode<T>( string tag, T value )
{
WriteValue valueToWrite = new WriteValue( )
{
NodeId = new NodeId( tag ),
AttributeId = Attributes.Value
};
valueToWrite.Value.Value = value;
valueToWrite.Value.StatusCode = StatusCodes.Good;
valueToWrite.Value.ServerTimestamp = DateTime.MinValue;
valueToWrite.Value.SourceTimestamp = DateTime.MinValue;
WriteValueCollection valuesToWrite = new WriteValueCollection
{
valueToWrite
};
// 写入当前的值
m_session.Write(
null,
valuesToWrite,
out StatusCodeCollection results,
out DiagnosticInfoCollection diagnosticInfos );
ClientBase.ValidateResponse( results, valuesToWrite );
ClientBase.ValidateDiagnosticInfos( diagnosticInfos, valuesToWrite );
if (StatusCode.IsBad( results[0] ))
{
throw new ServiceResultException( results[0] );
}
return !StatusCode.IsBad( results[0] );
}
/// <summary>
/// Write a value on the specified opc tag asynchronously
/// </summary>
/// <typeparam name="T">The type of tag to write on</typeparam>
/// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name. E.g: the tag `foo.bar` writes on the tag `bar` on the folder `foo`</param>
/// <param name="value">The value for the item to write</param>
public Task<bool> WriteNodeAsync<T>( string tag, T value )
{
WriteValue valueToWrite = new WriteValue( )
{
NodeId = new NodeId( tag ),
AttributeId = Attributes.Value,
};
valueToWrite.Value.Value = value;
valueToWrite.Value.StatusCode = StatusCodes.Good;
valueToWrite.Value.ServerTimestamp = DateTime.MinValue;
valueToWrite.Value.SourceTimestamp = DateTime.MinValue;
WriteValueCollection valuesToWrite = new WriteValueCollection
{
valueToWrite
};
// Wrap the WriteAsync logic in a TaskCompletionSource, so we can use C# async/await syntax to call it:
var taskCompletionSource = new TaskCompletionSource<bool>( );
m_session.BeginWrite(
requestHeader: null,
nodesToWrite: valuesToWrite,
callback: ar =>
{
var response = m_session.EndWrite(
result: ar,
results: out StatusCodeCollection results,
diagnosticInfos: out DiagnosticInfoCollection diag );
try
{
ClientBase.ValidateResponse( results, valuesToWrite );
ClientBase.ValidateDiagnosticInfos( diag, valuesToWrite );
taskCompletionSource.SetResult( StatusCode.IsGood( results[0] ) );
}
catch (Exception ex)
{
taskCompletionSource.TrySetException( ex );
}
},
asyncState: null );
return taskCompletionSource.Task;
}
/// <summary>
/// 所有的节点都写入成功,返回<c>True</c>,否则返回<c>False</c>
/// </summary>
/// <param name="tags">节点名称数组</param>
/// <param name="values">节点的值数据</param>
/// <returns>所有的是否都写入成功</returns>
public bool WriteNodes( string[] tags, object[] values )
{
WriteValueCollection valuesToWrite = new WriteValueCollection( );
for (int i = 0; i < tags.Length; i++)
{
if (i < values.Length)
{
WriteValue valueToWrite = new WriteValue( )
{
NodeId = new NodeId( tags[i] ),
AttributeId = Attributes.Value
};
valueToWrite.Value.Value = values[i];
valueToWrite.Value.StatusCode = StatusCodes.Good;
valueToWrite.Value.ServerTimestamp = DateTime.MinValue;
valueToWrite.Value.SourceTimestamp = DateTime.MinValue;
valuesToWrite.Add( valueToWrite );
}
}
// 写入当前的值
m_session.Write(
null,
valuesToWrite,
out StatusCodeCollection results,
out DiagnosticInfoCollection diagnosticInfos );
ClientBase.ValidateResponse( results, valuesToWrite );
ClientBase.ValidateDiagnosticInfos( diagnosticInfos, valuesToWrite );
bool result = true;
foreach (var r in results)
{
if (StatusCode.IsBad( r ))
{
result = false;
break;
}
}
return result;
}
#endregion Node Write/Read Support
#region DeleteNode Support
/// <summary>
/// 删除一个节点的操作,除非服务器配置允许,否则引发异常,成功返回<c>True</c>,否则返回<c>False</c>
/// </summary>
/// <param name="tag">节点文本描述</param>
/// <returns>是否删除成功</returns>
public bool DeleteExsistNode( string tag )
{
DeleteNodesItemCollection waitDelete = new DeleteNodesItemCollection( );
DeleteNodesItem nodesItem = new DeleteNodesItem( )
{
NodeId = new NodeId( tag ),
};
m_session.DeleteNodes(
null,
waitDelete,
out StatusCodeCollection results,
out DiagnosticInfoCollection diagnosticInfos );
ClientBase.ValidateResponse( results, waitDelete );
ClientBase.ValidateDiagnosticInfos( diagnosticInfos, waitDelete );
return !StatusCode.IsBad( results[0] );
}
#endregion DeleteNode Support
#region Test Function
/// <summary>
/// 新增一个节点数据
/// </summary>
/// <param name="parent">父节点tag名称</param>
[Obsolete( "还未经过测试,无法使用" )]
public void AddNewNode( NodeId parent )
{
// Create a Variable node.
AddNodesItem node2 = new AddNodesItem( );
node2.ParentNodeId = new NodeId( parent );
node2.ReferenceTypeId = ReferenceTypes.HasComponent;
node2.RequestedNewNodeId = null;
node2.BrowseName = new QualifiedName( "DataVariable1" );
node2.NodeClass = NodeClass.Variable;
node2.NodeAttributes = null;
node2.TypeDefinition = VariableTypeIds.BaseDataVariableType;
//specify node attributes.
VariableAttributes node2Attribtues = new VariableAttributes( );
node2Attribtues.DisplayName = "DataVariable1";
node2Attribtues.Description = "DataVariable1 Description";
node2Attribtues.Value = new Variant( 123 );
node2Attribtues.DataType = (uint)BuiltInType.Int32;
node2Attribtues.ValueRank = ValueRanks.Scalar;
node2Attribtues.ArrayDimensions = new UInt32Collection( );
node2Attribtues.AccessLevel = AccessLevels.CurrentReadOrWrite;
node2Attribtues.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
node2Attribtues.MinimumSamplingInterval = 0;
node2Attribtues.Historizing = false;
node2Attribtues.WriteMask = (uint)AttributeWriteMask.None;
node2Attribtues.UserWriteMask = (uint)AttributeWriteMask.None;
node2Attribtues.SpecifiedAttributes = (uint)NodeAttributesMask.All;
node2.NodeAttributes = new ExtensionObject( node2Attribtues );
AddNodesItemCollection nodesToAdd = new AddNodesItemCollection { node2 };
m_session.AddNodes(
null,
nodesToAdd,
out AddNodesResultCollection results,
out DiagnosticInfoCollection diagnosticInfos );
ClientBase.ValidateResponse( results, nodesToAdd );
ClientBase.ValidateDiagnosticInfos( diagnosticInfos, nodesToAdd );
}
#endregion Test Function
#region Monitor Support
/// <summary>
/// 新增一个订阅需要指定订阅的关键字订阅的tag名以及回调方法
/// </summary>
/// <param name="key">关键字</param>
/// <param name="tag">tag</param>
/// <param name="callback">回调方法</param>
public void AddSubscription( string key, string tag, Action<string, MonitoredItem, MonitoredItemNotificationEventArgs> callback )
{
AddSubscription( key, new string[] { tag }, callback );
}
/// <summary>
/// 新增一批订阅需要指定订阅的关键字订阅的tag名数组以及回调方法
/// </summary>
/// <param name="key">关键字</param>
/// <param name="tags">节点名称数组</param>
/// <param name="callback">回调方法</param>
public void AddSubscription( string key, string[] tags, Action<string, MonitoredItem, MonitoredItemNotificationEventArgs> callback )
{
Subscription m_subscription = new Subscription( m_session.DefaultSubscription );
m_subscription.PublishingEnabled = true;
m_subscription.PublishingInterval = 0;
m_subscription.KeepAliveCount = uint.MaxValue;
m_subscription.LifetimeCount = uint.MaxValue;
m_subscription.MaxNotificationsPerPublish = uint.MaxValue;
m_subscription.Priority = 100;
m_subscription.DisplayName = key;
for (int i = 0; i < tags.Length; i++)
{
var item = new MonitoredItem
{
StartNodeId = new NodeId( tags[i] ),
AttributeId = Attributes.Value,
DisplayName = tags[i],
SamplingInterval = 100,
};
item.Notification += ( MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs args ) =>
{
callback?.Invoke( key, monitoredItem, args );
};
m_subscription.AddItem( item );
}
m_session.AddSubscription( m_subscription );
m_subscription.Create( );
lock (dic_subscriptions)
{
if (dic_subscriptions.ContainsKey( key ))
{
// remove
dic_subscriptions[key].Delete( true );
m_session.RemoveSubscription( dic_subscriptions[key] );
dic_subscriptions[key].Dispose( );
dic_subscriptions[key] = m_subscription;
}
else
{
dic_subscriptions.Add( key, m_subscription );
}
}
}
/// <summary>
/// 移除订阅消息,如果该订阅消息是批量的,也直接移除
/// </summary>
/// <param name="key">订阅关键值</param>
public void RemoveSubscription( string key )
{
lock (dic_subscriptions)
{
if (dic_subscriptions.ContainsKey( key ))
{
// remove
dic_subscriptions[key].Delete( true );
m_session.RemoveSubscription( dic_subscriptions[key] );
dic_subscriptions[key].Dispose( );
dic_subscriptions.Remove( key );
}
}
}
/// <summary>
/// 移除所有的订阅消息
/// </summary>
public void RemoveAllSubscription( )
{
lock (dic_subscriptions)
{
foreach (var item in dic_subscriptions)
{
item.Value.Delete( true );
m_session.RemoveSubscription( item.Value );
item.Value.Dispose( );
}
dic_subscriptions.Clear( );
}
}
#endregion Monitor Support
#region ReadHistory Support
/// <summary>
/// read History data
/// </summary>
/// <param name="tag">节点的索引</param>
/// <param name="start">开始时间</param>
/// <param name="end">结束时间</param>
/// <param name="count">读取的个数</param>
/// <param name="containBound">是否包含边界</param>
/// <returns>读取的数据列表</returns>
public IEnumerable<DataValue> ReadHistoryRawDataValues( string tag, DateTime start, DateTime end, uint count = 1, bool containBound = false )
{
HistoryReadValueId m_nodeToContinue = new HistoryReadValueId( )
{
NodeId = new NodeId( tag ),
};
ReadRawModifiedDetails m_details = new ReadRawModifiedDetails
{
StartTime = start,
EndTime = end,
NumValuesPerNode = count,
IsReadModified = false,
ReturnBounds = containBound
};
HistoryReadValueIdCollection nodesToRead = new HistoryReadValueIdCollection( );
nodesToRead.Add( m_nodeToContinue );
m_session.HistoryRead(
null,
new ExtensionObject( m_details ),
TimestampsToReturn.Both,
false,
nodesToRead,
out HistoryReadResultCollection results,
out DiagnosticInfoCollection 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;
foreach (var value in values.DataValues)
{
yield return value;
}
}
/// <summary>
/// 读取一连串的历史数据,并将其转化成指定的类型
/// </summary>
/// <param name="tag">节点的索引</param>
/// <param name="start">开始时间</param>
/// <param name="end">结束时间</param>
/// <param name="count">读取的个数</param>
/// <param name="containBound">是否包含边界</param>
/// <returns>读取的数据列表</returns>
public IEnumerable<T> ReadHistoryRawDataValues<T>( string tag, DateTime start, DateTime end, uint count = 1, bool containBound = false )
{
HistoryReadValueId m_nodeToContinue = new HistoryReadValueId( )
{
NodeId = new NodeId( tag ),
};
ReadRawModifiedDetails m_details = new ReadRawModifiedDetails
{
StartTime = start.ToUniversalTime( ),
EndTime = end.ToUniversalTime( ),
NumValuesPerNode = count,
IsReadModified = false,
ReturnBounds = containBound
};
HistoryReadValueIdCollection nodesToRead = new HistoryReadValueIdCollection( );
nodesToRead.Add( m_nodeToContinue );
m_session.HistoryRead(
null,
new ExtensionObject( m_details ),
TimestampsToReturn.Both,
false,
nodesToRead,
out HistoryReadResultCollection results,
out DiagnosticInfoCollection 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;
foreach (var value in values.DataValues)
{
yield return (T)value.Value;
}
}
#endregion ReadHistory Support
#region BrowseNode Support
/// <summary>
/// 浏览一个节点的引用
/// </summary>
/// <param name="tag">节点值</param>
/// <returns>引用节点描述</returns>
public ReferenceDescription[] BrowseNodeReference( string tag )
{
NodeId sourceId = new NodeId( tag );
// 该节点可以读取到方法
BrowseDescription nodeToBrowse1 = new BrowseDescription( );
nodeToBrowse1.NodeId = sourceId;
nodeToBrowse1.BrowseDirection = BrowseDirection.Forward;
nodeToBrowse1.ReferenceTypeId = ReferenceTypeIds.Aggregates;
nodeToBrowse1.IncludeSubtypes = true;
nodeToBrowse1.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable | NodeClass.Method);
nodeToBrowse1.ResultMask = (uint)BrowseResultMask.All;
// 该节点无论怎么样都读取不到方法
// find all nodes organized by the node.
BrowseDescription nodeToBrowse2 = new BrowseDescription( );
nodeToBrowse2.NodeId = sourceId;
nodeToBrowse2.BrowseDirection = BrowseDirection.Forward;
nodeToBrowse2.ReferenceTypeId = ReferenceTypeIds.Organizes;
nodeToBrowse2.IncludeSubtypes = true;
nodeToBrowse2.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable);
nodeToBrowse2.ResultMask = (uint)BrowseResultMask.All;
BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection( );
nodesToBrowse.Add( nodeToBrowse1 );
nodesToBrowse.Add( nodeToBrowse2 );
// fetch references from the server.
ReferenceDescriptionCollection references = FormUtils.Browse( m_session, nodesToBrowse, false );
return references.ToArray( );
}
#endregion BrowseNode Support
#region Read Attributes Support
/// <summary>
/// 读取一个节点的所有属性
/// </summary>
/// <param name="tag">节点信息</param>
/// <returns>节点的特性值</returns>
public OpcNodeAttribute[] ReadNoteAttributes( string tag )
{
NodeId sourceId = new NodeId( tag );
ReadValueIdCollection nodesToRead = new ReadValueIdCollection( );
// attempt to read all possible attributes.
// 尝试着去读取所有可能的特性
for (uint ii = Attributes.NodeClass; ii <= Attributes.UserExecutable; ii++)
{
ReadValueId nodeToRead = new ReadValueId( );
nodeToRead.NodeId = sourceId;
nodeToRead.AttributeId = ii;
nodesToRead.Add( nodeToRead );
}
int startOfProperties = nodesToRead.Count;
// find all of the pror of the node.
BrowseDescription nodeToBrowse1 = new BrowseDescription( );
nodeToBrowse1.NodeId = sourceId;
nodeToBrowse1.BrowseDirection = BrowseDirection.Forward;
nodeToBrowse1.ReferenceTypeId = ReferenceTypeIds.HasProperty;
nodeToBrowse1.IncludeSubtypes = true;
nodeToBrowse1.NodeClassMask = 0;
nodeToBrowse1.ResultMask = (uint)BrowseResultMask.All;
BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection( );
nodesToBrowse.Add( nodeToBrowse1 );
// fetch property references from the server.
ReferenceDescriptionCollection references = FormUtils.Browse( m_session, nodesToBrowse, false );
if (references == null)
{
return new OpcNodeAttribute[0];
}
for (int ii = 0; ii < references.Count; ii++)
{
// ignore external references.
if (references[ii].NodeId.IsAbsolute)
{
continue;
}
ReadValueId nodeToRead = new ReadValueId( );
nodeToRead.NodeId = (NodeId)references[ii].NodeId;
nodeToRead.AttributeId = Attributes.Value;
nodesToRead.Add( nodeToRead );
}
// read all values.
DataValueCollection results = null;
DiagnosticInfoCollection diagnosticInfos = null;
m_session.Read(
null,
0,
TimestampsToReturn.Neither,
nodesToRead,
out results,
out diagnosticInfos );
ClientBase.ValidateResponse( results, nodesToRead );
ClientBase.ValidateDiagnosticInfos( diagnosticInfos, nodesToRead );
// process results.
List<OpcNodeAttribute> nodeAttribute = new List<OpcNodeAttribute>( );
for (int ii = 0; ii < results.Count; ii++)
{
OpcNodeAttribute item = new OpcNodeAttribute( );
// 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; //Utils.Format("{0}", results[ii].Value);
}
}
nodeAttribute.Add( item );
}
return nodeAttribute.ToArray( );
}
/// <summary>
/// 读取一个节点的所有属性
/// </summary>
/// <param name="tag">节点值</param>
/// <returns>所有的数据</returns>
public DataValue[] ReadNoteDataValueAttributes( string tag )
{
NodeId sourceId = new NodeId( tag );
ReadValueIdCollection nodesToRead = new ReadValueIdCollection( );
// attempt to read all possible attributes.
// 尝试着去读取所有可能的特性
for (uint ii = Attributes.NodeId; ii <= Attributes.UserExecutable; ii++)
{
ReadValueId nodeToRead = new ReadValueId( );
nodeToRead.NodeId = sourceId;
nodeToRead.AttributeId = ii;
nodesToRead.Add( nodeToRead );
}
int startOfProperties = nodesToRead.Count;
// find all of the pror of the node.
BrowseDescription nodeToBrowse1 = new BrowseDescription( );
nodeToBrowse1.NodeId = sourceId;
nodeToBrowse1.BrowseDirection = BrowseDirection.Forward;
nodeToBrowse1.ReferenceTypeId = ReferenceTypeIds.HasProperty;
nodeToBrowse1.IncludeSubtypes = true;
nodeToBrowse1.NodeClassMask = 0;
nodeToBrowse1.ResultMask = (uint)BrowseResultMask.All;
BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection( );
nodesToBrowse.Add( nodeToBrowse1 );
// fetch property references from the server.
ReferenceDescriptionCollection references = FormUtils.Browse( m_session, nodesToBrowse, false );
if (references == null)
{
return new DataValue[0];
}
for (int ii = 0; ii < references.Count; ii++)
{
// ignore external references.
if (references[ii].NodeId.IsAbsolute)
{
continue;
}
ReadValueId nodeToRead = new ReadValueId( );
nodeToRead.NodeId = (NodeId)references[ii].NodeId;
nodeToRead.AttributeId = Attributes.Value;
nodesToRead.Add( nodeToRead );
}
// read all values.
DataValueCollection results = null;
DiagnosticInfoCollection diagnosticInfos = null;
m_session.Read(
null,
0,
TimestampsToReturn.Neither,
nodesToRead,
out results,
out diagnosticInfos );
ClientBase.ValidateResponse( results, nodesToRead );
ClientBase.ValidateDiagnosticInfos( diagnosticInfos, nodesToRead );
return results.ToArray( );
}
#endregion Read Attributes Support
#region Method Call Support
/// <summary>
/// call a server method
/// </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( );
}
#endregion Method Call Support
#region Private Methods
/// <summary>
/// Raises the connect complete event on the main GUI thread.
/// </summary>
private void DoConnectComplete( object state )
{
m_ConnectComplete?.Invoke( this, null );
}
private void CheckReturnValue( StatusCode status )
{
if (!StatusCode.IsGood( status ))
throw new Exception( string.Format( "Invalid response from the server. (Response Status: {0})", status ) );
}
#endregion Private Methods
#region Private Fields
private ApplicationConfiguration m_configuration;
private Session m_session;
private bool m_IsConnected; //是否已经连接过
private int m_reconnectPeriod = 10; // 重连状态
private bool m_useSecurity;
private SessionReconnectHandler m_reConnectHandler;
private EventHandler m_ReconnectComplete;
private EventHandler m_ReconnectStarting;
private EventHandler m_KeepAliveComplete;
private EventHandler m_ConnectComplete;
private EventHandler<OpcUaStatusEventArgs> m_OpcStatusChange;
private Dictionary<string, Subscription> dic_subscriptions; // 系统所有的节点信息
#endregion Private Fields
}
}