1863 lines
80 KiB
C#
1863 lines
80 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// Evento per incapsulare dati x refresh pagina
|
|
/// </summary>
|
|
public class opcUaMonitItemChange : EventArgs
|
|
{
|
|
#region Public Constructors
|
|
|
|
/// <summary>
|
|
/// salvataggio obj
|
|
/// </summary>
|
|
/// <param name="newObject"></param>
|
|
public opcUaMonitItemChange(MonitoredItem monitoredItem, MonitoredItemNotification notification)
|
|
{
|
|
_monitoredItem = monitoredItem;
|
|
_notification = notification;
|
|
}
|
|
|
|
#endregion Public Constructors
|
|
|
|
#region Public Properties
|
|
|
|
/// <summary>
|
|
/// Proprietà lettura del MonitoredItem
|
|
/// </summary>
|
|
public MonitoredItem CurrMonitoredItem
|
|
{
|
|
get { return _monitoredItem; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Proprietà lettura della notifica
|
|
/// </summary>
|
|
public MonitoredItemNotification CurrNotify
|
|
{
|
|
get { return _notification; }
|
|
}
|
|
|
|
#endregion Public Properties
|
|
|
|
#region Private Fields
|
|
|
|
/// <summary>
|
|
/// Monitored Item da notificare
|
|
/// </summary>
|
|
private readonly MonitoredItem _monitoredItem;
|
|
|
|
/// <summary>
|
|
/// Valore notifica
|
|
/// </summary>
|
|
private readonly MonitoredItemNotification _notification;
|
|
|
|
#endregion Private Fields
|
|
}
|
|
|
|
/// <summary>
|
|
/// OPC UA Client with examples of basic functionality.
|
|
/// </summary>
|
|
public class UAClient
|
|
{
|
|
#region Public Constructors
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the UAClient class.
|
|
/// </summary>
|
|
/// <param name="configuration"></param>
|
|
/// <param name="codIOB"></param>
|
|
/// <param name="user"></param>
|
|
/// <param name="pwd"></param>
|
|
/// <param name="verboseLog"></param>
|
|
/// <param name="validateResponse"></param>
|
|
/// <param name="maxNullRead">Numero massimo di risposte nulle in browsing prima di disconnettersi</param>
|
|
public UAClient(ApplicationConfiguration configuration, string codIOB, string user, string pwd, bool verboseLog, Action<IList, IList> 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
|
|
|
|
/// <summary>
|
|
/// Evento notifica variazione MonitoredItem
|
|
/// </summary>
|
|
public event EventHandler<opcUaMonitItemChange> eh_MonItChange;
|
|
|
|
/// <summary>
|
|
/// Evento notifica superamento numero letture null
|
|
/// </summary>
|
|
public event EventHandler<int> eh_nullExceed;
|
|
|
|
#endregion Public Events
|
|
|
|
#region Public Properties
|
|
|
|
/// <summary>
|
|
/// The user identity to use when creating the session.
|
|
/// </summary>
|
|
public IUserIdentity CurrUserIdentity { get; set; } = new UserIdentity();
|
|
|
|
/// <summary>
|
|
/// Gets or sets the server URL.
|
|
/// </summary>
|
|
public string ServerUrl { get; set; } = "opc.tcp://localhost:4840";
|
|
|
|
/// <summary>
|
|
/// Gets the client session.
|
|
/// </summary>
|
|
public Session Session => m_session;
|
|
|
|
#endregion Public Properties
|
|
|
|
#region Public Methods
|
|
|
|
/// <summary>
|
|
/// Browse Server nodes
|
|
/// </summary>
|
|
public bool Browse(ushort startNodeNS, uint startNodeVal, List<string> vetoBrowse, ref Dictionary<string, string> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Browse Server nodes
|
|
/// </summary>
|
|
public bool Browse(string browsePath, List<string> vetoBrowse, ref Dictionary<string, string> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call UA method
|
|
/// </summary>
|
|
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<object> 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}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a session with the UA server
|
|
/// </summary>
|
|
public async Task<bool> 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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disconnects the session.
|
|
/// </summary>
|
|
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}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a node by node Id
|
|
/// </summary>
|
|
/// <param name="nodeIdString">The node Id as string</param>
|
|
/// <returns>The read node</returns>
|
|
/// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
|
|
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;
|
|
}
|
|
|
|
/// <summary>Reads a node by node Id</summary>
|
|
/// <param name="nodeIdString">The node Id as string</param>
|
|
/// <returns>A dictionary containing the attribute values of the node using the attribute uint identifier as key;
|
|
/// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
|
|
public Dictionary<uint, DataValue> ReadNodeAttributes(String nodeIdString)
|
|
{
|
|
//Create a nodeId using the identifier string
|
|
NodeId nodeId = new NodeId(nodeIdString);
|
|
return ReadNodeAttributes(nodeId);
|
|
}
|
|
|
|
/// <summary>Reads a node by node Id</summary>
|
|
/// <param name="nodeId">The node Id</param>
|
|
/// <returns>A dictionary containing the attribute values of the node using the attribute uint identifier as key;
|
|
/// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
|
|
public Dictionary<uint, DataValue> ReadNodeAttributes(NodeId nodeId)
|
|
{
|
|
Dictionary<int, uint> attributeDictionary = new Dictionary<int, uint>()
|
|
{
|
|
{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<uint, DataValue> attributeValues = new Dictionary<uint, DataValue>();
|
|
//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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read a SINGLE of nodes value (RAW) from Server
|
|
/// </summary>
|
|
/// <param name="reqNodeId"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read a list of nodes from Server
|
|
/// </summary>
|
|
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}.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read a SINGLE of nodes value from Server
|
|
/// </summary>
|
|
/// <param name="reqNodeId"></param>
|
|
/// <returns>Value as String</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>Reads a struct or UDT by node Id</summary>
|
|
/// <param name="nodeIdString">The node Id as strings</param>
|
|
/// <returns>The read struct/UDT elements as a list of string[3]; string[0] = tag name, string[1] = value, string[2] = opc data type</returns>
|
|
/// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
|
|
public List<string[]> ReadStructUdt(String nodeIdString)
|
|
{
|
|
//Define result list to return var name and var value
|
|
List<string[]> resultStringList = new List<string[]>();
|
|
|
|
//Read the node attributes
|
|
Dictionary<uint, DataValue> 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<byte[]> byteArrays = new List<byte[]>();
|
|
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<string[]> structureDictionary = new List<string[]>();
|
|
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[]> string[0]=array index; string[1]=tag name; string[2]=tag value; string[3]=tag data type
|
|
return resultStringList;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create Subscription and MonitoredItems for DataChanges
|
|
/// </summary>
|
|
public List<MonitoredItem> SubscribeToDataChanges(Dictionary<string, string> DataList)
|
|
{
|
|
List<MonitoredItem> monItList = new List<MonitoredItem>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a list of nodes to the Server
|
|
/// </summary>
|
|
/// <param name="node2Write"></param>
|
|
public void WriteNodes(List<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.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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Metodo scrittura alternativa in formato UDT dei dati...
|
|
/// </summary>
|
|
/// <param name="node2Write"></param>
|
|
public void WriteNodesUdt(string nodeIdString, Dictionary<string, string> 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<string[]>();
|
|
|
|
listUdtRows = new List<string[]>();
|
|
// 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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes values to node Ids
|
|
/// </summary>
|
|
/// <param name="valuesByNodeId">Mulitple NodeIds with there values (multiple possible if not a scalar).</param>
|
|
/// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
|
|
public void WriteValues(Dictionary<NodeId, IEnumerable<string>> valuesByNodeId)
|
|
{
|
|
//Create a collection of values to write
|
|
WriteValueCollection valuesToWrite = new WriteValueCollection();
|
|
|
|
foreach (var keyValuePair in valuesByNodeId)
|
|
{
|
|
NodeId nodeId = keyValuePair.Key;
|
|
IEnumerable<string> 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<string> tempValues = new List<string>();
|
|
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
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a list of nodes to the Server
|
|
/// </summary>
|
|
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}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Writes data to a struct or UDT by node Id</summary>
|
|
/// <param name="nodeIdString">The node Id as strings</param>
|
|
/// <param name="dataToWrite">The data to write as string[3]; string[0] = tag name, string[1] = value, string[2] = opc data type</param>
|
|
/// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
|
|
public void WriteStructUdt(String nodeIdString, List<string[]> 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<uint, DataValue> 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<byte[]> byteArrays = new List<byte[]>();
|
|
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<string[]> structureDictionary = new List<string[]>();
|
|
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<byte[]> 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a list of nodes to the Server
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Effettua logging DEBUG corretto impostanto anche la variabile IOB prima di scrivere...
|
|
/// </summary>
|
|
/// <param name="message"></param>
|
|
protected void lgDebug(string message)
|
|
{
|
|
lg.Factory.Configuration.Variables["codIOB"] = currIob;
|
|
lg.Debug(message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua logging ERROR corretto impostanto anche la variabile IOB prima di scrivere...
|
|
/// </summary>
|
|
/// <param name="message"></param>
|
|
protected void lgError(string message)
|
|
{
|
|
lg.Factory.Configuration.Variables["codIOB"] = currIob;
|
|
lg.Error(message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua logging INFO corretto impostanto anche la variabile IOB prima di scrivere...
|
|
/// </summary>
|
|
/// <param name="message"></param>
|
|
protected void lgInfo(string message)
|
|
{
|
|
lg.Factory.Configuration.Variables["codIOB"] = currIob;
|
|
lg.Info(message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua logging TRACE corretto impostanto anche la variabile IOB prima di scrivere...
|
|
/// </summary>
|
|
/// <param name="message"></param>
|
|
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<IList, IList> m_validateResponse;
|
|
|
|
private ApplicationConfiguration m_configuration;
|
|
|
|
private Session m_session;
|
|
|
|
#endregion Private Fields
|
|
|
|
#region Private Methods
|
|
|
|
/// <summary>
|
|
/// Browses for the node id of the parent data type of type base data type
|
|
/// </summary>
|
|
/// <param name="nodeId">The data to write as string[4]; string[0] = index, string[1] = tag name, string[2] = value, string[3] = opc data type</param>
|
|
/// <returns>The node id of the parent data type</returns>
|
|
/// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Browses for the desired type dictonary to parse for containing data types
|
|
/// </summary>
|
|
/// <param name="dataTypeNode">The data type node</param>
|
|
/// <param name="theSessionToBrowseIn">The current session to browse in</param>
|
|
/// <param name="structureDictionary">The list of string arrays containing the structure information tag name, data type id and array dimension</param>
|
|
/// <returns>The dictionary as List of string arrays</returns>
|
|
/// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
|
|
private static List<string[]> GetTypeDictionary(DataTypeNode dataTypeNode, Session theSessionToBrowseIn, List<string[]> structureDictionary)
|
|
{
|
|
//Create a temp list of string containing the structure dictionary
|
|
List<string[]> tempStructureDictionary = new List<string[]>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses data to write to a list of byte arrays
|
|
/// </summary>
|
|
/// <param name="dataToWrite">The data to write as string[4]; string[0] = index, string[1] = tag name, string[2] = value, string[3] = opc data type</param>
|
|
/// <param name="structureDictionary">The structure of the udt/struct</param>
|
|
/// <returns>The list of the parsed byte arrays</returns>
|
|
/// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
|
|
private static List<byte[]> ParseDataToByteArray(List<string[]> dataToWrite, List<string[]> structureDictionary)
|
|
{
|
|
//Define result byte list to return a byte array
|
|
List<byte[]> resultByteList = new List<byte[]>();
|
|
|
|
//Dictionary to get the right byte length depending on the data type
|
|
Dictionary<BuiltInType, int> byteLength = new Dictionary<BuiltInType, int>()
|
|
{
|
|
{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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses a byte array to objects containing tag names and tag data types
|
|
/// </summary>
|
|
/// <param name="varList">List of object containing tag names and tag data types</param>
|
|
/// <param name="byteResult">A byte array to parse</param>
|
|
/// <returns>A list of string[4]; string[0] = tag name, string[1] = tag name, string[2] = value, string[3] = opc data type</returns>
|
|
/// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
|
|
private static List<string[]> ParseDataToTagsFromDictionary(List<string[]> varList, List<byte[]> byteArrays)
|
|
{
|
|
//Define result list to return var name, var value and var data type
|
|
List<string[]> resultStringList = new List<string[]>();
|
|
|
|
//Dictionary for index counting
|
|
Dictionary<BuiltInType, int> byteLength = new Dictionary<BuiltInType, int>()
|
|
{
|
|
{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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles the certificate validation event. This event is triggered every time an
|
|
/// untrusted certificate is received from the server.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle DataChange notifications from Server
|
|
/// </summary>
|
|
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
|
|
}
|
|
} |