//============================================================================= // Siemens AG // (c)Copyright (2017) All Rights Reserved //----------------------------------------------------------------------------- // Tested with: Windows 7 Ultimate x64 // Engineering: Visual Studio 2013 // Functionality: Wrapps up important classes/methods of the OPC UA .NET Stack to help // with simple client implementation //----------------------------------------------------------------------------- // Change log table: // Version Date Expert in charge Changes applied // 01.00.00 31.08.2016 (Siemens) First released version // 01.01.00 22.02.2017 (Siemens) Implement user authentication, SHA256 Cert, Basic256Rsa256 connection, // Basic256Rsa256 connections, read/write structs/UDTs //============================================================================= using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Sockets; using System.Security.Cryptography; using System.Security.Permissions; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; using System.Xml; using Opc.Ua; using Opc.Ua.Client; namespace Siemens.UAClientHelper { public class UAClientHelperAPI { #region Construction public UAClientHelperAPI() { // Creats the application configuration (containing the certificate) on construction mApplicationConfig = CreateClientConfiguration(); } #endregion #region Properties /// /// Keeps a session with an UA server. /// private Session mSession = null; /// /// Specifies this application. /// private ApplicationConfiguration mApplicationConfig = null; /// /// Provides the session being established with an OPC UA server. /// public Session Session { get { return mSession; } } /// /// Provides the event handling for server certificates. /// public CertificateValidationEventHandler CertificateValidationNotification = null; /// /// Provides the event for value changes of a monitored item. /// public MonitoredItemNotificationEventHandler ItemChangedNotification = null; /// /// Provides the event for KeepAliveNotifications. /// public KeepAliveEventHandler KeepAliveNotification = null; #endregion #region Discovery /// Finds Servers based on a discovery url /// The discovery url /// ApplicationDescriptionCollection containing found servers /// Throws and forwards any exception with short error description. public ApplicationDescriptionCollection FindServers(string discoveryUrl) { //Create a URI using the discovery URL Uri uri = new Uri(discoveryUrl); try { //Ceate a DiscoveryClient DiscoveryClient client = DiscoveryClient.Create(uri); //Find servers //ApplicationDescriptionCollection servers = client.FindServers(null); ApplicationDescriptionCollection servers = client.FindServers(null); return servers; } catch (Exception e) { //handle Exception here throw e; } } /// Finds Endpoints based on a server's url /// The server's url /// EndpointDescriptionCollection containing found Endpoints /// Throws and forwards any exception with short error description. public EndpointDescriptionCollection GetEndpoints(string serverUrl) { //Create a URI using the server's URL Uri uri = new Uri(serverUrl); try { //Create a DiscoveryClient DiscoveryClient client = DiscoveryClient.Create(uri); //Search for available endpoints EndpointDescriptionCollection endpoints = client.GetEndpoints(null); return endpoints; } catch (Exception e) { //handle Exception here throw e; } } #endregion #region Connect/Disconnect /// Establishes the connection to an OPC UA server and creates a session using a server url. /// The Url of the endpoint as string. /// The security policy to use /// The message security mode to use /// Autheticate anonymous or with username and password /// The user name /// The password /// Throws and forwards any exception with short error description. [Obsolete("Only use if no EndpointDescription of the server's endpoint is available")] public void Connect(string url, string secPolicy, MessageSecurityMode msgSecMode, bool userAuth, string userName, string password) { try { //Secify application configuration ApplicationConfiguration ApplicationConfig = mApplicationConfig; //Hook up a validator function for a CertificateValidation event mApplicationConfig.CertificateValidator.CertificateValidation += Notificatio_CertificateValidation; //Create EndPoint description EndpointDescription EndpointDescription = CreateEndpointDescription(url, secPolicy, msgSecMode); //Create EndPoint configuration EndpointConfiguration EndpointConfiguration = EndpointConfiguration.Create(ApplicationConfig); //Create an Endpoint object to connect to server ConfiguredEndpoint Endpoint = new ConfiguredEndpoint(null, EndpointDescription, EndpointConfiguration); //Create anonymous user identity UserIdentity UserIdentity; if (userAuth) { UserIdentity = new UserIdentity(userName, password); } else { UserIdentity = new UserIdentity(); } //Update certificate store before connection attempt ApplicationConfig.CertificateValidator.Update(ApplicationConfig); //Create and connect session mSession = Session.Create( ApplicationConfig, Endpoint, true, "MySession", 60000, UserIdentity, null ); mSession.KeepAlive += new KeepAliveEventHandler(Notification_KeepAlive); } catch (Exception e) { //handle Exception here throw e; } } /// Establishes the connection to an OPC UA server and creates a session using an EndpointDescription. /// The EndpointDescription of the server's endpoint /// Autheticate anonymous or with username and password /// The user name /// The password /// Throws and forwards any exception with short error description. public void Connect(EndpointDescription endpointDescription, bool userAuth, string userName, string password) { try { //Secify application configuration ApplicationConfiguration ApplicationConfig = mApplicationConfig; //Hook up a validator function for a CertificateValidation event ApplicationConfig.CertificateValidator.CertificateValidation += Notificatio_CertificateValidation; //Create EndPoint configuration EndpointConfiguration EndpointConfiguration = EndpointConfiguration.Create(ApplicationConfig); //Connect to server and get endpoints ConfiguredEndpoint mEndpoint = new ConfiguredEndpoint(null, endpointDescription, EndpointConfiguration); //Create the binding factory. BindingFactory bindingFactory = BindingFactory.Create(mApplicationConfig, ServiceMessageContext.GlobalContext); //Creat a session name String sessionName = "MySession"; //Create user identity UserIdentity UserIdentity; if (userAuth) { UserIdentity = new UserIdentity(userName, password); } else { UserIdentity = new UserIdentity(); } //Update certificate store before connection attempt ApplicationConfig.CertificateValidator.Update(ApplicationConfig); //Create and connect session mSession = Session.Create( ApplicationConfig, mEndpoint, true, sessionName, 60000, UserIdentity, null ); mSession.KeepAlive += new KeepAliveEventHandler(Notification_KeepAlive); } catch (Exception e) { //handle Exception here throw e; } } /// Closes an existing session and disconnects from the server. /// Throws and forwards any exception with short error description. public void Disconnect() { // Close the session. try { mSession.Close(10000); mSession.Dispose(); } catch (Exception e) { //handle Exception here throw e; } } #endregion #region Browse /// Browses the root folder of an OPC UA server. /// ReferenceDescriptionCollection of found nodes /// Throws and forwards any exception with short error description. public ReferenceDescriptionCollection BrowseRoot() { //Create a collection for the browse results ReferenceDescriptionCollection referenceDescriptionCollection; //Create a continuationPoint byte[] continuationPoint; try { //Browse the RootFolder for variables, objects and methods mSession.Browse(null, null, ObjectIds.RootFolder, 0u, BrowseDirection.Forward, ReferenceTypeIds.HierarchicalReferences, true, (uint)NodeClass.Variable | (uint)NodeClass.Object | (uint)NodeClass.Method, out continuationPoint, out referenceDescriptionCollection); return referenceDescriptionCollection; } catch (Exception e) { //handle Exception here throw e; } } /// Browses a node ID provided by a ReferenceDescription /// The ReferenceDescription /// ReferenceDescriptionCollection of found nodes /// Throws and forwards any exception with short error description. public ReferenceDescriptionCollection BrowseNode(ReferenceDescription refDesc) { //Create a collection for the browse results ReferenceDescriptionCollection referenceDescriptionCollection; ReferenceDescriptionCollection nextreferenceDescriptionCollection; //Create a continuationPoint byte[] continuationPoint; byte[] revisedContinuationPoint; //Create a NodeId using the selected ReferenceDescription as browsing starting point NodeId nodeId = ExpandedNodeId.ToNodeId(refDesc.NodeId, null); try { //Browse from starting point for all object types mSession.Browse(null, null, nodeId, 0u, BrowseDirection.Forward, ReferenceTypeIds.HierarchicalReferences, true, 0, out continuationPoint, out referenceDescriptionCollection); while(continuationPoint != null) { mSession.BrowseNext(null, false, continuationPoint, out revisedContinuationPoint, out nextreferenceDescriptionCollection); referenceDescriptionCollection.AddRange(nextreferenceDescriptionCollection); continuationPoint = revisedContinuationPoint; } return referenceDescriptionCollection; } catch (Exception e) { //handle Exception here throw e; } } #endregion #region Subscription /// Creats a Subscription object to a server /// The publishing interval /// Subscription /// Throws and forwards any exception with short error description. public Subscription Subscribe(int publishingInterval) { //Create a Subscription object Subscription subscription = new Subscription(mSession.DefaultSubscription); //Enable publishing subscription.PublishingEnabled = true; //Set the publishing interval subscription.PublishingInterval = publishingInterval; //Add the subscription to the session mSession.AddSubscription(subscription); try { //Create/Activate the subscription subscription.Create(); return subscription; } catch (Exception e) { //handle Exception here throw e; } } /// Ads a monitored item to an existing subscription /// The subscription /// The node Id as string /// The name of the item to add /// The sampling interval /// The added item /// Throws and forwards any exception with short error description. public MonitoredItem AddMonitoredItem(Subscription subscription, string nodeIdString, string itemName, int samplingInterval) { //Create a monitored item MonitoredItem monitoredItem = new MonitoredItem(); //Set the name of the item for assigning items and values later on; make sure item names differ monitoredItem.DisplayName = itemName; //Set the NodeId of the item monitoredItem.StartNodeId = nodeIdString; //Set the attribute Id (value here) monitoredItem.AttributeId = Attributes.Value; //Set reporting mode monitoredItem.MonitoringMode = MonitoringMode.Reporting; //Set the sampling interval (1 = fastest possible) monitoredItem.SamplingInterval = samplingInterval; //Set the queue size monitoredItem.QueueSize = 1; //Discard the oldest item after new one has been received monitoredItem.DiscardOldest = true; //Define event handler for this item and then add to monitoredItem monitoredItem.Notification += new MonitoredItemNotificationEventHandler(Notification_MonitoredItem); try { //Add the item to the subscription subscription.AddItem(monitoredItem); //Apply changes to the subscription subscription.ApplyChanges(); return monitoredItem; } catch (Exception e) { //handle Exception here throw e; } } /// Removs a monitored item from an existing subscription /// The subscription /// The item /// Throws and forwards any exception with short error description. public MonitoredItem RemoveMonitoredItem(Subscription subscription, MonitoredItem monitoredItem) { try { //Add the item to the subscription subscription.RemoveItem(monitoredItem); //Apply changes to the subscription subscription.ApplyChanges(); return null; } catch (Exception e) { //handle Exception here throw e; } } /// Removes an existing Subscription /// The subscription /// Throws and forwards any exception with short error description. public void RemoveSubscription(Subscription subscription) { try { //Delete the subscription and all items submitted subscription.Delete(true); } catch (Exception e) { //handle Exception here throw e; } } #endregion #region Read/Write /// Reads a node by node Id /// The node Id as string /// The read node /// Throws and forwards any exception with short error description. public Node ReadNode(String nodeIdString) { //Create a nodeId using the identifier string NodeId nodeId = new NodeId(nodeIdString); //Create a node Node node = new Node(); try { //Read the dataValue node = mSession.ReadNode(nodeId); return node; } catch (Exception e) { //handle Exception here throw e; } } /// Reads values from node Ids /// The node Ids as strings /// The read values as strings /// Throws and forwards any exception with short error description. public List ReadValues(List nodeIdStrings) { List nodeIds = new List(); List types = new List(); List values = new List(); List serviceResults = new List(); foreach (string str in nodeIdStrings) { //Create a nodeId using the identifier string and add to list nodeIds.Add(new NodeId(str)); //No need for types types.Add(null); } try { //Read the dataValues mSession.ReadValues(nodeIds, types, out values, out serviceResults); //check ServiceResults to foreach (ServiceResult svResult in serviceResults) { if (svResult.ToString() != "Good") { Exception e = new Exception(svResult.ToString()); throw e; } } List resultStrings = new List(); foreach (object result in values) { if (result != null) { if (result.ToString() == "System.Byte[]") { string str = ""; str = BitConverter.ToString((byte[])result).Replace("-", ";"); resultStrings.Add(str); } if (result.ToString() == "System.String[]") { string str = ""; str = String.Join(";", (string[])result); resultStrings.Add(str); } else if (result.ToString() == "System.Boolean[]") { string str = ""; foreach (Boolean intVar in (Boolean[])result) { str = str + ";" + intVar.ToString(); } str = str.Remove(0, 1); resultStrings.Add(str); } else if (result.ToString() == "System.Int16[]") { string str = ""; foreach (Int16 intVar in (Int16[])result) { str = str + ";" + intVar.ToString(); } str = str.Remove(0, 1); resultStrings.Add(str); } else if (result.ToString() == "System.UInt16[]") { string str = ""; foreach (UInt16 intVar in (UInt16[])result) { str = str + ";" + intVar.ToString(); } str = str.Remove(0, 1); resultStrings.Add(str); } else if (result.ToString() == "System.Int64[]") { string str = ""; foreach (Int64 intVar in (Int64[])result) { str = str + ";" + intVar.ToString(); } str = str.Remove(0, 1); resultStrings.Add(str); } else if (result.ToString() == "System.Single[]") { string str = ""; foreach (float intVar in (float[])result) { str = str + ";" + intVar.ToString(); } str = str.Remove(0, 1); resultStrings.Add(str); } else if (result.ToString() == "System.Double[]") { string str = ""; foreach (double intVar in (double[])result) { str = str + ";" + intVar.ToString(); } str = str.Remove(0, 1); resultStrings.Add(str); } else { resultStrings.Add(result.ToString()); } } else { resultStrings.Add("(null)"); } } return resultStrings; } catch (Exception e) { //handle Exception here throw e; } } /// Writes values to node Ids /// The values as strings /// The node Ids as strings /// Throws and forwards any exception with short error description. public void WriteValues(List values, List nodeIdStrings) { //Create a collection of values to write WriteValueCollection valuesToWrite = new WriteValueCollection(); //Create a collection for StatusCodes StatusCodeCollection result = new StatusCodeCollection(); //Create a collection for DiagnosticInfos DiagnosticInfoCollection diagnostics = new DiagnosticInfoCollection(); foreach (String str in nodeIdStrings) { //Create a nodeId NodeId nodeId = new NodeId(str); //Create a dataValue DataValue dataValue = new DataValue(); //Read the dataValue try { dataValue = mSession.ReadValue(nodeId); } catch (Exception e) { //handle Exception here throw e; } string test = dataValue.Value.GetType().Name; //Get the data type of the read dataValue //Handle Arrays here: TBD Variant variant = 0; try { variant = new Variant(Convert.ChangeType(values[nodeIdStrings.IndexOf(str)], dataValue.Value.GetType())); } catch //no base data type { //Handle different arrays types here: TBD if (dataValue.Value.GetType().Name == "string[]") { string[] arrString = values[nodeIdStrings.IndexOf(str)].Split(';'); variant = new Variant(arrString); } else if (dataValue.Value.GetType().Name == "Byte[]") { //handle byte array here ; } } //Overwrite the dataValue with a new constructor using read dataType dataValue = new DataValue(variant); //Create a WriteValue using the NodeId, dataValue and attributeType WriteValue valueToWrite = new WriteValue(); valueToWrite.Value = dataValue; valueToWrite.NodeId = nodeId; valueToWrite.AttributeId = Attributes.Value; //Add the dataValues to the collection valuesToWrite.Add(valueToWrite); } try { //Write the collection to the server mSession.Write(null, valuesToWrite, out result, out diagnostics); foreach (StatusCode code in result) { if (code != 0) { Exception ex = new Exception(code.ToString()); throw ex; } } } catch (Exception e) { //handle Exception here throw e; } } #endregion #region Read/Write Struct/UDT /// Reads a struct or UDT by node Id /// The node Id as strings /// The read struct/UDT elements as a list of string[3]; string[0] = tag name, string[1] = value, string[2] = opc data type /// Throws and forwards any exception with short error description. public List ReadStructUdt(String nodeIdString) { //Define result list to return var name and var value List resultStringList = new List(); //Get the type dictionary of desired struct/UDT and the name of desired var to parse String parseString; String xmlString = GetTypeDictionary(nodeIdString, mSession, out parseString); //Parse xmlString to create objects of the struct/UDT containing var name and var data type List varList = new List(); varList = ParseTypeDictionary(xmlString, parseString); //Read the struct List nodeIds = new List(); NodeId myNode = new NodeId(nodeIdString); nodeIds.Add(myNode); List serviceResults = new List(); List values = new List(); List types = new List(); types.Add(null); try { mSession.ReadValues(nodeIds, types, out values, out serviceResults); } catch (Exception e) { //Handle Exception here throw e; } //Check result codes foreach (ServiceResult svResult in serviceResults) { if (svResult.ToString() != "Good") { Exception e = new Exception(svResult.ToString()); throw e; } } //Create an empty byte-array to store ExtensionObject.Body (containing the whole binary data of the desired strucht/UDT) into byte[] readBinaryData = null; foreach (object val in values) { //Cast object to ExtensionObject ExtensionObject encodeable = val as ExtensionObject; if (encodeable == null) { ExtensionObject[] exObjArr = val as ExtensionObject[]; encodeable = exObjArr[0]; } //Write the body of the ExtensionObject into the byte-array readBinaryData = (byte[])encodeable.Body; } //Check for data types and parse byte array resultStringList = ParseDataToTagsFromDictionary(varList, readBinaryData); //return result as List (string[0]=tag name; string[1]=tag value; string[2]=tag data type return resultStringList; } /// Writes data to a struct or UDT by node Id /// The node Id as strings /// The data to write as string[3]; string[0] = tag name, string[1] = value, string[2] = opc data type /// Throws and forwards any exception with short error description. public void WriteStructUdt(String nodeIdString, List dataToWrite) { //Create a NodeId from the NodeIdString NodeId nodeId = new NodeId(nodeIdString); //Creat a WriteValueColelction WriteValueCollection valuesToWrite = new WriteValueCollection(); //Create a WriteValue WriteValue writevalue = new WriteValue(); //Create a StatusCodeCollection StatusCodeCollection results = new StatusCodeCollection(); //Create a DiagnosticInfoCollection DiagnosticInfoCollection diag = new DiagnosticInfoCollection(); //Determine lentgh of byte array needed to contain all data Int64 length = GetLengthOfDataToWrite(dataToWrite); //Create a byte array byte[] bytesToWrite; //Parse dataToWrite to the byte array bytesToWrite = ParseDataToByteArray(dataToWrite, length); //Create an ExtensionObject from the Structure given to this function ExtensionObject writeExtObj = new ExtensionObject(); writeExtObj.Body = bytesToWrite; //Turn the created ExtensionObject into a DataValue DataValue dataValue = new DataValue(writeExtObj); //Setup for the WriteValue writevalue.NodeId = nodeId; writevalue.Value = dataValue; writevalue.AttributeId = Attributes.Value; //Add the created value to the collection valuesToWrite.Add(writevalue); try { mSession.Write(null, valuesToWrite, out results, out diag); } catch (Exception e) { //Handle Exception here throw e; } //Check result codes foreach (StatusCode result in results) { if (result.ToString() != "Good") { Exception e = new Exception(result.ToString()); throw e; } } } #endregion #region Register/Unregister nodes Ids /// Registers Node Ids to the server /// The node Ids as strings /// The registered Node Ids as strings /// Throws and forwards any exception with short error description. public List RegisterNodeIds(List nodeIdStrings) { NodeIdCollection nodesToRegister = new NodeIdCollection(); NodeIdCollection registeredNodes = new NodeIdCollection(); List registeredNodeIdStrings = new List(); foreach (string str in nodeIdStrings) { //Create a nodeId using the identifier string and add to list nodesToRegister.Add(new NodeId(str)); } try { //Register nodes mSession.RegisterNodes(null, nodesToRegister, out registeredNodes); foreach (NodeId nodeId in registeredNodes) { registeredNodeIdStrings.Add(nodeId.ToString()); } return registeredNodeIdStrings; } catch (Exception e) { //handle Exception here throw e; } } /// Unregister Node Ids to the server /// The node Ids as string /// Throws and forwards any exception with short error description. public void UnregisterNodeIds(List nodeIdStrings) { NodeIdCollection nodesToUnregister = new NodeIdCollection(); List registeredNodeIdStrings = new List(); foreach (string str in nodeIdStrings) { //Create a nodeId using the identifier string and add to list nodesToUnregister.Add(new NodeId(str)); } try { //Register nodes mSession.UnregisterNodes(null, nodesToUnregister); } catch (Exception e) { //handle Exception here throw e; } } #endregion #region EventHandling /// Eventhandler to validate the server certificate forwards this event private void Notificatio_CertificateValidation(CertificateValidator certificate, CertificateValidationEventArgs e) { CertificateValidationNotification(certificate, e); } /// Eventhandler for MonitoredItemNotifications forwards this event private void Notification_MonitoredItem(MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e) { ItemChangedNotification(monitoredItem, e); } /// Eventhandler for KeepAlive forwards this event private void Notification_KeepAlive(Session session, KeepAliveEventArgs e) { KeepAliveNotification(session, e); } #endregion #region Private methods /// Creats a minimal required ApplicationConfiguration /// The ip address of the interface to connect with /// The ApplicationConfiguration /// Throws and forwards any exception with short error description. private static ApplicationConfiguration CreateClientConfiguration() { // The application configuration can be loaded from any file. // ApplicationConfiguration.Load() method loads configuration by looking up a file path in the App.config. // This approach allows applications to share configuration files and to update them. // This example creates a minimum ApplicationConfiguration using its default constructor. ApplicationConfiguration configuration = new ApplicationConfiguration(); // Step 1 - Specify the client identity. configuration.ApplicationName = "UA Client 1500"; configuration.ApplicationType = ApplicationType.Client; configuration.ApplicationUri = "urn:MyClient"; //Kepp this syntax configuration.ProductUri = "SiemensAG.IndustryOnlineSupport"; // Step 2 - Specify the client's application instance certificate. // Application instance certificates must be placed in a windows certficate store because that is // the best way to protect the private key. Certificates in a store are identified with 4 parameters: // StoreLocation, StoreName, SubjectName and Thumbprint. // When using StoreType = Directory you need to have the opc.ua.certificategenerator.exe installed on your machine configuration.SecurityConfiguration = new SecurityConfiguration(); configuration.SecurityConfiguration.ApplicationCertificate = new CertificateIdentifier(); configuration.SecurityConfiguration.ApplicationCertificate.StoreType = CertificateStoreType.Windows; configuration.SecurityConfiguration.ApplicationCertificate.StorePath = "CurrentUser\\My"; configuration.SecurityConfiguration.ApplicationCertificate.SubjectName = configuration.ApplicationName; // Define trusted root store for server certificate checks configuration.SecurityConfiguration.TrustedIssuerCertificates.StoreType = CertificateStoreType.Windows; configuration.SecurityConfiguration.TrustedIssuerCertificates.StorePath = "CurrentUser\\Root"; configuration.SecurityConfiguration.TrustedPeerCertificates.StoreType = CertificateStoreType.Windows; configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath = "CurrentUser\\Root"; // find the client certificate in the store. X509Certificate2 clientCertificate = configuration.SecurityConfiguration.ApplicationCertificate.Find(true); // create a new self signed certificate if not found. if (clientCertificate == null) { // Get local interface ip addresses and DNS name List localIps = GetLocalIpAddressAndDns(); UInt16 keySize = 2048; //must be multiples of 1024 UInt16 lifeTime = 24; //in month UInt16 algorithm = 1; //0 = SHA1; 1 = SHA256 // this code would normally be called as part of the installer - called here to illustrate. // create a new certificate an place it in the current user certificate store. clientCertificate = CertificateFactory.CreateCertificate( configuration.SecurityConfiguration.ApplicationCertificate.StoreType, configuration.SecurityConfiguration.ApplicationCertificate.StorePath, configuration.ApplicationUri, configuration.ApplicationName, null, localIps, keySize, lifeTime, algorithm); } // Step 3 - Specify the supported transport quotas. // The transport quotas are used to set limits on the contents of messages and are // used to protect against DOS attacks and rogue clients. They should be set to // reasonable values. configuration.TransportQuotas = new TransportQuotas(); configuration.TransportQuotas.OperationTimeout = 360000; configuration.TransportQuotas.MaxStringLength = 67108864; configuration.TransportQuotas.MaxByteStringLength = 16777216; //Needed, i.e. for large TypeDictionarys // Step 4 - Specify the client specific configuration. configuration.ClientConfiguration = new ClientConfiguration(); configuration.ClientConfiguration.DefaultSessionTimeout = 360000; // Step 5 - Validate the configuration. // This step checks if the configuration is consistent and assigns a few internal variables // that are used by the SDK. This is called automatically if the configuration is loaded from // a file using the ApplicationConfiguration.Load() method. configuration.Validate(ApplicationType.Client); return configuration; } /// Creats an EndpointDescription /// The endpoint url /// Use security or not /// The EndpointDescription /// Throws and forwards any exception with short error description. private static EndpointDescription CreateEndpointDescription(string url, string secPolicy, MessageSecurityMode msgSecMode) { // create the endpoint description. EndpointDescription endpointDescription = new EndpointDescription(); // submit the url of the endopoint endpointDescription.EndpointUrl = url; // specify the security policy to use. endpointDescription.SecurityPolicyUri = secPolicy; endpointDescription.SecurityMode = msgSecMode; // specify the transport profile. endpointDescription.TransportProfileUri = Profiles.UaTcpTransport; return endpointDescription; } /// Gets the local IP addresses and the DNS name /// The list of IPs and names /// Throws and forwards any exception with short error description. private static List GetLocalIpAddressAndDns() { List localIps = new List(); var host = Dns.GetHostEntry(Dns.GetHostName()); foreach (var ip in host.AddressList) { if (ip.AddressFamily == AddressFamily.InterNetwork) { localIps.Add(ip.ToString()); } } if (localIps.Count == 0) { throw new Exception("Local IP Address Not Found!"); } localIps.Add(Dns.GetHostName()); return localIps; } /// Parses a XML string for the a default data type /// The XML string containing data type information /// The data type as string to search for /// The created objects after parsing for default data type /// Throws and forwards any exception with short error description. private static List ParseTypeDictionary(String xmlStringToParse, String stringToParserFor) { List varList = new List(); //Remove last XML sign and create a XML document out of the dictionary string xmlStringToParse = xmlStringToParse.Remove(xmlStringToParse.Length - 1); XmlDocument docToParse = new XmlDocument(); docToParse.LoadXml(xmlStringToParse); //Get a XML node list of objectes named by "stringToParseFor" docToParse.GetElementsByTagName(stringToParserFor); XmlNodeList nodeList; nodeList = docToParse.GetElementsByTagName("opc:StructuredType"); XmlNode foundNode = null; //search for the attribute name == "stringToParseFor" foreach (XmlNode node in nodeList) { if (node.Attributes["Name"].Value == stringToParserFor) { foundNode = node; break; } } //check if attribute name was found if (foundNode == null) { return null; } //get child nodes of parent node with attribute name == "stringToParseFor" and parse for var name and var type foreach (XmlNode node in foundNode.ChildNodes) { string[] dataReferenceStringArray = new string[2]; dataReferenceStringArray[0] = node.Attributes["Name"].Value; dataReferenceStringArray[1] = node.Attributes["TypeName"].Value; varList.Add(dataReferenceStringArray); } //Check if result contains another struct/UDT inside and parse for var name and var type //Note: This check is consistent even if there are more structs/UDTs inside of structs/UDTs for (int count = 0; count < varList.Count; count++) { Object varObject = varList[count]; if (((string[])varObject)[1].Contains("tns:")) { XmlNode innerNode = null; foreach (XmlNode anotherNode in nodeList) { if (anotherNode.Attributes["Name"].Value == ((string[])varObject)[1].Remove(0, 4)) { innerNode = anotherNode; break; } } if (innerNode == null) { return null; } int i = 0; foreach (XmlNode innerChildNode in innerNode.ChildNodes) { string[] innerDataReferenceStringArray = new string[2]; innerDataReferenceStringArray[0] = innerChildNode.Attributes["Name"].Value; ; innerDataReferenceStringArray[1] = innerChildNode.Attributes["TypeName"].Value; varList.Insert(varList.IndexOf(varObject) + 1 + i, innerDataReferenceStringArray); i += 1; } } } return varList; } /// Parses a byte array to objects containing tag names and tag data types /// List of object containing tag names and tag data types /// A byte array to parse /// A list of string[3]; string[0] = tag name, string[1] = value, string[2] = opc data type /// Throws and forwards any exception with short error description. private static List ParseDataToTagsFromDictionary(List varList, byte[] byteResult) { //Define result list to return var name, var value and var data type List resultStringList = new List(); //Byte decoding index int index = 0; //Int used to decode arrays Int32 arraylength = 0; //Start decoding for opc data types foreach (object val in varList) { string[] dataReferenceStringArray = new string[3]; dataReferenceStringArray[0] = ((string[])val)[0]; if (((string[])val)[1] == "opc:Boolean") { dataReferenceStringArray[1] = BitConverter.ToBoolean(byteResult, index).ToString(); index += 1; } else if (((string[])val)[1] == "opc:Int16") { dataReferenceStringArray[1] = BitConverter.ToInt16(byteResult, index).ToString(); index += 2; } else if (((string[])val)[1] == "opc:Int32" && arraylength == 0 && !((string[])val)[0].Contains("_Size")) { dataReferenceStringArray[1] = BitConverter.ToInt32(byteResult, index).ToString(); index += 4; } else if (((string[])val)[1] == "opc:Float") { dataReferenceStringArray[1] = BitConverter.ToSingle(byteResult, index).ToString(); index += 4; } else if (((string[])val)[1] == "opc:Double") { dataReferenceStringArray[1] = BitConverter.ToDouble(byteResult, index).ToString(); index += 8; } else if (((string[])val)[1] == "opc:String" && !(arraylength > 0)) { Int32 stringlength = BitConverter.ToInt32(byteResult, index); index += 4; if (stringlength > 0) { dataReferenceStringArray[1] = Encoding.UTF8.GetString(byteResult, index, stringlength); index += stringlength; } else { dataReferenceStringArray[1] = ""; } } else if (((string[])val)[1] == "opc:CharArray") { Int32 stringlength = BitConverter.ToInt32(byteResult, index); index += 4; if (stringlength > 0) { dataReferenceStringArray[1] = Encoding.UTF8.GetString(byteResult, index, stringlength); index += stringlength; } else { dataReferenceStringArray[1] = ""; } } else if (((string[])val)[1] == "opc:UInt16") { dataReferenceStringArray[1] = BitConverter.ToUInt16(byteResult, index).ToString(); index += 2; } else if (((string[])val)[1] == "opc:UInt32") { dataReferenceStringArray[1] = BitConverter.ToUInt32(byteResult, index).ToString(); index += 4; } else if (((string[])val)[1] == "opc:Int64") { dataReferenceStringArray[1] = BitConverter.ToInt64(byteResult, index).ToString(); index += 8; } else if (((string[])val)[1] == "opc:UInt64") { dataReferenceStringArray[1] = BitConverter.ToUInt64(byteResult, index).ToString(); index += 8; } else if (((string[])val)[1] == "opc:Byte" && !(arraylength > 0)) { dataReferenceStringArray[1] = byteResult[index].ToString(); index += 1; } else if (((string[])val)[1] == "opc:Int32" && ((string[])val)[0].Contains("_Size")) { arraylength = BitConverter.ToInt32(byteResult, index); dataReferenceStringArray[1] = arraylength.ToString(); index += 4; } else if (((string[])val)[1] == "opc:Byte" && arraylength > 0) { Int32[] tempArray = new Int32[arraylength]; for (int i = 0; i < arraylength; i++) { tempArray[i] = byteResult[index]; index += 1; } dataReferenceStringArray[1] = String.Join(";", tempArray); dataReferenceStringArray[1] = String.Concat(dataReferenceStringArray[1], ";"); arraylength = 0; } else if (((string[])val)[1] == "opc:String" && arraylength > 0) { for (int i = 0; i < arraylength; i++) { Int32 stringlength = BitConverter.ToInt32(byteResult, index); index += 4; if (stringlength > 0) { dataReferenceStringArray[1] = String.Concat(dataReferenceStringArray[1], Encoding.UTF8.GetString(byteResult, index, stringlength)); dataReferenceStringArray[1] = String.Concat(dataReferenceStringArray[1], ";"); index += stringlength; } else { dataReferenceStringArray[1] = ""; } } arraylength = 0; } else if (((string[])val)[1].Contains("tns:")) { dataReferenceStringArray[1] = ""; } else { Exception e = new Exception("Read result contains unknown or advanced binary data type."); throw e; } dataReferenceStringArray[2] = ((string[])val)[1]; resultStringList.Add(dataReferenceStringArray); } return resultStringList; } /// Parses a byte array to objects containing tag names and tag data types /// The data to analyze /// The length /// Throws and forwards any exception with short error description. private static Int64 GetLengthOfDataToWrite(List dataToWrite) { Int64 length = 0; Int32 arraySize = 0; foreach (string[] val in dataToWrite) { if (arraySize > 0) { arraySize = 0; continue; } if (val[2] == "opc:Boolean") { length += 1; } else if (val[2] == "opc:Int16") { length += 2; } else if (val[2] == "opc:Float") { length += 4; } else if (val[2] == "opc:Double") { length += 8; } else if (val[2] == "opc:UInt16") { length += 2; } else if (val[2] == "opc:Int32" && !(val[0].Contains("_Size"))) { length += 4; } else if (val[2] == "opc:UInt32") { length += 4; } else if (val[2] == "opc:Int64") { length += 8; } else if (val[2] == "opc:UInt64") { length += 8; } else if (val[2] == "opc:Byte") { length += 1; } else if (val[2] == "opc:String") { length += val[1].Length + 4; } else if (val[2] == "opc:CharArray") { length += val[1].Length + 4; } else if (val[2] == "opc:Int32" && val[0].Contains("_Size")) { arraySize = Convert.ToInt32(val[1]); if (((string[])dataToWrite[dataToWrite.IndexOf(val) + 1])[2] == "opc:String") { string[] tempStringArr = new string[arraySize]; tempStringArr = ((string[])dataToWrite[dataToWrite.IndexOf(val) + 1])[1].Split(';'); for (int ii = 0; ii < arraySize; ii++) { length += (4 + tempStringArr[ii].Length); } length += 4; } else { length += (4 + arraySize); } } else if (val[2].Contains("tns:")) { ; } else { Exception e = new Exception("Unknow data type. Can't determine length of data"); throw e; } } return length; } /// Browses for the desired type dictonary to parse for containing data types /// The node Id string /// The current session to browse in /// The name of the var to parse for inside of dictionary /// The dictionary as ASCII string /// Throws and forwards any exception with short error description. private static String GetTypeDictionary(String nodeIdString, Session theSessionToBrowseIn, out String parseString) { //Read the desired node first and chekc if it's a variable Node node = theSessionToBrowseIn.ReadNode(nodeIdString); if (node.NodeClass == NodeClass.Variable) { //Get the node id of node's data type VariableNode variableNode = (VariableNode)node.DataLock; NodeId nodeId = new NodeId(variableNode.DataType.Identifier, variableNode.DataType.NamespaceIndex); //Browse for HasEncoding ReferenceDescriptionCollection refDescCol; byte[] continuationPoint; theSessionToBrowseIn.Browse(null, null, nodeId, 0u, BrowseDirection.Forward, ReferenceTypeIds.HasEncoding, true, 0, out continuationPoint, out refDescCol); //Check For found reference if (refDescCol.Count == 0) { Exception ex = new Exception("No data type to encode. Could be a build-in data type you want to read."); throw ex; } //Check for HasEncoding reference with name "Default Binary" foreach (ReferenceDescription refDesc in refDescCol) { if (refDesc.DisplayName.Text == "Default Binary") { nodeId = new NodeId(refDesc.NodeId.Identifier, refDesc.NodeId.NamespaceIndex); } else { Exception ex = new Exception("No default binary data type found."); throw ex; } } //Browse for HasDescription refDescCol = null; theSessionToBrowseIn.Browse(null, null, nodeId, 0u, BrowseDirection.Forward, ReferenceTypeIds.HasDescription, true, 0, out continuationPoint, out refDescCol); //Check For found reference if (refDescCol.Count == 0) { Exception ex = new Exception("No data type description found in address space."); throw ex; } //Read from node id of the found description to get a value to parse for later on nodeId = new NodeId(refDescCol[0].NodeId.Identifier, refDescCol[0].NodeId.NamespaceIndex); DataValue resultValue = theSessionToBrowseIn.ReadValue(nodeId); parseString = resultValue.Value.ToString(); //Browse for ComponentOf from last browsing result inversly refDescCol = null; theSessionToBrowseIn.Browse(null, null, nodeId, 0u, BrowseDirection.Inverse, ReferenceTypeIds.HasComponent, true, 0, out continuationPoint, out refDescCol); //Check if reference was found if (refDescCol.Count == 0) { Exception ex = new Exception("Data type isn't a component of parent type in address space. Can't continue decoding."); throw ex; } //Read from node id of the found HasCompoment reference to get a XML file (as HEX string) containing struct/UDT information nodeId = new NodeId(refDescCol[0].NodeId.Identifier, refDescCol[0].NodeId.NamespaceIndex); resultValue = theSessionToBrowseIn.ReadValue(nodeId); //Convert the HEX string to ASCII string String xmlString = ASCIIEncoding.ASCII.GetString((byte[])resultValue.Value); //Return the dictionary as ASCII string return xmlString; } { Exception ex = new Exception("No variable data type found"); throw ex; } } /// Parses data to write to a byte array /// The data to write as string[3]; string[0] = tag name, string[1] = value, string[2] = opc data type /// The length of the data to write /// The parsed byte array /// Throws and forwards any exception with short error description. private static byte[] ParseDataToByteArray(List dataToWrite, Int64 dataLength) { byte[] bytesToWrite = new byte[dataLength]; Int32 convertIndex = 0; Int32 arraySize = 0; foreach (string[] val in dataToWrite) { if (val[2] == "opc:Boolean") { Boolean tempBool = Convert.ToBoolean(val[1]); bytesToWrite[convertIndex] = Convert.ToByte(tempBool); convertIndex += 1; } else if (val[2] == "opc:Int16") { Array.Copy(BitConverter.GetBytes(Convert.ToInt16(val[1])), 0, bytesToWrite, convertIndex, 2); convertIndex += 2; } else if (val[2] == "opc:Float") { Array.Copy(BitConverter.GetBytes(Convert.ToSingle(val[1])), 0, bytesToWrite, convertIndex, 4); convertIndex += 4; } else if (val[2] == "opc:Double") { Array.Copy(BitConverter.GetBytes(Convert.ToDouble(val[1])), 0, bytesToWrite, convertIndex, 8); convertIndex += 8; } else if (val[2] == "opc:UInt16") { Array.Copy(BitConverter.GetBytes(Convert.ToUInt16(val[1])), 0, bytesToWrite, convertIndex, 2); convertIndex += 2; } else if (val[2] == "opc:Int32" && !(val[0].Contains("_Size"))) { Array.Copy(BitConverter.GetBytes(Convert.ToUInt32(val[1])), 0, bytesToWrite, convertIndex, 4); convertIndex += 4; } else if (val[2] == "opc:UInt32") { Array.Copy(BitConverter.GetBytes(Convert.ToUInt32(val[1])), 0, bytesToWrite, convertIndex, 4); convertIndex += 4; } else if (val[2] == "opc:Int64") { Array.Copy(BitConverter.GetBytes(Convert.ToInt64(val[1])), 0, bytesToWrite, convertIndex, 8); convertIndex += 8; } else if (val[2] == "opc:UInt64") { Array.Copy(BitConverter.GetBytes(Convert.ToUInt64(val[1])), 0, bytesToWrite, convertIndex, 8); convertIndex += 8; } else if (val[2] == "opc:Byte" && !(arraySize > 0)) { bytesToWrite[convertIndex] = Convert.ToByte(val[1]); convertIndex += 1; } else if (val[2] == "opc:String" && !(arraySize > 0)) { Array.Copy(BitConverter.GetBytes(val[1].Length), 0, bytesToWrite, convertIndex, 4); convertIndex += 4; foreach (Char c in val[1]) { bytesToWrite[convertIndex] = Convert.ToByte(c); convertIndex += 1; } } else if (val[2] == "opc:CharArray") { Array.Copy(BitConverter.GetBytes(val[1].Length), 0, bytesToWrite, convertIndex, 4); convertIndex += 4; foreach (Char c in val[1]) { bytesToWrite[convertIndex] = Convert.ToByte(c); convertIndex += 1; } } else if (val[2] == "opc:Int32" && val[0].Contains("_Size")) { Array.Copy(BitConverter.GetBytes(Convert.ToUInt32(val[1])), 0, bytesToWrite, convertIndex, 4); arraySize = Convert.ToInt32(val[1]); convertIndex += 4; } else if (val[2] == "opc:Byte" && arraySize > 0) { String tempString = ""; foreach (Char c in val[1]) { if (c != ';') { tempString = String.Concat(tempString, c); } else { bytesToWrite[convertIndex] = Convert.ToByte(tempString); convertIndex += 1; tempString = ""; } } arraySize = 0; } else if (val[2] == "opc:String" && arraySize > 0) { string[] tempStringArr = new string[arraySize]; tempStringArr = val[1].Split(';'); for (int ii = 0; ii < arraySize; ii++) { Array.Copy(BitConverter.GetBytes(tempStringArr[ii].Length), 0, bytesToWrite, convertIndex, 4); convertIndex += 4; foreach (Char c in tempStringArr[ii]) { bytesToWrite[convertIndex] = Convert.ToByte(c); convertIndex += 1; } } arraySize = 0; } else if (val[2].Contains("tns:")) { ; } else { Exception e = new Exception("Can't covert" + val[0] + "."); throw e; } } return bytesToWrite; } #endregion } }