using Opc.Ua; using Opc.Ua.Client; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace OpcUaHelper { /// /// 一个二次封装了的OPC UA库,支持从opc ua服务器读写节点数据,批量读写,订阅,批量订阅,历史数据读取,方法调用操作。 /// public class OpcUaClientHelper { #region Constructors /// /// 默认的构造函数,实例化一个新的OPC UA类 /// public OpcUaClientHelper( ) { dic_subscriptions = new Dictionary( ); 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 /// /// connect to server /// /// remote url public async Task ConnectServer( string serverUrl ) { m_session = await Connect( serverUrl ); } /// /// Creates a new session. /// /// The new session object. private async Task 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; } /// /// Disconnects from the server. /// 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 /// /// Report the client status /// /// Whether the status represents an error. /// The time associated with the status. /// The status message. /// Arguments used to format the status message. 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 ), } ); } /// /// Handles a keep alive event from a session. /// 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; } } /// /// Handles a reconnect event complete from the reconnect handler. /// 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 /// /// 设置OPC客户端的日志输出 /// /// 完整的文件路径 /// 是否删除原文件 public void SetLogPathName( string filePath, bool deleteExisting ) { Utils.SetTraceLog( filePath, deleteExisting ); Utils.SetTraceMask( 515 ); } #endregion LogOut Setting #region Public Members /// /// a name of application name show on server /// public string OpcUaName { get; set; } = "Opc Ua Helper"; /// /// Whether to use security when connecting. /// public bool UseSecurity { get { return m_useSecurity; } set { m_useSecurity = value; } } /// /// The user identity to use when creating the session. /// public IUserIdentity UserIdentity { get; set; } /// /// The currently active session. /// public Session Session { get { return m_session; } } /// /// Indicate the connect status /// public bool Connected { get { return m_IsConnected; } } /// /// The number of seconds between reconnect attempts (0 means reconnect is disabled). /// public int ReconnectPeriod { get { return m_reconnectPeriod; } set { m_reconnectPeriod = value; } } /// /// Raised when a good keep alive from the server arrives. /// public event EventHandler KeepAliveComplete { add { m_KeepAliveComplete += value; } remove { m_KeepAliveComplete -= value; } } /// /// Raised when a reconnect operation starts. /// public event EventHandler ReconnectStarting { add { m_ReconnectStarting += value; } remove { m_ReconnectStarting -= value; } } /// /// Raised when a reconnect operation completes. /// public event EventHandler ReconnectComplete { add { m_ReconnectComplete += value; } remove { m_ReconnectComplete -= value; } } /// /// Raised after successfully connecting to or disconnecing from a server. /// public event EventHandler ConnectComplete { add { m_ConnectComplete += value; } remove { m_ConnectComplete -= value; } } /// /// Raised after the client status change /// public event EventHandler OpcStatusChange { add { m_OpcStatusChange += value; } remove { m_OpcStatusChange -= value; } } /// /// 配置信息 /// public ApplicationConfiguration AppConfig => m_configuration; #endregion Public Members #region Node Write/Read Support /// /// Read a value node from server /// /// node id /// DataValue 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]; } /// /// Read a value node from server /// /// type of value /// node id /// 实际值 public T ReadNode( string tag ) { DataValue dataValue = ReadNode( new NodeId( tag ) ); return (T)dataValue.Value; } /// /// Read a tag asynchronously /// /// The type of tag to read /// tag值 /// The value retrieved from the OPC public Task ReadNodeAsync( 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( ); 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; } /// /// read several value nodes from server /// /// all NodeIds /// all values public List 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( ); } /// /// read several value nodes from server /// /// all NodeIds /// all values public Task> 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>( ); // 读取当前的值 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; } /// /// read several value nodes from server /// /// 所以的节点数组信息 /// all values public List ReadNodes( string[] tags ) { List result = new List( ); 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; } /// /// read several value nodes from server /// /// all NodeIds /// all values public Task> ReadNodesAsync( 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>( ); // 读取当前的值 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 result = new List( ); foreach (var item in results) { result.Add( (T)item.Value ); } taskCompletionSource.TrySetResult( result ); } catch (Exception ex) { taskCompletionSource.TrySetException( ex ); } }, asyncState: null ); return taskCompletionSource.Task; } /// /// write a note to server(you should use try catch) /// /// The type of tag to write on /// 节点名称 /// 值 /// if success True,otherwise False public bool WriteNode( 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] ); } /// /// Write a value on the specified opc tag asynchronously /// /// The type of tag to write on /// 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` /// The value for the item to write public Task WriteNodeAsync( 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( ); 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; } /// /// 所有的节点都写入成功,返回True,否则返回False /// /// 节点名称数组 /// 节点的值数据 /// 所有的是否都写入成功 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 /// /// 删除一个节点的操作,除非服务器配置允许,否则引发异常,成功返回True,否则返回False /// /// 节点文本描述 /// 是否删除成功 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 /// /// 新增一个节点数据 /// /// 父节点tag名称 [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 /// /// 新增一个订阅,需要指定订阅的关键字,订阅的tag名,以及回调方法 /// /// 关键字 /// tag /// 回调方法 public void AddSubscription( string key, string tag, Action callback ) { AddSubscription( key, new string[] { tag }, callback ); } /// /// 新增一批订阅,需要指定订阅的关键字,订阅的tag名数组,以及回调方法 /// /// 关键字 /// 节点名称数组 /// 回调方法 public void AddSubscription( string key, string[] tags, Action 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 ); } } } /// /// 移除订阅消息,如果该订阅消息是批量的,也直接移除 /// /// 订阅关键值 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 ); } } } /// /// 移除所有的订阅消息 /// 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 /// /// read History data /// /// 节点的索引 /// 开始时间 /// 结束时间 /// 读取的个数 /// 是否包含边界 /// 读取的数据列表 public IEnumerable 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; } } /// /// 读取一连串的历史数据,并将其转化成指定的类型 /// /// 节点的索引 /// 开始时间 /// 结束时间 /// 读取的个数 /// 是否包含边界 /// 读取的数据列表 public IEnumerable 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.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 /// /// 浏览一个节点的引用 /// /// 节点值 /// 引用节点描述 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 /// /// 读取一个节点的所有属性 /// /// 节点信息 /// 节点的特性值 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 nodeAttribute = new List( ); 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( ); } /// /// 读取一个节点的所有属性 /// /// 节点值 /// 所有的数据 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 /// /// call a server method /// /// 方法的父节点tag /// 方法的节点tag /// 传递的参数 /// 输出的结果值 public object[] CallMethodByNodeId( string tagParent, string tag, params object[] args ) { if (m_session == null) { return null; } IList outputArguments = m_session.Call( new NodeId( tagParent ), new NodeId( tag ), args ); return outputArguments.ToArray( ); } #endregion Method Call Support #region Private Methods /// /// Raises the connect complete event on the main GUI thread. /// 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 m_OpcStatusChange; private Dictionary dic_subscriptions; // 系统所有的节点信息 #endregion Private Fields } }