using System; using System.Collections; using System.Security.Cryptography.X509Certificates; using Opc.Ua; using Opc.Ua.Client; using Siemens.OpcUA.Helper; namespace Siemens.OpcUA { /// /// Event handler for the event that the client received a value change. /// public delegate void valueChanged(object clientHandle, DataValue value); /// /// Event handler for the event that the client received a untrusted server certificate. /// public delegate void certificateValidation(CertificateValidator validator, CertificateValidationEventArgs e); /// /// This class encapsulates a connection to an OPC UA server and access to particular Services of it. /// public class Server { public object lockThis = new object(); #region Construction public Server() { } #endregion #region Fields /// /// Keeps a session with an UA server. /// private Session m_Session = null; /// /// Interface which encapsulates the use of the browse service of an UA server. /// private Browser m_Browser = null; /// /// Keeps a hash table for attribute names. /// private Hashtable m_hashAttributeNames = null; #endregion #region Properties /// /// Use the certificateValidation delegate as event. /// public event certificateValidation CertificateEvent = null; /// /// Provides the session being established with an OPC UA server. /// public Session Session { get { return m_Session; } } #endregion #region Connect /// Establishes the connection to an OPC UA server. /// The Url of the endpoint. /// Result code. /// Throws and forwards any exception with short error description. public void Connect(string Url) { try { // Create the configuration. ApplicationConfiguration configuration = Helpers.CreateClientConfiguration(); // Create the endpoint description. EndpointDescription endpointDescription = Helpers.CreateEndpointDescription(Url); // Create the endpoint configuration (use the application configuration to provide default values). EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(configuration); // The default timeout for a requests sent using the channel. endpointConfiguration.OperationTimeout = 300000; // Use the pure binary encoding on the wire. endpointConfiguration.UseBinaryEncoding = true; // Create the endpoint. ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration); // Create the binding factory. BindingFactory bindingFactory = BindingFactory.Create(configuration); // Update endpoint description using the discovery endpoint. if (endpoint.UpdateBeforeConnect) { endpoint.UpdateFromServer(bindingFactory); endpointDescription = endpoint.Description; endpointConfiguration = endpoint.Configuration; } X509Certificate2 clientCertificate = configuration.SecurityConfiguration.ApplicationCertificate.Find(); // Set up a callback to handle certificate validation errors. configuration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_CertificateValidation); // Initialize the channel which will be created with the server. SessionChannel channel = SessionChannel.Create( configuration, endpointDescription, endpointConfiguration, bindingFactory, clientCertificate, null); // Wrap the channel with the session object. // This call will fail if the server does not trust the client certificate. m_Session = new Session(channel, configuration, endpoint); m_Session.ReturnDiagnostics = DiagnosticsMasks.All; // Register keep alive callback. m_Session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive); UserIdentity identity = new UserIdentity(); // Create the session. This actually connects to the server. // Passing null for the user identity will create an anonymous session. m_Session.Open("Siemens OPC UA sample client - main session", identity); } catch (Exception e) { throw e; } } /// Establishes the connection to an OPC UA server. /// The Url of the endpoint. /// Result code. /// Throws and forwards any exception with short error description. public void Connect(EndpointDescription endpointDescription) { try { // Create the configuration. ApplicationConfiguration configuration = Helpers.CreateClientConfiguration(); // Create the endpoint configuration (use the application configuration to provide default values). EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(configuration); // The default timeout for a requests sent using the channel. endpointConfiguration.OperationTimeout = 300000; // Use the pure binary encoding on the wire. endpointConfiguration.UseBinaryEncoding = true; // Create the endpoint. ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration); // Create the binding factory. BindingFactory bindingFactory = BindingFactory.Create(configuration); X509Certificate2 clientCertificate = configuration.SecurityConfiguration.ApplicationCertificate.Find(); // Set up a callback to handle certificate validation errors. configuration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_CertificateValidation); // Initialize the channel which will be created with the server. SessionChannel channel = SessionChannel.Create( configuration, endpointDescription, endpointConfiguration, bindingFactory, clientCertificate, null); // Wrap the channel with the session object. // This call will fail if the server does not trust the client certificate. m_Session = new Session(channel, configuration, endpoint); m_Session.ReturnDiagnostics = DiagnosticsMasks.All; // Register keep alive callback. m_Session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive); UserIdentity identity = new UserIdentity(); // Create the session. This actually connects to the server. // Passing null for the user identity will create an anonymous session. m_Session.Open("Siemens OPC UA sample client - main session", identity); } catch (Exception e) { throw e; } } /// Releases the connection to an OPC UA server. /// Result code. /// Throws and forwards any exception with short error description. public int Disconnect() { int result = 0; // Close the session. try { m_Session.Close(10000); } catch (Exception e) { return -1; throw e; } // Delete browser. m_Browser = null; return result; } #endregion #region Browse /// /// Navigates through the address space of an OPC UA server. /// /// The starting node. /// A list of results for the passed starting node and filters. /// Result code. /// Throws and forwards any exception with short error description. public int Browse(NodeId nodeId, out ReferenceDescriptionCollection browseResults) { int result = 0; if (m_Browser == null) { // Instantiate browser. m_Browser = new Browser(m_Session); } // Browse filter: only follow hierarchical references. m_Browser.ReferenceTypeId = ReferenceTypes.HierarchicalReferences; browseResults = null; try { // The actual call to the server. browseResults = m_Browser.Browse(nodeId); } catch (Exception e) { result = -1; throw e; } return result; } #endregion #region Read / Write /// /// Reads any attribute of one or more nodes. /// /// A list of nodes to read. /// A list of read results. /// Throws and forwards any exception with short error description. public void Read( ReadValueIdCollection nodesToRead, out DataValueCollection results) { results = null; DiagnosticInfoCollection diagnosticInfos = null; try { // The actual call to the server. m_Session.Read( null, 0, TimestampsToReturn.Both, nodesToRead, out results, out diagnosticInfos); } catch (Exception e) { throw e; } } /// /// Reads the value attribute of one or more nodes. /// /// A list of nodes to read. /// A list of data values. /// Throws and forwards any exception with short error description. public ResponseHeader ReadValues( NodeIdCollection nodesToRead, out DataValueCollection results) { ResponseHeader response = null; results = null; DiagnosticInfoCollection diagnosticInfos; // build list of attributes to read. ReadValueIdCollection valueIdsToRead = new ReadValueIdCollection(); try { for (int i = 0; i < nodesToRead.Count; i++) { ReadValueId attributeToRead = new ReadValueId(); attributeToRead.NodeId = nodesToRead[i]; attributeToRead.AttributeId = Attributes.Value; attributeToRead.Handle = attributeIdToString(Attributes.Value); valueIdsToRead.Add(attributeToRead); } response = m_Session.Read( null, 0, TimestampsToReturn.Both, valueIdsToRead, out results, out diagnosticInfos); } catch (Exception e) { throw e; } return response; } /// /// Writes the Value attribute of one or more nodes. /// /// A list of nodes to write. /// A list of values to write. /// Throws and forwards any exception with short error description. public void WriteValues( NodeIdCollection nodesToWrite, DataValueCollection values, out StatusCodeCollection results) { results = null; DiagnosticInfoCollection diagnosticInfos = null; // Build list of attributes to write. WriteValueCollection valuesToWrite = new WriteValueCollection(); try { for (int i = 0; i < nodesToWrite.Count; i++) { WriteValue attributeToWrite = new WriteValue(); attributeToWrite.NodeId = nodesToWrite[i]; // We have to write the Value attribute only: attributeToWrite.AttributeId = Attributes.Value; attributeToWrite.Handle = attributeIdToString(Attributes.Value); // Set the according value: attributeToWrite.Value = values[i]; valuesToWrite.Add(attributeToWrite); } // Actual call to server. m_Session.Write( null, valuesToWrite, out results, out diagnosticInfos); } catch (Exception e) { throw e; } } #endregion #region 20220518 asynchronous Writes /// /// asynchronous Writes the Value attribute of one or more nodes. /// /// A list of nodes to write. /// A list of values to write. /// Throws and forwards any exception with short error description. public void AsyWriteValues( NodeIdCollection nodesToWrite, DataValueCollection values, out StatusCodeCollection results) { results = null; //DiagnosticInfoCollection diagnosticInfos = null; // Build list of attributes to write. WriteValueCollection valuesToWrite = new WriteValueCollection(); try { for (int i = 0; i < nodesToWrite.Count; i++) { WriteValue attributeToWrite = new WriteValue(); attributeToWrite.NodeId = nodesToWrite[i]; // We have to write the Value attribute only: attributeToWrite.AttributeId = Attributes.Value; attributeToWrite.Handle = attributeIdToString(Attributes.Value); // Set the according value: attributeToWrite.Value = values[i]; valuesToWrite.Add(attributeToWrite); } // Actual call to server. AsyWriteValues m_Session.BeginWrite( null, valuesToWrite, null, // miss the call back function null); // miss the call back function } catch (Exception e) { throw e; } } #endregion #region Subscribe for Data Changes /// /// Creates a subscription and associate it with the current session. /// /// The requested publishing interval. /// Instance of class Subscription. /// Throws and forwards any exception with short error description. public Subscription AddSubscription(int publishingInterval) { Subscription newSubscription = null; try { Opc.Ua.Client.Subscription innerSubscription = new Opc.Ua.Client.Subscription(m_Session.DefaultSubscription); innerSubscription.DisplayName = "My Subscription Name"; innerSubscription.PublishingEnabled = true; innerSubscription.PublishingInterval = publishingInterval; // in milliseconds. innerSubscription.KeepAliveCount = 10; // 10*UaRefreshRate = 5s if UaRefreshRate = 500 innerSubscription.LifetimeCount = 100; // UaRefreshRate*100 = 50s if UaRefreshRate = 500; innerSubscription.MaxNotificationsPerPublish = 1000;//每次订阅返回,订阅组最大数的限制//20151120 // Associate the subscription with the session. m_Session.AddSubscription(innerSubscription); // Call the server and create the subscription. innerSubscription.Create(); // At this point the subscription is sending publish requests at the keep alive rate. // Use the Notification event the session to receive updates when a publish completes. m_Session.Notification += new NotificationEventHandler(Session_Notification); newSubscription = new Subscription(); newSubscription.Session = m_Session; newSubscription.innerSubscription = innerSubscription; } catch (Exception e) { throw e; } return newSubscription; } // Modifies the publishing interval of a subscription. /// /// Creates a subscription and associates it with the current session. /// /// The subscription object. /// The newly requested publishing interval. /// Throws and forwards any exception with short error description. public void ModifySubscription(Subscription subscription,int publishingInterval) { try { // Modify publishing interval subscription.innerSubscription.PublishingInterval = publishingInterval; // Apply change subscription.innerSubscription.Modify(); } catch (Exception e) { throw e; } } /// /// Removes a subscription from the session. /// /// The subscription object. /// Throws and forwards any exception with short error description. public void RemoveSubscription(Subscription subscription) { try { // Remove the subscription from the corresponding session. m_Session.RemoveSubscription(subscription.innerSubscription); } catch (Exception e) { throw e; } } #endregion #region Event Handlers /// /// Will be raised when a certificate is classified as untrusted. /// /// The event handler. /// The Instance containing the event data. private void CertificateValidator_CertificateValidation(CertificateValidator validator, CertificateValidationEventArgs e) { if (CertificateEvent != null) { CertificateEvent(validator, e); } else { e.Accept = false; } } /// /// Keep-alive callback. /// /// The target of the event. /// The Instance containing the event data. private void Session_KeepAlive(Session session, KeepAliveEventArgs e) { } /// /// Raised when a publish response arrives from the server. /// /// The target of the event. /// The Instance containing the event data. private void Session_Notification(Session session, NotificationEventArgs e) { lock (lockThis) { NotificationMessage message = e.NotificationMessage; // Check for keep alive. if (message.NotificationData.Count == 0) { return; } // Get the data changes (oldest to newest). foreach (MonitoredItemNotification datachange in message.GetDataChanges(false)) { // Lookup the monitored item. MonitoredItem monitoredItem = e.Subscription.FindItemByClientHandle(datachange.ClientHandle); if (monitoredItem == null) { continue; } ClientMonitoredItemData clientData = monitoredItem.Handle as ClientMonitoredItemData; clientData.callback(clientData.clientHandle, datachange.Value); } } } #endregion #region Helper Methods /// /// Converts an attribute id to string. /// /// The attribute id. /// The attribute name as string. public string attributeIdToString(uint attributeId) { // Populate hashtable if called for the first time if (m_hashAttributeNames == null) { m_hashAttributeNames = new Hashtable(); m_hashAttributeNames.Add(Attributes.AccessLevel, "AccessLevel"); m_hashAttributeNames.Add(Attributes.ArrayDimensions, "ArrayDimensions"); m_hashAttributeNames.Add(Attributes.BrowseName, "BrowseName"); m_hashAttributeNames.Add(Attributes.ContainsNoLoops, "ContainsNoLoops"); m_hashAttributeNames.Add(Attributes.DataType, "DataType"); m_hashAttributeNames.Add(Attributes.Description, "Description"); m_hashAttributeNames.Add(Attributes.DisplayName, "DisplayName"); m_hashAttributeNames.Add(Attributes.EventNotifier, "EventNotifier"); m_hashAttributeNames.Add(Attributes.Executable, "Executable"); m_hashAttributeNames.Add(Attributes.Historizing, "Historizing"); m_hashAttributeNames.Add(Attributes.InverseName, "InverseName"); m_hashAttributeNames.Add(Attributes.IsAbstract, "IsAbstract"); m_hashAttributeNames.Add(Attributes.MinimumSamplingInterval, "MinimumSamplingInterval"); m_hashAttributeNames.Add(Attributes.NodeClass, "NodeClass"); m_hashAttributeNames.Add(Attributes.NodeId, "NodeId"); m_hashAttributeNames.Add(Attributes.Symmetric, "Symmetric"); m_hashAttributeNames.Add(Attributes.UserAccessLevel, "UserAccessLevel"); m_hashAttributeNames.Add(Attributes.UserExecutable, "UserExecutable"); m_hashAttributeNames.Add(Attributes.UserWriteMask, "UserWriteMask"); m_hashAttributeNames.Add(Attributes.Value, "Value"); m_hashAttributeNames.Add(Attributes.ValueRank, "ValueRank"); m_hashAttributeNames.Add(Attributes.WriteMask, "WriteMask"); } string ret = (string)m_hashAttributeNames[attributeId]; return ret; } // /// /// Converts an attribute value to an user readable string. /// /// Contains the attriute id. /// The value to convert. /// The attribute value as string. public string attributeValueToString(ReadValueId nodeRead, DataValue result) { if (result == null || result.Value == null) { return ""; } string ret = ""; switch (nodeRead.AttributeId) { case Attributes.AccessLevel: case Attributes.UserAccessLevel: { // Check datatype. if (result.Value.GetType() == typeof(byte)) { if (((byte)result.Value & AccessLevels.CurrentRead) == AccessLevels.CurrentRead) { ret = "Readable"; } if (((byte)result.Value & AccessLevels.CurrentWrite) == AccessLevels.CurrentWrite) { if (ret.Length > 0) { ret += ", "; } ret += "Writeable"; } if (((byte)result.Value & AccessLevels.HistoryRead) == AccessLevels.HistoryRead) { if (ret.Length > 0) { ret += ", "; } ret += "HistoryReadable"; } if (((byte)result.Value & AccessLevels.HistoryWrite) == AccessLevels.HistoryWrite) { if (ret.Length > 0) { ret += ", "; } ret += "HistoryWriteable"; } if (ret.Length == 0) { ret = "None"; } } else { ret = "Invalid Datatype: Expected Byte, Received " + result.Value.GetType().ToString(); } } break; case Attributes.WriteMask: case Attributes.UserWriteMask: { // Check datatype. if (result.Value.GetType() == typeof(uint)) { if (((uint)result.Value & (uint)AttributeWriteMask.AccessLevel) == (uint)AttributeWriteMask.AccessLevel) { ret = "AccessLevel"; } if (((uint)result.Value & (uint)AttributeWriteMask.BrowseName) == (uint)AttributeWriteMask.BrowseName) { if (ret.Length > 0) { ret += ", "; } ret += "BrowseName"; } if (((uint)result.Value & (uint)AttributeWriteMask.ContainsNoLoops) == (uint)AttributeWriteMask.ContainsNoLoops) { if (ret.Length > 0) { ret += ", "; } ret += "ContainsNoLoops"; } if (((uint)result.Value & (uint)AttributeWriteMask.DataType) == (uint)AttributeWriteMask.DataType) { if (ret.Length > 0) { ret += ", "; } ret += "DataType"; } if (((uint)result.Value & (uint)AttributeWriteMask.Description) == (uint)AttributeWriteMask.Description) { if (ret.Length > 0) { ret += ", "; } ret += "Description"; } if (((uint)result.Value & (uint)AttributeWriteMask.DisplayName) == (uint)AttributeWriteMask.DisplayName) { if (ret.Length > 0) { ret += ", "; } ret += "DisplayName"; } if (((uint)result.Value & (uint)AttributeWriteMask.EventNotifier) == (uint)AttributeWriteMask.EventNotifier) { if (ret.Length > 0) { ret += ", "; } ret += "EventNotifier"; } if (((uint)result.Value & (uint)AttributeWriteMask.Executable) == (uint)AttributeWriteMask.Executable) { if (ret.Length > 0) { ret += ", "; } ret += "Executable"; } if (((uint)result.Value & (uint)AttributeWriteMask.Historizing) == (uint)AttributeWriteMask.Historizing) { if (ret.Length > 0) { ret += ", "; } ret += "Historizing"; } if (((uint)result.Value & (uint)AttributeWriteMask.InverseName) == (uint)AttributeWriteMask.InverseName) { if (ret.Length > 0) { ret += ", "; } ret += "InverseName"; } if (((uint)result.Value & (uint)AttributeWriteMask.IsAbstract) == (uint)AttributeWriteMask.IsAbstract) { if (ret.Length > 0) { ret += ", "; } ret += "IsAbstract"; } if (((uint)result.Value & (uint)AttributeWriteMask.MinimumSamplingInterval) == (uint)AttributeWriteMask.MinimumSamplingInterval) { if (ret.Length > 0) { ret += ", "; } ret += "MinimumSamplingInterval"; } if (((uint)result.Value & (uint)AttributeWriteMask.Symmetric) == (uint)AttributeWriteMask.Symmetric) { if (ret.Length > 0) { ret += ", "; } ret += "Symmetric"; } if (((uint)result.Value & (uint)AttributeWriteMask.UserAccessLevel) == (uint)AttributeWriteMask.UserAccessLevel) { if (ret.Length > 0) { ret += ", "; } ret += "UserAccessLevel"; } if (((uint)result.Value & (uint)AttributeWriteMask.UserExecutable) == (uint)AttributeWriteMask.UserExecutable) { if (ret.Length > 0) { ret += ", "; } ret += "UserExecutable"; } if (((uint)result.Value & (uint)AttributeWriteMask.UserWriteMask) == (uint)AttributeWriteMask.UserWriteMask) { if (ret.Length > 0) { ret += ", "; } ret += "UserWriteMask"; } if (((uint)result.Value & (uint)AttributeWriteMask.ValueRank) == (uint)AttributeWriteMask.ValueRank) { if (ret.Length > 0) { ret += ", "; } ret += "ValueRank"; } if (((uint)result.Value & (uint)AttributeWriteMask.WriteMask) == (uint)AttributeWriteMask.WriteMask) { if (ret.Length > 0) { ret += ", "; } ret += "WriteMask"; } if (ret.Length == 0) { ret = "None"; } } else { ret = "Invalid Datatype: Expected Uint32, Received " + result.Value.GetType().ToString(); } } break; case Attributes.NodeClass: { // Check datatype. if (result.Value.GetType() == typeof(Int32)) { switch ((Int32)result.Value) { case (int)NodeClass.DataType: ret = "DataType"; break; case (int)NodeClass.Method: ret = "Method"; break; case (int)NodeClass.Object: ret = "Object"; break; case (int)NodeClass.ObjectType: ret = "ObjectType"; break; case (int)NodeClass.ReferenceType: ret = "ReferenceType"; break; case (int)NodeClass.Unspecified: ret = "Unspecified"; break; case (int)NodeClass.Variable: ret = "Variable"; break; case (int)NodeClass.VariableType: ret = "VariableType"; break; case (int)NodeClass.View: ret = "View"; break; default: ret = "Invalid NodeClass"; break; } } else { ret = "Invalid Datatype: Expected Int32, Received " + result.Value.GetType().ToString(); } } break; case Attributes.EventNotifier: { // Check datatype. if (result.Value.GetType() == typeof(byte)) { if (((byte)result.Value & EventNotifiers.HistoryRead) == EventNotifiers.HistoryRead) { ret = "HistoryRead"; } if (((byte)result.Value & EventNotifiers.HistoryWrite) == EventNotifiers.HistoryWrite) { if (ret.Length > 0) { ret += ", "; } ret += "HistoryWrite"; } if (((byte)result.Value & EventNotifiers.SubscribeToEvents) == EventNotifiers.SubscribeToEvents) { if (ret.Length > 0) { ret += ", "; } ret += "SubscribeToEvents"; } if (ret.Length == 0) { ret += "None"; } } else { ret = "Invalid Datatype: Expected Byte, Received " + result.Value.GetType().ToString(); } } break; case Attributes.DataType: { // Check datatype. if (result.Value.GetType() == typeof(NodeId)) { NodeId node = (NodeId)result.Value; INode datatype = m_Session.NodeCache.Find(node); if (datatype != null) { ret = String.Format("{0}", datatype); } else { ret = String.Format("{0}", node); } } else { ret = "Invalid Datatype: Expected NodeId, Received " + result.Value.GetType().ToString(); } } break; case Attributes.ArrayDimensions: case Attributes.BrowseName: case Attributes.ContainsNoLoops: case Attributes.Description: case Attributes.DisplayName: case Attributes.Executable: case Attributes.Historizing: case Attributes.InverseName: case Attributes.IsAbstract: case Attributes.MinimumSamplingInterval: case Attributes.NodeId: case Attributes.Symmetric: case Attributes.UserExecutable: case Attributes.Value: case Attributes.ValueRank: default: ret = result.Value.ToString(); break; } return ret; } #endregion } }