using NLog; using Opc.Ua; using Opc.Ua.Client; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace IOB_WIN_NEXT { /// /// Evento per incapsulare dati x refresh pagina /// public class opcUaMonitItemChange : EventArgs { #region Public Constructors /// /// salvataggio obj /// /// public opcUaMonitItemChange(MonitoredItem monitoredItem, MonitoredItemNotification notification) { _monitoredItem = monitoredItem; _notification = notification; } #endregion Public Constructors #region Public Properties /// /// Proprietà lettura del MonitoredItem /// public MonitoredItem CurrMonitoredItem { get { return _monitoredItem; } } /// /// Proprietà lettura della notifica /// public MonitoredItemNotification CurrNotify { get { return _notification; } } #endregion Public Properties #region Private Fields /// /// Monitored Item da notificare /// private readonly MonitoredItem _monitoredItem; /// /// Valore notifica /// private readonly MonitoredItemNotification _notification; #endregion Private Fields } /// /// OPC UA Client with examples of basic functionality. /// public class UAClient { #region Public Constructors /// /// Initializes a new instance of the UAClient class. /// /// /// /// /// /// /// /// Numero massimo di risposte nulle in browsing prima di disconnettersi public UAClient(ApplicationConfiguration configuration, string codIOB, string user, string pwd, bool verboseLog, Action validateResponse, int maxNullRead) { currNullReceiv = 0; maxNullAllowed = maxNullRead; m_validateResponse = validateResponse; currIob = codIOB; lg = LogManager.GetCurrentClassLogger(); if (!string.IsNullOrEmpty(user) && !string.IsNullOrEmpty(pwd)) { CurrUserIdentity = new UserIdentity(user, pwd); } else { CurrUserIdentity = new UserIdentity(); } isLogVerbose = verboseLog; m_configuration = configuration; m_configuration.CertificateValidator.CertificateValidation += CertificateValidation; } #endregion Public Constructors #region Public Events /// /// Evento notifica variazione MonitoredItem /// public event EventHandler eh_MonItChange; /// /// Evento notifica superamento numero letture null /// public event EventHandler eh_nullExceed; #endregion Public Events #region Public Properties /// /// The user identity to use when creating the session. /// public IUserIdentity CurrUserIdentity { get; set; } = new UserIdentity(); /// /// Gets or sets the server URL. /// public string ServerUrl { get; set; } = "opc.tcp://localhost:4840"; /// /// Gets the client session. /// public Session Session => m_session; #endregion Public Properties #region Public Methods /// /// Browse Server nodes /// public bool Browse(ushort startNodeNS, uint startNodeVal, List vetoBrowse, ref Dictionary nodeIdNameList) { bool fatto = false; if (m_session == null || m_session.Connected == false) { lgError("Session not connected!"); return false; } try { // Create a Browser object Browser browser = new Browser(m_session); // Set browse parameters browser.BrowseDirection = BrowseDirection.Forward; browser.NodeClassMask = (int)NodeClass.Object | (int)NodeClass.Variable; browser.ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences; //NodeId nodeToBrowse = ObjectIds.Server; //NodeId nodeToBrowse = new NodeId("ns=4,i=5001"); NodeId nodeToBrowse = new NodeId(startNodeVal, startNodeNS); // Call Browse service lgTrace($"Browsing {nodeToBrowse} node..."); ReferenceDescriptionCollection browseResults = browser.Browse(nodeToBrowse); // Display the results lgTrace($"Browse returned {browseResults.Count} results:"); foreach (ReferenceDescription result in browseResults) { lgTrace($" NodeId = {result.NodeId}, TypeId = {result.TypeId}, DisplayName = {result.DisplayName.Text}, NodeClass = {result.NodeClass}, Others: {result.BinaryEncodingId} | {result.BrowseName}"); // se NON fa parte dell'elenco dei VETO di filterItems... if (!vetoBrowse.Contains($"{result.NodeId}")) { // se mancasse aggiungo... if (!nodeIdNameList.ContainsKey($"{result.NodeId}")) { nodeIdNameList.Add(result.NodeId.ToString(), result.DisplayName.Text); } } } fatto = true; } catch (Exception ex) { // Log Error lgError($"Browse Error : {ex.Message}"); } return fatto; } /// /// Browse Server nodes /// public bool Browse(string browsePath, List vetoBrowse, ref Dictionary nodeIdNameList) { bool fatto = false; if (m_session == null || m_session.Connected == false) { lgError("Session not connected!"); return false; } try { // Create a Browser object Browser browser = new Browser(m_session); // Set browse parameters browser.BrowseDirection = BrowseDirection.Forward; browser.NodeClassMask = (int)NodeClass.Object | (int)NodeClass.Variable; browser.ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences; //NodeId nodeToBrowse = ObjectIds.Server; //NodeId nodeToBrowse = new NodeId("ns=4;i=5001"); NodeId nodeToBrowse = new NodeId(browsePath); //nodeToBrowse = ObjectIds.Server; //nodeToBrowse = new NodeId("Calibratrice_L1", 4); //nodeToBrowse = new NodeId("Dati_Mes", 4); //nodeToBrowse = new NodeId(5001, 2); //nodeToBrowse = new NodeId("ns=4;s=NxController"); //nodeToBrowse = new NodeId("ns=4;s=Dati_Mes"); //nodeToBrowse = new NodeId("NxController.GlobalVars", 4); //nodeToBrowse = new NodeId("Dati_Mes", 4); // Call Browse service lgTrace($"Browsing {nodeToBrowse} node..."); ReferenceDescriptionCollection browseResults = browser.Browse(nodeToBrowse); // Display the results lgTrace($"Browse returned {browseResults.Count} results:"); foreach (ReferenceDescription result in browseResults) { // se veto --> loggo veto if (vetoBrowse.Contains($"{result.NodeId}")) { lgDebug($"FILTER --> NodeId = {result.NodeId}, DisplayName = {result.DisplayName.Text}, NodeClass = {result.NodeClass}, Others: {result.BinaryEncodingId} | {result.BrowseName}"); } // se NON fa parte dell'elenco dei VETO di filterItems... else { lgDebug($" NodeId = {result.NodeId}, DisplayName = {result.DisplayName.Text}, NodeClass = {result.NodeClass}, Others: {result.BinaryEncodingId} | {result.BrowseName}"); // se mancasse aggiungo... if (!nodeIdNameList.ContainsKey($"{result.NodeId}")) { nodeIdNameList.Add($"{result.NodeId}", result.DisplayName.Text); // se è un nodo object --> faccio sub browse! if (result.NodeClass != NodeClass.Variable) { this.Browse($"{result.NodeId}", vetoBrowse, ref nodeIdNameList); } } } } fatto = true; } catch (Exception ex) { // Log Error lgError($"Browse Error : {ex.Message}"); } return fatto; } /// /// Call UA method /// public void CallMethod() { if (m_session == null || m_session.Connected == false) { lgError("Session not connected!"); return; } try { // Define the UA Method to call Parent node - Objects\CTT\Methods Method node - Objects\CTT\Methods\Add NodeId objectId = new NodeId("ns=2;s=Methods"); NodeId methodId = new NodeId("ns=2;s=Methods_Add"); // Define the method parameters Input argument requires a Float and an UInt32 value object[] inputArguments = new object[] { (float)10.5, (uint)10 }; IList outputArguments = null; // Invoke Call service lgDebug($"Calling UAMethod for node {methodId} ..."); outputArguments = m_session.Call(objectId, methodId, inputArguments); // Display results lgDebug($"Method call returned {outputArguments.Count} output argument(s):"); foreach (var outputArgument in outputArguments) { lgDebug($" OutputValue = {outputArgument}"); } } catch (Exception ex) { lgError($"Method call error: {ex.Message}"); } } /// /// Creates a session with the UA server /// public async Task ConnectAsync() { try { if (m_session != null && m_session.Connected == true) { lgInfo("Session already connected!"); } else { lgInfo("Connecting..."); // Get the endpoint by connecting to server's discovery endpoint. Try to find // the first endopint without security. EndpointDescription endpointDescription = CoreClientUtils.SelectEndpoint(ServerUrl, false); EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(m_configuration); ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration); // Create the session Session session = await Session.Create( m_configuration, endpoint, true, false, m_configuration.ApplicationName, 60 * 60 * 1000, CurrUserIdentity, null ); #if false Session session = await Session.Create( m_configuration, endpoint, false, false, m_configuration.ApplicationName, 60 * 60 * 1000, CurrUserIdentity, null ); #endif // Assign the created session if (session != null && session.Connected) { m_session = session; } // Session created successfully. lgInfo($"New Session Created with SessionName = {m_session.SessionName}"); } return true; } catch (Exception ex) { // Log Error lgError($"Create Session Error : {ex.Message}"); return false; } } /// /// Disconnects the session. /// public void Disconnect() { try { if (m_session != null) { lgInfo("Disconnecting..."); m_session.Close(); m_session.Dispose(); m_session = null; // Log Session Disconnected event lgInfo("Session Disconnected."); } else { lgError("Session not created!"); } } catch (Exception ex) { // Log Error lgError($"Disconnect Error : {ex.Message}"); } } /// /// 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(); //Read the dataValue node = m_session.ReadNode(nodeId); return node; } /// Reads a node by node Id /// The node Id as string /// A dictionary containing the attribute values of the node using the attribute uint identifier as key; /// Throws and forwards any exception with short error description. public Dictionary ReadNodeAttributes(String nodeIdString) { //Create a nodeId using the identifier string NodeId nodeId = new NodeId(nodeIdString); return ReadNodeAttributes(nodeId); } /// Reads a node by node Id /// The node Id /// A dictionary containing the attribute values of the node using the attribute uint identifier as key; /// Throws and forwards any exception with short error description. public Dictionary ReadNodeAttributes(NodeId nodeId) { Dictionary attributeDictionary = new Dictionary() { {1, Attributes.NodeId }, {2, Attributes.NodeClass }, {3, Attributes.BrowseName }, {4, Attributes.DisplayName }, {5, Attributes.Value }, {6, Attributes.DataType}, {7, Attributes.ValueRank}, {8, Attributes.ArrayDimensions}, {9, Attributes.AccessLevel } }; //Create a read value id collection to store the nodes to read ReadValueIdCollection nodesToRead = new ReadValueIdCollection(); //Create a StatusCodeCollection DataValueCollection dataValueCollection = new DataValueCollection(); //Create a DiagnosticInfoCollection DiagnosticInfoCollection diag = new DiagnosticInfoCollection(); //Go through the attribute dictionary and create the nodes accordingly for (int i = 0; i < attributeDictionary.Count; i++) { ///Create a read value id with the nessessary attribute ReadValueId nodeToRead = new ReadValueId(); nodeToRead.NodeId = nodeId; nodeToRead.AttributeId = attributeDictionary[i + 1]; nodesToRead.Add(nodeToRead); } //Read the read value id collection m_session.Read(null, 0, TimestampsToReturn.Neither, nodesToRead, out dataValueCollection, out diag); //Create a dictionary to store the attribute values Dictionary attributeValues = new Dictionary(); //Go through the data value collection to store every data value according to its attribute identifier int counter = 1; foreach (DataValue dataValue in dataValueCollection) { attributeValues.Add(attributeDictionary[counter], dataValue); counter += 1; } return attributeValues; } /// /// Read a SINGLE of nodes value (RAW) from Server /// /// /// public object ReadNodeRaw(NodeId reqNodeId) { object answ = null; if (m_session == null || m_session.Connected == false) { lgError("Session not connected!"); numErrors++; return answ; } try { #region Read the Value attribute of a node by calling the Session.ReadValue method try { DataValue resp = m_session.ReadValue(reqNodeId); answ = resp; } catch (Exception exc) { // Log Error lgError($"ReadNodeRaw Error 01: {Environment.NewLine}{exc}"); numErrors++; // provare disconnect?!? if (numErrors >= maxErrors) { numErrors = 0; Disconnect(); } } #endregion Read the Value attribute of a node by calling the Session.ReadValue method } catch (Exception ex) { // Log Error lgError($"ReadNodeRaw Error 02: {ex.Message}."); } return answ; } /// /// Read a list of nodes from Server /// public void ReadNodes() { if (m_session == null || m_session.Connected == false) { lgError("Session not connected!"); return; } try { #region Read a node by calling the Read Service // build a list of nodes to be read ReadValueIdCollection nodesToRead = new ReadValueIdCollection() { // Value of ServerStatus new ReadValueId() { NodeId = Variables.Server_ServerStatus, AttributeId = Attributes.Value }, // BrowseName of ServerStatus_StartTime new ReadValueId() { NodeId = Variables.Server_ServerStatus_StartTime, AttributeId = Attributes.BrowseName }, // Value of ServerStatus_StartTime new ReadValueId() { NodeId = Variables.Server_ServerStatus_StartTime, AttributeId = Attributes.Value } }; // Read the node attributes lgInfo("Reading nodes..."); // Call Read Service m_session.Read( null, 0, TimestampsToReturn.Both, nodesToRead, out DataValueCollection resultsValues, out DiagnosticInfoCollection diagnosticInfos); // Validate the results m_validateResponse(resultsValues, nodesToRead); // Display the results. foreach (DataValue result in resultsValues) { lgTrace($"Read Value = {result.Value} , StatusCode = {result.StatusCode}"); } #endregion Read a node by calling the Read Service #region Read the Value attribute of a node by calling the Session.ReadValue method // Read Server NamespaceArray lgTrace("Reading Value of NamespaceArray node..."); DataValue namespaceArray = m_session.ReadValue(Variables.Server_NamespaceArray); // Display the result lgTrace($"NamespaceArray Value = {namespaceArray}"); #endregion Read the Value attribute of a node by calling the Session.ReadValue method } catch (Exception ex) { // Log Error lgError($"Read Nodes Error : {ex.Message}."); } } /// /// Read a SINGLE of nodes value from Server /// /// /// Value as String public string ReadNodeString(NodeId reqNodeId) { string answ = ""; if (m_session == null || m_session.Connected == false) { lgError("Session not connected!"); return answ; } try { try { DataValue resp = m_session.ReadValue(reqNodeId); answ = $"{resp.Value}"; // decremento errore... if (numErrors > 0) { numErrors--; } } catch (Exception exc) { // Log Error numErrors++; lgTrace($"ReadValue Error | reqNodeId: {reqNodeId} | numErrors: {numErrors}{Environment.NewLine}{exc}"); } } catch (Exception ex) { // Log Error lgError($"Read Nodes Error : {ex.Message}."); numErrors++; } // se supero maxErrors --> invio eccezione e resetto... if (numErrors >= maxErrors) { // reset + exception numErrors = 0; throw new Exception($"Errore lettura OPC-UA: {maxErrors} errori"); } return answ; } /// 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(); //Read the node attributes Dictionary nodeAttributes = ReadNodeAttributes(nodeIdString); //Create a data value storing the value DataValue data = nodeAttributes[Attributes.Value]; //Check if the entered node id is of type array and accordingly get the byte array of the data values ExtensionObject dataValue = new ExtensionObject(); List byteArrays = new List(); if (data.Value is ExtensionObject[]) { foreach (ExtensionObject temp in (ExtensionObject[])data.Value) { byteArrays.Add((byte[])temp.Body); } } else if (data.Value is ExtensionObject) { dataValue = (ExtensionObject)data.Value; byteArrays.Add((byte[])dataValue.Body); } else { throw new Exception("The entered node id is not of tpye struct/udt."); } //Get the type dictionary of the struct as a list of string[3]; string[0] = tag name, string[1] = data type, string [2] = array dimensions List structureDictionary = new List(); DataTypeNode dataTypeNode = (DataTypeNode)ReadNode(nodeAttributes[Attributes.DataType].ToString()); GetTypeDictionary(dataTypeNode, m_session, structureDictionary); //Get the deserialized byte string as a list of string[4]; string[0]=array index; string[1]=tag name; string[2]=tag value; string[3]=tag data type resultStringList = ParseDataToTagsFromDictionary(structureDictionary, byteArrays); //return result as List string[0]=array index; string[1]=tag name; string[2]=tag value; string[3]=tag data type return resultStringList; } /// /// Create Subscription and MonitoredItems for DataChanges /// public List SubscribeToDataChanges(Dictionary DataList) { List monItList = new List(); if (m_session == null || m_session.Connected == false) { lgError("Session not connected!"); return monItList; } try { // Create a subscription for receiving data change notifications // Define Subscription parameters Subscription subscription = new Subscription(m_session.DefaultSubscription); subscription.DisplayName = "Steamware IOB-WIN Subscription"; subscription.PublishingEnabled = true; subscription.PublishingInterval = 1000; m_session.AddSubscription(subscription); // Create the subscription on Server side subscription.Create(); lgInfo($"New Subscription created with SubscriptionId = {subscription.Id}"); // Create MonitoredItems for data changes foreach (var item in DataList) { MonitoredItem currMonIt = new MonitoredItem(subscription.DefaultItem); // Int32 Node - Objects\CTT\Scalar\Simulation\Int32 currMonIt.StartNodeId = new NodeId(item.Key); currMonIt.AttributeId = Attributes.Value; currMonIt.DisplayName = item.Value; currMonIt.SamplingInterval = 1000; currMonIt.Notification += OnMonitoredItemNotification; subscription.AddItem(currMonIt); monItList.Add(currMonIt); } // Create the monitored items on Server side subscription.ApplyChanges(); lgInfo($"MonitoredItems created for SubscriptionId = {subscription.Id}"); } catch (Exception ex) { lgError($"Subscribe error: {ex.Message}"); } return monItList; } /// /// Write a list of nodes to the Server /// /// public void WriteNodes(List node2Write) { if (node2Write != null) { if (m_session == null || m_session.Connected == false) { lgError("Session not connected!"); return; } try { // Write the configured nodes WriteValueCollection nodesToWrite = new WriteValueCollection(); nodesToWrite.AddRange(node2Write); // Write the node attributes StatusCodeCollection results = null; DiagnosticInfoCollection diagnosticInfos; lgDebug("Writing nodes..."); // Call Write Service m_session.Write(null, nodesToWrite, out results, out diagnosticInfos); // Validate the response m_validateResponse(results, nodesToWrite); // Display the results. lgDebug("Write Results :"); foreach (StatusCode writeResult in results) { lgDebug($" {writeResult}"); } } catch (Exception ex) { // Log Error lgError($"Write Nodes Error : {ex.Message}"); numErrors++; // provare disconnect?!? if (numErrors >= maxErrors) { numErrors = 0; Disconnect(); } } } } /// /// Metodo scrittura alternativa in formato UDT dei dati... /// /// public void WriteNodesUdt(string nodeIdString, Dictionary data2write) { if (!string.IsNullOrEmpty(nodeIdString) && data2write != null && data2write.Count > 0) { if (m_session == null || m_session.Connected == false) { lgError("Session not connected!"); return; } try { var listUdtRows = new List(); listUdtRows = new List(); // leggo elenco dati in primis listUdtRows = ReadStructUdt(nodeIdString); // va scartata ultima riga!!!! listUdtRows.RemoveAt(listUdtRows.Count - 1); // sostituzione dei dati: per ogni valore corrente cerco nei valori data2write foreach (var item in listUdtRows) { // se trovo valore da sostituire if (data2write.ContainsKey(item[1])) { item[2] = data2write[item[1]]; } } // scrivo WriteStructUdt(nodeIdString, listUdtRows); } catch (Exception ex) { // Log Error lgError($"Error WriteNodesUdt for {nodeIdString}: {Environment.NewLine}{ex.Message}"); numErrors++; // provare disconnect?!? if (numErrors >= maxErrors) { numErrors = 0; Disconnect(); } } } } /// /// Writes values to node Ids /// /// Mulitple NodeIds with there values (multiple possible if not a scalar). /// Throws and forwards any exception with short error description. public void WriteValues(Dictionary> valuesByNodeId) { //Create a collection of values to write WriteValueCollection valuesToWrite = new WriteValueCollection(); foreach (var keyValuePair in valuesByNodeId) { NodeId nodeId = keyValuePair.Key; IEnumerable values = keyValuePair.Value; //Get the OPC UA data type and the Rank Node node = m_session.ReadNode(nodeId); VariableNode variableNode = (VariableNode)node.DataLock; NodeId datatypeId = variableNode.DataType; BuiltInType targetype = TypeInfo.GetBuiltInType(datatypeId); //Ensure that target type is not null by reading the node of the data type id if (targetype == BuiltInType.Null) { try { //Get the node id of the parent base data type NodeId tempNodeId = GetParentDataType(datatypeId, m_session); targetype = TypeInfo.GetBuiltInType(tempNodeId); if (targetype == BuiltInType.Null)//Ensure that the entered node id is not of type struct { throw new Exception("The entered node id may be of type struct. Please use the methods for read/write structs."); } } catch { throw new Exception("The node id data type is not castable. Please check the data type."); } } int valueRank = variableNode.ValueRank; DataValue dataValue = null; //Check if there is a value entered if (!values.Any() || nodeId == null) //no value in the textbox { throw new Exception("There is no NodeId or value entered."); } //Ensure that boolean entries have lower case initial and float, double and long double have the rigth format List tempValues = new List(); if (targetype == BuiltInType.Boolean) { foreach (string tempValue in values) { tempValues.Add(tempValue.ToLower()); } values = tempValues; } if (targetype == BuiltInType.Float || targetype == BuiltInType.Double) { foreach (string tempValue in values) { tempValues.Add(tempValue.Replace(',', '.')); } values = tempValues; } //Check if the inputed value has the type matrix or array if (valueRank >= ValueRanks.OneDimension) { //Cast values in the target type Array castValues = TypeInfo.CastArray(values.ToArray(), BuiltInType.Null, targetype, (object source, BuiltInType srcType, BuiltInType dstType) => TypeInfo.Cast(source, dstType)); if (valueRank == ValueRanks.OneDimension) //Check if inputed value is array { dataValue = new DataValue(new Variant(castValues, new TypeInfo(targetype, valueRank))); } else //Inputed value has more then one dimension { //Get matrix dimensions int[] dimensions = new int[valueRank]; for (int i = 0; i < valueRank; i++) { dimensions[i] = (int)variableNode.ArrayDimensions[i]; } Matrix matrix = new Matrix(castValues, targetype, dimensions); dataValue = new DataValue(new Variant(matrix)); } } else { try //OPC UA data type with type scalar { dataValue = new DataValue(new Variant(TypeInfo.Cast(values.First(), targetype))); } catch //no OPC UA data type { throw new FormatException($"The value inputed [{values.First()}] is not convertable to type {targetype}."); } } //Create a WriteValue using the NodeId, dataValue and attributeType WriteValue valueToWrite = new WriteValue() { Value = dataValue, NodeId = nodeId, AttributeId = Attributes.Value }; //Add the dataValues to the collection valuesToWrite.Add(valueToWrite); } StatusCodeCollection result; //Write the collection to the server m_session.Write(null, valuesToWrite, out result, out _); foreach (StatusCode code in result) { lgDebug($" {code}"); #if false if (code != 0) { throw new Exception(code.ToString()); } #endif } } /// /// Write a list of nodes to the Server /// public void WriteSingleNode(WriteValue node2Write) { if (node2Write != null) { if (m_session == null || m_session.Connected == false) { lgError("Session not connected!"); return; } try { // Write the configured nodes WriteValueCollection nodesToWrite = new WriteValueCollection(); nodesToWrite.Add(node2Write); // Write the node attributes StatusCodeCollection results = null; DiagnosticInfoCollection diagnosticInfos; lgDebug("Writing nodes..."); // Call Write Service m_session.Write(null, nodesToWrite, out results, out diagnosticInfos); // Validate the response m_validateResponse(results, nodesToWrite); // Display the results. lgDebug("Write Results :"); foreach (StatusCode writeResult in results) { lgDebug($" {writeResult}"); } } catch (Exception ex) { // Log Error lgError($"Write Nodes Error : {ex.Message}"); } } } /// 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(); //Read the node attributes Dictionary nodeAttributes = ReadNodeAttributes(nodeIdString); //Create a data value storing the value DataValue data = nodeAttributes[Attributes.Value]; //Check if the entered node id is of type array and accordingly get the byte array(s) of the data values ExtensionObject extensionObject = new ExtensionObject(); List byteArrays = new List(); if (data.Value is ExtensionObject[]) { foreach (ExtensionObject temp in (ExtensionObject[])data.Value) { byteArrays.Add((byte[])temp.Body); } } else { extensionObject = (ExtensionObject)data.Value; byteArrays.Add((byte[])extensionObject.Body); } //Get the type dictionary of the struct as a list of string[3]; string[0] = tag name, string[1] = data type, string [2] = array dimensions List structureDictionary = new List(); DataTypeNode dataTypeNode = (DataTypeNode)ReadNode(nodeAttributes[Attributes.DataType].ToString()); GetTypeDictionary(dataTypeNode, m_session, structureDictionary); //Delete empty list elements in the structure dictionary and in the list of the gui for (int j = 0; j < structureDictionary.Count; j++) { if (structureDictionary[j][1] == "")//Check if the data type entry is empty as this is an indicator for a struct/udt in a struct/udt { structureDictionary.RemoveAt(j); //dataToWrite.RemoveAt(j); } } //Create a byte array List bytesToWrite; //Parse dataToWrite to the byte array bytesToWrite = ParseDataToByteArray(dataToWrite, structureDictionary); //Create a StatusCodeCollection StatusCodeCollection results = new StatusCodeCollection(); //Create a DiagnosticInfoCollection DiagnosticInfoCollection diag = new DiagnosticInfoCollection(); //Get the structure definition StructureDefinition structureDefinition = (StructureDefinition)dataTypeNode.DataTypeDefinition.Body; //Create an ExtensionObject from the Structure given to this function ExtensionObject writeExtObj = new ExtensionObject(); //Copy the encoding node id to the extension object type id writeExtObj.TypeId = structureDefinition.DefaultEncodingId; //Copy data to extension object body DataValue dataValue = null; if (bytesToWrite.Count == 1) { //Copy data to extension object body writeExtObj.Body = bytesToWrite[0]; //Turn the created ExtensionObject into a DataValue dataValue = new DataValue(writeExtObj); } else { ExtensionObject[] writeExteObjArr = new ExtensionObject[bytesToWrite.Count]; for (int i = 0; i < bytesToWrite.Count; i++) { //Create an ExtensionObject from the Structure given to this function writeExtObj = new ExtensionObject(); //Copy data to extension object body writeExtObj.TypeId = structureDefinition.DefaultEncodingId; writeExtObj.Body = bytesToWrite[i]; //Copy extension object to extension object array writeExteObjArr[i] = writeExtObj; } //Turn the created ExtensionObject[] into a DataValue dataValue = new DataValue(writeExteObjArr); } //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 { m_session.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; } } } /// /// Write a list of nodes to the Server /// public void WriteTestNodes() { if (m_session == null || m_session.Connected == false) { lgError("Session not connected!"); return; } try { // scrivo vaslori a caso.. hhmm odierni int hhmm = 9876; int.TryParse(DateTime.Now.ToString("HHmm"), out hhmm); // Write the configured nodes WriteValueCollection nodesToWrite = new WriteValueCollection(); // Int32 Node - Objects\CTT\Scalar\Scalar_Static\Int32 WriteValue commWriteVal = new WriteValue(); commWriteVal.NodeId = new NodeId("ns=4;s=IO_151"); commWriteVal.AttributeId = Attributes.Value; commWriteVal.Value = new DataValue(); commWriteVal.Value.Value = (int)hhmm - 10; nodesToWrite.Add(commWriteVal); WriteValue artWriteVal = new WriteValue(); artWriteVal.NodeId = new NodeId("ns=4;s=IO_151"); artWriteVal.AttributeId = Attributes.Value; artWriteVal.Value = new DataValue(); artWriteVal.Value.Value = (int)hhmm; nodesToWrite.Add(artWriteVal); WriteValue qtyWriteVal = new WriteValue(); qtyWriteVal.NodeId = new NodeId("ns=4;s=IO_153"); qtyWriteVal.AttributeId = Attributes.Value; qtyWriteVal.Value = new DataValue(); qtyWriteVal.Value.Value = (int)hhmm + 10; nodesToWrite.Add(qtyWriteVal); // Write the node attributes StatusCodeCollection results = null; DiagnosticInfoCollection diagnosticInfos; lgDebug("Writing nodes..."); // Call Write Service m_session.Write(null, nodesToWrite, out results, out diagnosticInfos); // Validate the response m_validateResponse(results, nodesToWrite); // Display the results. lgDebug("Write Results :"); foreach (StatusCode writeResult in results) { lgDebug($" {writeResult}"); } } catch (Exception ex) { // Log Error lgError($"Write Nodes Error : {ex.Message}."); } } #endregion Public Methods #region Protected Fields protected static bool isLogVerbose = false; protected static Logger lg; protected int currNullReceiv = 0; protected int maxErrors = 20; protected int maxNullAllowed = 100; protected int numErrors = 0; #endregion Protected Fields #region Protected Methods /// /// Effettua logging DEBUG corretto impostanto anche la variabile IOB prima di scrivere... /// /// protected void lgDebug(string message) { lg.Factory.Configuration.Variables["codIOB"] = currIob; lg.Debug(message); } /// /// Effettua logging ERROR corretto impostanto anche la variabile IOB prima di scrivere... /// /// protected void lgError(string message) { lg.Factory.Configuration.Variables["codIOB"] = currIob; lg.Error(message); } /// /// Effettua logging INFO corretto impostanto anche la variabile IOB prima di scrivere... /// /// protected void lgInfo(string message) { lg.Factory.Configuration.Variables["codIOB"] = currIob; lg.Info(message); } /// /// Effettua logging TRACE corretto impostanto anche la variabile IOB prima di scrivere... /// /// protected void lgTrace(string message) { lg.Factory.Configuration.Variables["codIOB"] = currIob; lg.Trace(message); } #endregion Protected Methods #region Private Fields private readonly string currIob; private readonly Action m_validateResponse; private ApplicationConfiguration m_configuration; private Session m_session; #endregion Private Fields #region Private Methods /// /// Browses for the node id of the parent data type of type base data type /// /// The data to write as string[4]; string[0] = index, string[1] = tag name, string[2] = value, string[3] = opc data type /// The node id of the parent data type /// Throws and forwards any exception with short error description. private static NodeId GetParentDataType(NodeId nodeId, Session theSessionToBrowseIn) { ReferenceDescriptionCollection referenceDescriptionCollection; byte[] continuationPoint; theSessionToBrowseIn.Browse(null, null, nodeId, 1, BrowseDirection.Inverse, ReferenceTypeIds.HasSubtype, true, 0, out continuationPoint, out referenceDescriptionCollection); NodeId nodeIdParentDataType = (NodeId)referenceDescriptionCollection[0].NodeId; if (nodeIdParentDataType.NamespaceIndex != 0) { nodeIdParentDataType = GetParentDataType(nodeIdParentDataType, theSessionToBrowseIn); } return nodeIdParentDataType; } /// /// Browses for the desired type dictonary to parse for containing data types /// /// The data type node /// The current session to browse in /// The list of string arrays containing the structure information tag name, data type id and array dimension /// The dictionary as List of string arrays /// Throws and forwards any exception with short error description. private static List GetTypeDictionary(DataTypeNode dataTypeNode, Session theSessionToBrowseIn, List structureDictionary) { //Create a temp list of string containing the structure dictionary List tempStructureDictionary = new List(); tempStructureDictionary = structureDictionary; Session tempSession = theSessionToBrowseIn; //Get Structure Definition StructureFieldCollection structureFieldCollection = null; try { StructureDefinition structureDefinition = (StructureDefinition)dataTypeNode.DataTypeDefinition.Body; structureFieldCollection = (StructureFieldCollection)structureDefinition.Fields; } catch { structureDictionary[structureDictionary.Count - 1][1] = UAClient.GetParentDataType(dataTypeNode.NodeId, tempSession).ToString(); return structureDictionary; } //Check every structure field in the collection foreach (StructureField structureField in structureFieldCollection) { if (structureField.DataType.NamespaceIndex != 0) //Check for structures in the structure { //Create a reference description collection ReferenceDescriptionCollection referenceDescriptionCollection; //Create a continuationPoint byte[] continuationPoint; //Browse for the references of the data type of the structure field theSessionToBrowseIn.Browse( null, null, structureField.DataType, 0u, BrowseDirection.Both, ReferenceTypeIds.References, true, 0, out continuationPoint, out referenceDescriptionCollection); if (((NodeId)referenceDescriptionCollection[0].NodeId).NamespaceIndex == 0)//Data type is sub type of a base data type, i.e. BYTE is a sub tpye of Byte { //Check if the structure field is an array and parse the dimension to string string arrayDimensions = ""; if (structureField.ValueRank == ValueRanks.OneDimension) { arrayDimensions = structureField.ArrayDimensions[0].ToString(); } //Add the structure field name, its data type node id and array dimension to the dictionary list tempStructureDictionary.Add(new string[] { structureField.Name, ((NodeId)referenceDescriptionCollection[0].NodeId).ToString(), arrayDimensions }); } else//The structure/udt has at least one additional structure/udt inside { //Read the data type node id of the structure/udt in the parent structure/udt DataTypeNode tempDataTypeNode = (DataTypeNode)theSessionToBrowseIn.ReadNode(structureField.DataType.ToString()); //Enter an empty Line to seperate the inside structure from the parent tempStructureDictionary.Add(new string[] { structureField.Name + " - " + "struct/udt of type: " + tempDataTypeNode.DisplayName.ToString(), "", "" }); //Copy the temp dictionary to the real dictionary structureDictionary = tempStructureDictionary; //Recursively get the structure of the structure/udt in the parent structure/udt GetTypeDictionary(tempDataTypeNode, tempSession, structureDictionary); } } else //The structure field is a base data type { //Check if the structure field is an array and parse the dimension to string string arrayDimensions = ""; if (structureField.ValueRank == ValueRanks.OneDimension) { arrayDimensions = structureField.ArrayDimensions[0].ToString(); } //Add the structure field name, its data type node id and array dimension to the dictionary list tempStructureDictionary.Add(new string[] { structureField.Name, structureField.DataType.ToString(), arrayDimensions }); } //Copy the temp dictionary to the real dictionary structureDictionary = tempStructureDictionary; } //Add a row for marking the end of the struct/udt structureDictionary.Add(new string[] { "/" + dataTypeNode.DisplayName.ToString(), "", "" }); return structureDictionary; } /// /// Parses data to write to a list of byte arrays /// /// The data to write as string[4]; string[0] = index, string[1] = tag name, string[2] = value, string[3] = opc data type /// The structure of the udt/struct /// The list of the parsed byte arrays /// Throws and forwards any exception with short error description. private static List ParseDataToByteArray(List dataToWrite, List structureDictionary) { //Define result byte list to return a byte array List resultByteList = new List(); //Dictionary to get the right byte length depending on the data type Dictionary byteLength = new Dictionary() { {BuiltInType.Boolean, 1}, {BuiltInType.Int16, 2}, {BuiltInType.Int32, 4}, {BuiltInType.Int64, 8}, {BuiltInType.UInt16, 2}, {BuiltInType.UInt32, 4}, {BuiltInType.UInt64, 8}, {BuiltInType.Float, 4}, {BuiltInType.Double, 8}, {BuiltInType.Byte, 1}, {BuiltInType.String, 4} }; //Counter for the structure dictionary browsing int tempCounter = 0; //Create byte array to store the data of one array byte[] byteArray = new byte[0]; //Start decoding for opc data types foreach (string[] element in dataToWrite) { //Reset the counter and the byteArray each time the counter reaches the size of the target Structure/UDT and the data to write include the target Structure/UDT several times if (tempCounter == structureDictionary.Count) { //Add the byte array to the result byte array list resultByteList.Add(byteArray); //Reset the counter and the array byteArray = new byte[0]; tempCounter = 0; } //Create byte array to store the bytes of one value depending on its data type, hence it is reseted to 0 for each element byte[] dataByteArray = new byte[0]; //Get the data type id and the target type by using the structure dictionary NodeId datatypeId = new NodeId(structureDictionary[tempCounter][1]); BuiltInType targetType = TypeInfo.GetBuiltInType(datatypeId); //Get the system type for getting the rigth parse method Type systemType = TypeInfo.GetSystemType(datatypeId, null); //Parse array dimension string of the structure dictionary in int32 Int32 arraySize = 0; if (structureDictionary[tempCounter][2] != "") { arraySize = Int32.Parse(structureDictionary[tempCounter][2]); } //Serialize the byte string depending on the target data type and the array size - special if queries are needed for byte and string if (targetType == BuiltInType.String && !(arraySize > 0)) { //Resize the data byte array to its correct size depending on the byte length of target type by using the dictionary dataByteArray = new byte[byteLength[targetType]]; //Get the bytes of the string length and copy them to the beginning of the data byte array Array.Copy(BitConverter.GetBytes(element[2].Length), 0, dataByteArray, 0, byteLength[targetType]); //Convert the string to byte and concat them to the data byte array by converting every char of the string foreach (Char c in element[2]) { byte[] tempArray = new byte[1]; tempArray[0] = Convert.ToByte(c); dataByteArray = dataByteArray.Concat(tempArray).ToArray(); } } else if (targetType == BuiltInType.String && arraySize > 0) { //Create a temp string array with the array size string[] tempStringArr = new string[(arraySize - 1)]; //Resize the data byte array to its correct size depending on the byte length of target type by using the dictionary dataByteArray = new byte[byteLength[BuiltInType.UInt32]]; //Get the bytes of the string length and copy them to the beginning of the data byte array Array.Copy(BitConverter.GetBytes(arraySize), 0, dataByteArray, 0, byteLength[targetType]); //Get the single array elements by splitting the string tempStringArr = element[2].Split(';'); //Go through every string element in the temp string array for (int ii = 0; ii < arraySize; ii++) { //Copy the string length as byte to the data byte array byte[] tempArray = new byte[byteLength[targetType]]; Array.Copy(BitConverter.GetBytes(tempStringArr[ii].Length), 0, tempArray, 0, byteLength[targetType]); dataByteArray = dataByteArray.Concat(tempArray).ToArray(); //Convert the string to byte and concat them to the data byte array by converting every char of the string foreach (Char c in tempStringArr[ii]) { byte[] tempArraySingle = new byte[1]; tempArraySingle[0] = Convert.ToByte(c); dataByteArray = dataByteArray.Concat(tempArraySingle).ToArray(); } } } else if (targetType == BuiltInType.Byte && !(arraySize > 0)) { //Convert the string of byte to byte dataByteArray = new byte[byteLength[targetType]]; dataByteArray[0] = Convert.ToByte(element[2]); } else if (targetType == BuiltInType.Byte && arraySize > 0) { //Create a temp string variable to store the array elements which are divided by ; String tempString = ""; //Resize the data byte array to its correct size depending on the byte length of target type by using the dictionary dataByteArray = new byte[byteLength[BuiltInType.UInt32]]; //Get the bytes of the string length and copy them to the beginning of the data byte array Array.Copy(BitConverter.GetBytes(arraySize), 0, dataByteArray, 0, byteLength[targetType]); //Check every char in the string value of the element foreach (Char c in element[2]) { if (c != ';') //Concat every char to a string value in elements except the ; { tempString = String.Concat(tempString, c); } else { //Create a temp byte array with its correct size depending on the byte length of the target data type byte[] tempArray = new byte[byteLength[targetType]]; tempArray[0] = Convert.ToByte(tempString); //Copy the temp array to the data byte array dataByteArray = dataByteArray.Concat(tempArray).ToArray(); //Reset the temp string tempString = ""; } } } else if (!(arraySize > 0)) { //Create the byte array with its correct size depending on the byte length of the target data type dataByteArray = new byte[byteLength[targetType]]; //Get the converter method depending on the system type System.Reflection.MethodInfo methodInfo = typeof(Convert).GetMethod("To" + systemType.Name, new Type[] { typeof(string) }); //Call the converter method dynamic convertDataType = methodInfo.Invoke(null, new Object[] { element[2] }); Array.Copy(BitConverter.GetBytes(convertDataType), 0, dataByteArray, 0, byteLength[targetType]); } else if (arraySize > 0) { //Create a temp string variable to store the array elements which are divided by ; String tempString = ""; //Create the byte array with the size of a UInt32, which is the data type for the array dimension dataByteArray = new byte[byteLength[BuiltInType.UInt32]]; //Get the bytes of the array dimension and copy them to the data byte array as they always are at the beginning of a byte array of an array Array.Copy(BitConverter.GetBytes(arraySize), 0, dataByteArray, 0, byteLength[targetType]); //Check every char in the string value of the element foreach (Char c in element[2]) { if (c != ';') //Concat every char to a string value in elements except the ; { tempString = String.Concat(tempString, c); } else { //Create a temp byte array with its correct size depending on the byte length of the target data type byte[] tempArray = new byte[byteLength[targetType]]; //Get the converter method depending on the system type System.Reflection.MethodInfo methodInfo = typeof(Convert).GetMethod("To" + systemType.Name, new Type[] { typeof(string) }); //Call the converter method dynamic convertDataType = methodInfo.Invoke(null, new Object[] { element[2] }); Array.Copy(BitConverter.GetBytes(convertDataType), 0, tempArray, 0, byteLength[targetType]); //Concat the temp array with the data array dataByteArray = dataByteArray.Concat(tempArray).ToArray(); //Reset the temp string tempString = ""; } } } else { Exception e = new Exception("Data type is too complex to be parsed." + System.Environment.NewLine); throw e; } //Add the data byte array to the byte array containing all data and count the counter byteArray = byteArray.Concat(dataByteArray).ToArray(); tempCounter++; } resultByteList.Add(byteArray); return resultByteList; } /// /// 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[4]; string[0] = tag name, string[1] = tag name, string[2] = value, string[3] = opc data type /// Throws and forwards any exception with short error description. private static List ParseDataToTagsFromDictionary(List varList, List byteArrays) { //Define result list to return var name, var value and var data type List resultStringList = new List(); //Dictionary for index counting Dictionary byteLength = new Dictionary() { {BuiltInType.Boolean, 1}, {BuiltInType.Int16, 2}, {BuiltInType.Int32, 4}, {BuiltInType.Int64, 8}, {BuiltInType.UInt16, 2}, {BuiltInType.UInt32, 4}, {BuiltInType.UInt64, 8}, {BuiltInType.Float, 4}, {BuiltInType.Double, 8}, {BuiltInType.Byte, 1}, {BuiltInType.String, 4} }; //Array index int arrayIndex = 0; foreach (byte[] byteResult in byteArrays) { //Byte decoding index int index = 0; //Start decoding for opc data types foreach (string[] val in varList) { string[] dataReferenceStringArray = new string[4]; //Check if the type dictionary includes a struct/udt in a struct/udt and enter an empty row if (val[1] == "") { dataReferenceStringArray[0] = "[" + arrayIndex.ToString() + "]"; dataReferenceStringArray[1] = val[0]; dataReferenceStringArray[2] = ""; dataReferenceStringArray[3] = ""; //Add the data Reference string array to the result string list resultStringList.Add(dataReferenceStringArray); continue; } //Add array index dataReferenceStringArray[0] = "[" + arrayIndex.ToString() + "]"; //Copy tag name dataReferenceStringArray[1] = val[0]; //Get the target data type via the data type id NodeId datatypeId = new NodeId(val[1]); BuiltInType targetType = TypeInfo.GetBuiltInType(datatypeId); //Get the system type Type systemType = TypeInfo.GetSystemType(datatypeId, null); //Parse array dimensions string in int32 Int32 arrayDimensions = 0; if (val[2] != "") { arrayDimensions = Int32.Parse(val[2]); } //Deserialize the byte array depending on the target data type and the array dimension if (targetType == BuiltInType.String && !(arrayDimensions > 0)) { //Get the string length Int32 stringlength = BitConverter.ToInt32(byteResult, index); index += byteLength[targetType]; if (stringlength > 0) //Decode the bytes to its string value { dataReferenceStringArray[2] = Encoding.UTF8.GetString(byteResult, index, stringlength); index += stringlength; } else { dataReferenceStringArray[2] = ""; } } else if (targetType == BuiltInType.String && arrayDimensions > 0) { //Skip array information (UInt32) regarding its size index += byteLength[BuiltInType.UInt32]; //Check every element of the array for (int i = 0; i < arrayDimensions; i++) { //Get the string length Int32 stringlength = BitConverter.ToInt32(byteResult, index); index += byteLength[targetType]; if (stringlength > 0) //Decode the bytes to its string value { dataReferenceStringArray[2] = String.Concat(dataReferenceStringArray[2], Encoding.UTF8.GetString(byteResult, index, stringlength)); //Add a ; to the string value to seperate the value from the other array values dataReferenceStringArray[2] = String.Concat(dataReferenceStringArray[2], ";"); index += stringlength; } else { dataReferenceStringArray[2] = dataReferenceStringArray[2] + ";"; } } } else if (targetType == BuiltInType.Byte && !(arrayDimensions > 0)) { //Copy the byte value as string to the data reference array dataReferenceStringArray[2] = byteResult[index].ToString(); index += byteLength[targetType]; } else if (targetType == BuiltInType.Byte && arrayDimensions > 0) { //Skip array information (UInt32) regarding its size index += byteLength[BuiltInType.UInt32]; Int32[] tempArray = new Int32[arrayDimensions]; //Check every element of the array for (int i = 0; i < arrayDimensions; i++) { tempArray[i] = byteResult[index]; index += byteLength[targetType]; } //Add the values as one value seperated by ; to the data reference array dataReferenceStringArray[2] = String.Join(";", tempArray); dataReferenceStringArray[2] = String.Concat(dataReferenceStringArray[2], ";"); } else if (!(arrayDimensions > 0)) { //Get the converter method depending on the system type System.Reflection.MethodInfo methodInfo = typeof(BitConverter).GetMethod("To" + systemType.Name); //Call the method dataReferenceStringArray[2] = methodInfo.Invoke(null, new Object[] { byteResult, index }).ToString(); index += byteLength[targetType]; } else if (arrayDimensions > 0) { //Skip array information (UInt32) regarding its size index += byteLength[BuiltInType.UInt32]; //Get the converter method depending on the system type System.Reflection.MethodInfo methodInfo = typeof(BitConverter).GetMethod("To" + systemType.Name); dynamic[] tempArray = new dynamic[arrayDimensions]; //Check every element of the array for (int i = 0; i < arrayDimensions; i++) { tempArray[i] = methodInfo.Invoke(null, new Object[] { byteResult, index }).ToString(); index += byteLength[targetType]; } //Add the values as one value seperated by ; to the data reference array dataReferenceStringArray[2] = String.Join(";", tempArray); dataReferenceStringArray[2] = String.Concat(dataReferenceStringArray[1], ";"); } else { Exception e = new Exception("Data type is too complex to be parsed." + System.Environment.NewLine); throw e; } //Copy the target type as string to the data reference array dataReferenceStringArray[3] = targetType.ToString(); //Add the data Reference string array to the result string list resultStringList.Add(dataReferenceStringArray); } //Count the array index arrayIndex += 1; } return resultStringList; } /// /// Handles the certificate validation event. This event is triggered every time an /// untrusted certificate is received from the server. /// private void CertificateValidation(CertificateValidator sender, CertificateValidationEventArgs e) { bool certificateAccepted = true; // **** Implement a custom logic to decide if the certificate should be accepted or not // and set certificateAccepted flag accordingly. The certificate can be retrieved from // the e.Certificate field *** ServiceResult error = e.Error; while (error != null) { lgError($"{error.StatusCode} | {error.Code} | {error.LocalizedText}"); error = error.InnerResult; } if (certificateAccepted) { lgInfo($"Untrusted Certificate accepted. SubjectName = {e.Certificate.SubjectName}"); } e.AcceptAll = certificateAccepted; } /// /// Handle DataChange notifications from Server /// private void OnMonitoredItemNotification(MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e) { try { // Log MonitoredItem Notification event MonitoredItemNotification notification = e.NotificationValue as MonitoredItemNotification; // verifico se ricevo null, superata soglia genero eccezione if ($"{notification.Value}".Contains("null")) { currNullReceiv++; lgTrace($"NUll data read | Variable: {monitoredItem.StartNodeId.Identifier} | Value: {notification.Value}"); } else { currNullReceiv--; currNullReceiv = currNullReceiv > 0 ? currNullReceiv : 0; // sollevo evento notifica vaziazione MonitoredItem if (eh_MonItChange != null) { eh_MonItChange(this, new opcUaMonitItemChange(monitoredItem, notification)); } } lgTrace($"Notification Received | Variable: {monitoredItem.StartNodeId.Identifier} | Value: {notification.Value}"); // se ho ricevuto troppi nul --> disconnetto if (currNullReceiv > maxNullAllowed) { if (eh_nullExceed != null) { eh_nullExceed(this, currNullReceiv); } // reset currNullReceiv = 0; } } catch (Exception ex) { lgError($"OnMonitoredItemNotification error: {ex.Message}"); } } #endregion Private Methods } }