diff --git a/MP.MONO.ADAPTER.OLD.sln b/MP.MONO.ADAPTER.OLD.sln
new file mode 100644
index 0000000..9118d22
--- /dev/null
+++ b/MP.MONO.ADAPTER.OLD.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.2.32519.379
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MP.MONO.ADAPTER.OPC", "MP.MONO.ADAPTER.OPC\MP.MONO.ADAPTER.OPC.csproj", "{458FD824-7804-4CF9-8C43-463C1F5659DA}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MP.MONO.ADAPTER.OLD", "MP.MONO.ADAPTER.OLD\MP.MONO.ADAPTER.OLD.csproj", "{84F0ABFE-ADD6-426D-BD9B-E4E9086EE169}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {458FD824-7804-4CF9-8C43-463C1F5659DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {458FD824-7804-4CF9-8C43-463C1F5659DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {458FD824-7804-4CF9-8C43-463C1F5659DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {458FD824-7804-4CF9-8C43-463C1F5659DA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {84F0ABFE-ADD6-426D-BD9B-E4E9086EE169}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {84F0ABFE-ADD6-426D-BD9B-E4E9086EE169}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {84F0ABFE-ADD6-426D-BD9B-E4E9086EE169}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {84F0ABFE-ADD6-426D-BD9B-E4E9086EE169}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {3811E8B4-1DD3-4841-9840-1B1A08782CD3}
+ EndGlobalSection
+EndGlobal
diff --git a/MP.MONO.ADAPTER.OLD/MP.MONO.ADAPTER.OLD.csproj b/MP.MONO.ADAPTER.OLD/MP.MONO.ADAPTER.OLD.csproj
index 3531e0d..148f2b8 100644
--- a/MP.MONO.ADAPTER.OLD/MP.MONO.ADAPTER.OLD.csproj
+++ b/MP.MONO.ADAPTER.OLD/MP.MONO.ADAPTER.OLD.csproj
@@ -5,7 +5,7 @@
net6.0
enable
enable
- 1.12206.1410
+ 1.12206.1416
diff --git a/MP.MONO.ADAPTER.OLD/Resources/ChangeLog.html b/MP.MONO.ADAPTER.OLD/Resources/ChangeLog.html
index ecdcea3..b2b32cf 100644
--- a/MP.MONO.ADAPTER.OLD/Resources/ChangeLog.html
+++ b/MP.MONO.ADAPTER.OLD/Resources/ChangeLog.html
@@ -1,6 +1,6 @@
MAPO-MONO
- Version: 1.12206.1410
+ Version: 1.12206.1416
Release Note:
-
diff --git a/MP.MONO.ADAPTER.OLD/Resources/VersNum.txt b/MP.MONO.ADAPTER.OLD/Resources/VersNum.txt
index 3720595..d360aeb 100644
--- a/MP.MONO.ADAPTER.OLD/Resources/VersNum.txt
+++ b/MP.MONO.ADAPTER.OLD/Resources/VersNum.txt
@@ -1 +1 @@
-1.12206.1410
+1.12206.1416
diff --git a/MP.MONO.ADAPTER.OLD/Resources/manifest.xml b/MP.MONO.ADAPTER.OLD/Resources/manifest.xml
index 23bb8cd..81a6875 100644
--- a/MP.MONO.ADAPTER.OLD/Resources/manifest.xml
+++ b/MP.MONO.ADAPTER.OLD/Resources/manifest.xml
@@ -1,6 +1,6 @@
-
- 1.12206.1410
+ 1.12206.1416
http://nexus.steamware.net/repository/SWS/MP.MONO.ADAPTER/stable/LAST/MP.Mon.zip
http://nexus.steamware.net/repository/SWS/MP.MONO.ADAPTER/stable/LAST/ChangeLog.html
false
diff --git a/MP.MONO.ADAPTER.OLD/UAClient.cs b/MP.MONO.ADAPTER.OLD/UAClient.cs
index cd8fb8e..b7c76b5 100644
--- a/MP.MONO.ADAPTER.OLD/UAClient.cs
+++ b/MP.MONO.ADAPTER.OLD/UAClient.cs
@@ -1,12 +1,7 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Collections;
+using NLog;
using Opc.Ua;
using Opc.Ua.Client;
-using NLog;
+using System.Collections;
namespace MP.MONO.ADAPTER
{
@@ -15,20 +10,6 @@ namespace MP.MONO.ADAPTER
///
public class opcUaMonitItemChange : EventArgs
{
- #region Private Fields
-
- ///
- /// Monitored Item da notificare
- ///
- private readonly MonitoredItem _monitoredItem;
-
- ///
- /// Valore notifica
- ///
- private readonly MonitoredItemNotification _notification;
-
- #endregion Private Fields
-
#region Public Constructors
///
@@ -62,34 +43,27 @@ namespace MP.MONO.ADAPTER
}
#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 Private Fields
-
- private readonly string currIob;
- private readonly Action m_validateResponse;
- private ApplicationConfiguration m_configuration;
- private Session m_session;
-
- #endregion Private Fields
-
- #region Protected Fields
-
- protected static Logger lg;
- protected static bool isLogVerbose = false;
-
-
- ///
- /// The user identity to use when creating the session.
- ///
- public IUserIdentity CurrUserIdentity { get; set; } = new UserIdentity();
-
- #endregion Protected Fields
-
#region Public Constructors
///
@@ -126,6 +100,11 @@ namespace MP.MONO.ADAPTER
#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.
///
@@ -138,105 +117,6 @@ namespace MP.MONO.ADAPTER
#endregion Public Properties
- #region Private Methods
-
- ///
- /// 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;
-
- // sollevo evento notifica vaziazione MonitoredItem
- if (eh_MonItChange != null)
- {
- eh_MonItChange(this, new opcUaMonitItemChange(monitoredItem, notification));
- }
- lgTrace($"Notification Received | Variable: {monitoredItem.DisplayName} | Value: {notification.Value}");
- }
- catch (Exception ex)
- {
- lgError($"OnMonitoredItemNotification error: {ex.Message}");
- }
- }
-
- #endregion Private Methods
-
- #region Protected Methods
-
- ///
- /// 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 DEBUG corretto impostanto anche la variabile IOB prima di scrivere...
- ///
- ///
- protected void lgDebug(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 Public Methods
///
@@ -384,14 +264,11 @@ namespace MP.MONO.ADAPTER
try
{
- // Define the UA Method to call
- // Parent node - Objects\CTT\Methods
- // Method node - Objects\CTT\Methods\Add
+ // 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
+ // Define the method parameters Input argument requires a Float and an UInt32 value
object[] inputArguments = new object[] { (float)10.5, (uint)10 };
IList
+ /// Write a list of nodes to the Server
public void WriteNodes(List node2Write)
{
if (m_session == null || m_session.Connected == false)
@@ -1001,5 +873,119 @@ namespace MP.MONO.ADAPTER
}
#endregion Public Methods
+
+ #region Protected Fields
+
+ protected static bool isLogVerbose = false;
+ protected static Logger lg;
+
+ #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.Info(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
+
+ ///
+ /// 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;
+
+ // sollevo evento notifica vaziazione MonitoredItem
+ if (eh_MonItChange != null)
+ {
+ eh_MonItChange(this, new opcUaMonitItemChange(monitoredItem, notification));
+ }
+ lgTrace($"Notification Received | Variable: {monitoredItem.DisplayName} | Value: {notification.Value}");
+ }
+ catch (Exception ex)
+ {
+ lgError($"OnMonitoredItemNotification error: {ex.Message}");
+ }
+ }
+
+ #endregion Private Methods
}
-}
+}
\ No newline at end of file
diff --git a/MP.MONO.ADAPTER.OPC.sln b/MP.MONO.ADAPTER.OPC.sln
index f328032..2ca434f 100644
--- a/MP.MONO.ADAPTER.OPC.sln
+++ b/MP.MONO.ADAPTER.OPC.sln
@@ -1,9 +1,9 @@

Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.32510.428
+# Visual Studio Version 17
+VisualStudioVersion = 17.2.32519.379
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MP.MONO.ADAPTER.OPC", "MP.MONO.ADAPTER.OPC\MP.MONO.ADAPTER.OPC.csproj", "{458FD824-7804-4CF9-8C43-463C1F5659DA}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MP.MONO.ADAPTER.OPC", "MP.MONO.ADAPTER.OPC\MP.MONO.ADAPTER.OPC.csproj", "{458FD824-7804-4CF9-8C43-463C1F5659DA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
diff --git a/MP.MONO.ADAPTER.OPC/Quickstarts.ReferenceClient.Config.xml b/MP.MONO.ADAPTER.OPC/CONF/MpMonoAdapterClient.Config.xml
similarity index 100%
rename from MP.MONO.ADAPTER.OPC/Quickstarts.ReferenceClient.Config.xml
rename to MP.MONO.ADAPTER.OPC/CONF/MpMonoAdapterClient.Config.xml
diff --git a/MP.MONO.ADAPTER.OPC/CONF/Multiax.json b/MP.MONO.ADAPTER.OPC/CONF/Multiax.json
new file mode 100644
index 0000000..f9f4ad9
--- /dev/null
+++ b/MP.MONO.ADAPTER.OPC/CONF/Multiax.json
@@ -0,0 +1,204 @@
+{
+ "BrowseFullVal": "ns=1;s=OSAI-PLC",
+ "BrowseNSIndex": 1,
+ "BrowseValue": 0,
+ "keyPartCount": "1:507_2_WORKPIECE QTY",
+ "keyPartReq": "",
+ "keyPartId": "",
+ "keyProgName": "1:502_1_RECIPE NAME",
+ "keyRunMode": "",
+ "pingAsPowerOn": true,
+ //"Identity": {
+ // "UserName": "CUSTOMER",
+ // "Passwd": "BLM"
+ //},
+ "condWork": [
+ {
+ "keyName": "1:1_1_MACHINE STATUS",
+ "targetValue": "ON"
+ }
+ ],
+ "condWorkOpc": {
+ "checkMode": "AND",
+ "checkList": [
+ {
+ "keyName": "MachineStatus(0)",
+ "targetValue": "4"
+ }
+ ]
+ },
+ "condPowerOn": {
+ "checkMode": "OR",
+ "negateValue": true,
+ "checkList": [
+ {
+ "keyName": "1:1_1_MACHINE STATUS",
+ "targetValue": "ON"
+ },
+ {
+ "keyName": "1:500_1_STATUS",
+ "targetValue": "CYCLE"
+ },
+ {
+ "keyName": "1:501_1_MODE",
+ "targetValue": "AUTO"
+ }
+ ]
+ },
+ "condReady": {
+ "checkMode": "OR",
+ "checkList": [
+ //{
+ // "keyName": "MachineStatus(0)",
+ // "targetValue": "3"
+ //},
+ //{
+ // "keyName": "MachineStatus(0)",
+ // "targetValue": "5"
+ //}
+ ]
+ },
+ "condManual": {
+ "checkMode": "AND",
+ "checkList": [
+ //{
+ // "keyName": "MachineStatus(0)",
+ // "targetValue": "8"
+ //}
+ ]
+ },
+ "condEStop": {
+ "checkMode": "AND",
+ "checkList": [
+ //{
+ // "keyName": "EmergencyState",
+ // "targetValue": "0"
+ //}
+ ]
+ },
+ "condError": {
+ "checkMode": "AND",
+ "checkList": [
+ //{
+ // "keyName": "MachineStatus(0)",
+ // "targetValue": "6"
+ //}
+ ]
+ },
+ "condCountEnabled": {
+ "checkMode": "AND",
+ "checkList": []
+ },
+ "condWarmUpCoolDown": {
+ "checkMode": "OR",
+ "negateValue": true,
+ "checkList": []
+ },
+ "condWarning": {
+ "checkMode": "AND",
+ "checkList": [
+ //{
+ // "keyName": "MachineStatus(0)",
+ // "targetValue": "9"
+ //}
+ ]
+ },
+ "condSetup": {
+ "checkMode": "AND",
+ "checkList": [
+ //{
+ // "keyName": "MachineStatus(0)",
+ // "targetValue": "7"
+ //}
+ ]
+ },
+ "fluxLogVeto": [],
+ "itemTranslation": {
+ "avail": "Machine Available",
+ "rstat": "Execution Mode",
+ "mode": "Controller Mode",
+ "PartName(0)": "Program Name",
+ "PartDone(0)": "Pezzi Prodotti",
+ "PartToDo(0)": "Qta Richiesta",
+ "MachineStatus(0)": "Stato Macchina principale",
+ "MachineStatus(1)": "Stato Macchina secondario",
+ "EmergencyState": "Emergenza",
+ "fdovrd": "PATH FEED OVERRIDE",
+ "rovrd": "PATH RAPID OVERRIDE"
+ },
+ "subscribedItems": [
+ "ns=1;s=1_1_MACHINE STATUS",
+ "ns=1;s=1_2_MACHINE COUNTER OFF",
+ "ns=1;s=2_1_AXES STATUS",
+ "ns=1;s=2_2_AXES COUNTER OFF",
+ "ns=1;s=3_1_ALARM NC",
+ "ns=1;s=3_2_ALARM PLC 1",
+ "ns=1;s=3_3_ALARM PLC 2",
+ "ns=1;s=3_4_ALARM PLC 3",
+ "ns=1;s=3_5_ALARM PLC 4",
+ "ns=1;s=3_6_ALARM PLC 5",
+ "ns=1;s=3_7_ALARM PLC 6",
+ "ns=1;s=3_8_ALARM PLC 7",
+ "ns=1;s=3_9_ALARM PLC 8",
+ "ns=1;s=3_10_ALARM PLC 9",
+ "ns=1;s=3_11_ALARM PLC 10",
+ "ns=1;s=4_1_TOOL 1 CURRENT LIFE PC",
+ "ns=1;s=4_2_TOOL 2 CURRENT LIFE PC",
+ "ns=1;s=4_3_TOOL 3 CURRENT LIFE PC",
+ "ns=1;s=4_4_TOOL 4 CURRENT LIFE PC",
+ "ns=1;s=4_5_TOOL 11 CURRENT LIFE PC",
+ "ns=1;s=4_6_TOOL 12 CURRENT LIFE PC",
+ "ns=1;s=4_7_TOOL 13 CURRENT LIFE PC",
+ "ns=1;s=4_8_TOOL 14 CURRENT LIFE PC",
+ "ns=1;s=4_9_TOOL 21 CURRENT LIFE PC",
+ "ns=1;s=4_10_TOOL 22 CURRENT LIFE PC",
+ "ns=1;s=4_11_TOOL 23 CURRENT LIFE PC",
+ "ns=1;s=4_12_TOOL 24 CURRENT LIFE PC",
+ "ns=1;s=4_13_TOOL 31 CURRENT LIFE PC",
+ "ns=1;s=4_14_TOOL 32 CURRENT LIFE PC",
+ "ns=1;s=4_15_TOOL 33 CURRENT LIFE PC",
+ "ns=1;s=4_16_TOOL 34 CURRENT LIFE PC",
+ "ns=1;s=500_1_STATUS",
+ "ns=1;s=500_2_COUNTER CYCLE",
+ "ns=1;s=500_3_COUNTER HOLDA",
+ "ns=1;s=500_4_COUNTER ERROR",
+ "ns=1;s=500_5_COUNTER PAUSE",
+ "ns=1;s=501_1_MODE",
+ "ns=1;s=502_1_RECIPE NAME",
+ "ns=1;s=502_2_PROGRESS BAR",
+ "ns=1;s=25_AXIS 1 LOAD PC",
+ "ns=1;s=26_AXIS 2 LOAD PC",
+ "ns=1;s=27_AXIS 3 LOAD PC",
+ "ns=1;s=28_AXIS 4 LOAD PC",
+ "ns=1;s=29_AXIS 5 LOAD PC",
+ "ns=1;s=30_AXIS 6 LOAD PC",
+ "ns=1;s=31_AXIS 7 LOAD PC",
+ "ns=1;s=32_AXIS 8 LOAD PC",
+ "ns=1;s=33_AXIS 9 LOAD PC",
+ "ns=1;s=34_AXIS 10 LOAD PC",
+ "ns=1;s=35_AXIS 11 LOAD PC",
+ "ns=1;s=36_AXIS 12 LOAD PC",
+ "ns=1;s=37_AXIS 13 LOAD PC",
+ "ns=1;s=38_AXIS 14 LOAD PC",
+ "ns=1;s=39_AXIS 15 LOAD PC",
+ "ns=1;s=45_P2 AXIS V SW OVT UPPER",
+ "ns=1;s=46_P2 AXIS V SW OVT LOWER",
+ "ns=1;s=47_P2 AXIS V ACTUAL POSITION",
+ "ns=1;s=48_P3 AXIS W SW OVT UPPER",
+ "ns=1;s=49_P3 AXIS W SW OVT LOWER",
+ "ns=1;s=50_P3 AXIS W ACTUAL POSITION",
+ "ns=1;s=504_PROC 1 P.P. ORDER",
+ "ns=1;s=505_PROC 1 P.P. PROCESSING",
+ "ns=1;s=506_PROC 1 P.P. PROCESSING PHASE",
+ "ns=1;s=510_PROC 1 FEED OVERRIDE",
+ "ns=1;s=511_PROC 1 FEED PROGRAMMED",
+ "ns=1;s=512_PROC 1 FEED ACTUAL",
+ "ns=1;s=513_PROC 1 SPEED OVERRIDE",
+ "ns=1;s=514_PROC 1 SPEED PROGRAMMED",
+ "ns=1;s=515_PROC 1 SPEED ACTUAL",
+ "ns=1;s=516_PROC 1 SPINDLE 1 LOAD PC",
+ "ns=1;s=517_PROC 1 SPINDLE 2 LOAD PC",
+ "ns=1;s=518_PROC 1 SPINDLE 3 LOAD PC",
+ "ns=1;s=519_PROC 1 SPINDLE 4 LOAD PC"
+ ]
+}
\ No newline at end of file
diff --git a/MP.MONO.ADAPTER.OPC/IobGeneric.cs b/MP.MONO.ADAPTER.OPC/IobGeneric.cs
new file mode 100644
index 0000000..a3b85d9
--- /dev/null
+++ b/MP.MONO.ADAPTER.OPC/IobGeneric.cs
@@ -0,0 +1,722 @@
+using Microsoft.Extensions.Configuration;
+using MP.MONO.Core;
+using MP.MONO.Core.CONF;
+using Newtonsoft.Json;
+using NLog;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.NetworkInformation;
+using static MP.MONO.Core.CONF.OpcUaParamConf;
+using static MP.MONO.Core.Enums;
+
+namespace MP.MONO.ADAPTER.OPC
+{
+ public class IobGeneric
+ {
+ #region Public Fields
+
+ ///
+ /// valore booleano di check se sia stato AVVIATO l'adapter (Running)
+ ///
+ internal bool adpRunning = false;
+
+ ///
+ /// Data/ora ultimo avvio adapter
+ ///
+ internal DateTime dtAvvioAdp = DateTime.Now;
+
+ ///
+ /// Data/ora ultimo spegnimento adapter
+ ///
+ internal DateTime dtStopAdp = DateTime.Now;
+
+ ///
+ /// dataOra ultimo log periodico...
+ ///
+ internal DateTime lastPeriodicLog;
+
+ ///
+ /// dataOra ultimo PING inviato verso il PLC...
+ ///
+ internal DateTime lastPING = DateTime.Now.AddHours(-1);
+ ///
+ /// indica se serva refresh parametri e quindi PLC...
+ ///
+ internal bool needRefresh = true;
+
+ ///
+ /// Struttura memoria PLC x lettura/scrittura da JSON file
+ ///
+ public plcMemMap memMap;
+
+ #endregion Public Fields
+
+ #region Public Constructors
+
+ ///
+ /// Avvia generico IOB
+ ///
+ ///
+ ///
+ public IobGeneric(string confPath, IConfigurationRoot? config)
+ {
+ lg = LogManager.GetCurrentClassLogger();
+ connectionOk = false;
+ configPath = confPath;
+ confMan = config;
+ if (confMan != null)
+ {
+ var selection = confMan.GetSection("Endpoint");
+ if (selection.Exists())
+ {
+ // recupero dati endpoint
+ cIobConf = selection.Get();
+ }
+ }
+ }
+
+ #endregion Public Constructors
+
+ #region Public Properties
+
+ ///
+ /// Salva verifica stato connessione OK
+ ///
+ ///
+ internal virtual bool connectionOk
+ {
+ get
+ {
+ return _connOk;
+ }
+ set
+ {
+ _connOk = value;
+ }
+ }
+
+ ///
+ /// Fornisce il valore di flusso e valore in formato valido x messa in coda nel formato
+ /// dtEve#flusso#valore#cont Flusso datiValore da salvare
+ ///
+ public string qEncodeFLog(string flusso, string valore)
+ {
+ string answ = "";
+ // fixme todo !!! fare
+ // tenere? cambiare? modificare? si dovrebbe cambiare con invio su channel REDIS dei dati ricevuti, eventualmente divisi x canale secondo setup
+#if false
+ try
+ {
+ answ = $"{DateTime.Now:yyyyMMddHHmmssfff}#{flusso}#{valore}#{counterULog}";
+ }
+ catch
+ { }
+#endif
+ return answ;
+ }
+ ///
+ /// Accumula in coda i valori Flux Log e logga...
+ ///
+ /// VALORE RAW (x display)
+ /// VALORE già processato con qEncodeFLog(...)
+ public void accodaFLog(string val, string encodedVal)
+ {
+ // fixme todo !!! fare
+ // tenere? cambiare? modificare? si dovrebbe cambiare con invio su channel REDIS dei dati ricevuti, eventualmente divisi x canale secondo setup
+#if false
+ // mostro dati variati letti...
+ displayOtherData(val);
+ // --> accodo (valore già formattato)!
+ QueueFLog.Enqueue(encodedVal);
+ // se abilitato controllo coda FLog (superiore a 0...)
+ if (maxQueueFLog > 0)
+ {
+ // se ho una coda superiore a max ammesso
+ if (QueueFLog.Count > maxQueueFLog)
+ {
+ // elimino valori iniziali fino a tornare al max ammesso...
+ while (QueueFLog.Count > maxQueueFLog)
+ {
+ string currVal = "";
+ QueueFLog.TryDequeue(out currVal);
+ lgInfo($"Eliminazione da coda FLog per superamento maxLengh: {currVal}");
+ }
+ }
+ }
+ // loggo!
+ lgTrace(string.Format("[QUEUE-FLOG] {0}", encodedVal));
+ counterFLog++;
+ if (counterFLog > 9999)
+ {
+ counterFLog = 0;
+ }
+#endif
+ }
+
+ ///
+ /// indica se ping disabilitato da optPar
+ ///
+ public bool pingDisabled
+ {
+ get => getCRB("NO_PING");
+ }
+
+ ///
+ /// Verifica SE si debba fare log verboso (verboso + ogni tot letture IN)
+ ///
+ public bool verboseLog
+ {
+ get
+ {
+ bool answ = false;
+ int logEvery = getCRI("logEvery");
+ if (logEvery < 1)
+ {
+ logEvery = 10;
+ }
+
+ answ = getCRB("verbose") && (nReadIN % logEvery == 0);
+ return answ;
+ }
+ }
+
+ #endregion Public Properties
+
+ #region Public Methods
+
+ ///
+ /// Esecuzione dei task richiesti e pulizia coda richieste eseguite
+ ///
+ ///
+ public virtual Dictionary executeTasks(Dictionary task2exe)
+ {
+ // Verificare il protocollo: dovrebbe togliere SOLO i task eseguiti...
+ Dictionary taskDone = new Dictionary();
+ if (task2exe != null)
+ {
+ // controllo se memMap != null...
+ if (memMap != null)
+ {
+ bool taskOk = false;
+ string taskVal = "";
+ // cerco task specifici: se ho startSetup --> imposto bit DBB701.DBB0.4
+ foreach (var item in task2exe)
+ {
+ taskOk = false;
+ taskVal = "";
+ // converto richiesta in enum...
+ taskType tName = taskType.nihil;
+ Enum.TryParse(item.Key, out tName);
+ // controllo sulla KEY...
+ switch (tName)
+ {
+ case taskType.setArt:
+ case taskType.setComm:
+ case taskType.setProg:
+ case taskType.setPzComm:
+ // recupero dati da memMap...
+ if (memMap != null && memMap.mMapWrite != null)
+ {
+ if (memMap.mMapWrite.ContainsKey(item.Key))
+ {
+ dataConf currMem = memMap.mMapWrite[item.Key];
+ string addr = currMem.memAddr;
+ taskVal = $"SET task: {item.Key} --> {item.Value} | mem: {currMem.memAddr} - {currMem.size} byte";
+ // salvo il nuovo valore nella memoria... così prox invio lo trasmetterÃ
+ memMap.mMapWrite[item.Key].value = item.Value;
+ }
+ else
+ {
+ taskVal = $"NO DATA MEM, SET task: {item.Key} --> {item.Value}";
+ }
+ }
+ else
+ {
+ taskVal = $"NO MemMap found, SET task: {item.Key} --> {item.Value}";
+ }
+ // salvo in currProd..
+ saveProdData(new KeyValuePair(item.Key, item.Value));
+
+ break;
+
+ case taskType.forceResetPzCount:
+ // reset contapezzi inizio setup
+ taskOk = resetcontapezziPLC();
+ taskVal = taskOk ? "RESET PZ COUNT OK" : "PZ RESET DISABLED | NO EXEC";
+ lgInfo($"Chiamata forceResetPzCount: taskOk: {taskOk} | taskVal: {taskVal}");
+ break;
+
+ case taskType.startSetup:
+ // reset contapezzi inizio setup
+ taskOk = resetcontapezziPLC();
+ taskVal = taskOk ? "RESET: SETUP START" : "PZ RESET DISABLED | NO EXEC";
+ lgInfo($"Chiamata startSetup: taskOk: {taskOk} | taskVal: {taskVal}");
+ break;
+
+ case taskType.stopSetup:
+ // reset contapezzi fine setup SE ESPLICITAMENTE IMPOSTATO
+ if (getCRB("ENABLE_PZ_RESET_stopSetup"))
+ {
+ taskOk = resetcontapezziPLC();
+ }
+ taskVal = taskOk ? "RESET: SETUP END" : "PZ RESET DISABLED | NO EXEC";
+ lgInfo($"Chiamata stopSetup: taskOk: {taskOk} | taskVal: {taskVal}");
+ break;
+
+ case taskType.setParameter:
+ // richiedo da URL i parametri WRITE da popolare
+ lgInfo("Chiamata setParameter --> processMemWriteRequests");
+ taskVal = processMemWriteRequests();
+ // se restituiscce "" faccio altra prova...
+ if (string.IsNullOrEmpty(taskVal))
+ {
+ // i parametri me li aspetto come stringa composta paramName|paramvalue
+ if (item.Value.Contains("|"))
+ {
+ string[] paramsJob = item.Value.Split('|');
+ taskVal = $"REQUEST SET PARAMETERS: {paramsJob[0]} --> {paramsJob[1]}";
+ }
+ else
+ {
+ taskVal = $"WRONG REQUEST FOR SET PARAMETERS: {item.Value} doesnt contain pipe for splitting key/value";
+ }
+ }
+ break;
+
+ default:
+ taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | SKIPPED | NO EXEC";
+ lgInfo($"Chiamata senza processing: taskOk: {taskOk} | taskVal: {taskVal}");
+ break;
+ }
+ // aggiungo task!
+ taskDone.Add(item.Key, taskVal);
+ }
+ }
+ else
+ {
+ lgError($"Attenzione! memMap è nullo, non posso eseguire task2exe!");
+ }
+ }
+
+ return taskDone;
+ }
+
+ ///
+ /// Metodo generico di reset contapezzi...
+ ///
+ ///
+ public virtual bool resetcontapezziPLC()
+ {
+ return false;
+ }
+
+ ///
+ /// Salva valori indicati in prod data
+ ///
+ /// Item KVP di cui salvare i dati in currProdData come chiave/valore
+ ///
+ public void saveProdData(KeyValuePair item)
+ {
+ // imposto i valori...
+ if (currProdData.ContainsKey(item.Key))
+ {
+ currProdData[item.Key] = item.Value;
+ }
+ else
+ {
+ currProdData.Add(item.Key, item.Value);
+ }
+ }
+
+ ///
+ /// Metodo base connessione...
+ ///
+ public virtual void tryConnect()
+ {
+ dtAvvioAdp = DateTime.Now;
+ }
+
+ #endregion Public Methods
+
+ #region Internal Properties
+
+ ///
+ /// COnfiguraizone Endpoint corrente
+ ///
+ internal EndpointData cIobConf { get; set; } = new EndpointData();
+
+ internal string configPath { get; set; } = "";
+
+ internal IConfigurationRoot? confMan { get; set; } = null!;
+
+ #endregion Internal Properties
+
+ #region Internal Methods
+
+ internal bool getCRB(string keyName)
+ {
+ bool answ = false;
+ answ = confMan.GetValue(keyName);
+ return answ;
+ }
+
+ internal int getCRI(string keyName)
+ {
+ int answ = -1;
+ answ = confMan.GetValue(keyName);
+ return answ;
+ }
+
+ internal string getCRS(string keyName)
+ {
+ string answ = "";
+ answ = confMan.GetValue(keyName);
+ return answ;
+ }
+
+ #endregion Internal Methods
+
+ #region Protected Fields
+
+ ///
+ /// wrapper di log
+ ///
+ protected static Logger lg;
+
+ protected bool _connOk = false;
+
+ ///
+ /// Dizionario valori impostati x produzione
+ ///
+ protected Dictionary currProdData = new Dictionary();
+
+ ///
+ /// Dizionario ultimi valori (double) delle TSVC
+ ///
+ protected Dictionary LastTSVC = new Dictionary();
+
+ ///
+ /// Dizionario di VC da trattare come TimeSeries (con conf decodificata + processing successivo...)
+ ///
+ protected Dictionary TSVC_Data = new Dictionary();
+
+ #endregion Protected Fields
+
+ #region Protected Properties
+
+ ///
+ /// Numero letture IN da avvio
+ ///
+ protected int nReadIN { get; set; }
+
+ ///
+ /// test ping all'indirizzo PLC/CNC impostato nei parametri
+ ///
+ ///
+ protected IPStatus testPingMachine
+ {
+ get
+ {
+ IPStatus answ = IPStatus.Unknown;
+ // se disabilitato salto...
+ if (pingDisabled)
+ {
+ answ = IPStatus.Success;
+ }
+ else
+ {
+ IPAddress address;
+ PingReply reply;
+ using (Ping pingSender = new Ping())
+ {
+ address = IPAddress.Loopback;
+ int pingMsTimeout = cIobConf.PingMsTimeout;
+ IPAddress.TryParse(cIobConf.IpAddress, out address);
+ try
+ {
+ // se != null --> uso address...
+ if (address != null)
+ {
+ reply = pingSender.Send(address, pingMsTimeout);
+ }
+ else
+ {
+ reply = pingSender.Send(cIobConf.IpAddress, pingMsTimeout);
+ }
+ }
+ catch
+ {
+ reply = pingSender.Send(IPAddress.Loopback, pingMsTimeout);
+ }
+ answ = reply.Status;
+ }
+ }
+ return answ;
+ }
+ }
+
+ #endregion Protected Properties
+
+ #region Protected Methods
+
+ ///
+ /// Stringa raw dei parametri da scrivere...
+ ///
+ ///
+ protected string getParams2write()
+ {
+ string answ = "";
+ // recuperare da una apposita area REDIS...
+#if false
+ string url2call = $"{urlGetParams2Write}";
+ if (verboseLog)
+ {
+ lgInfo("chiamata URL " + url2call);
+ }
+ answ = utils.callUrlNow(url2call);
+ // se vuoto faccio seconda prova...
+ if (string.IsNullOrEmpty(answ))
+ {
+ answ = utils.callUrlNow(url2call);
+ }
+#endif
+ return answ;
+ }
+
+ ///
+ /// Effettua logging DEBUG corretto impostanto anche la variabile IOB prima di scrivere...
+ ///
+ ///
+ protected void lgDebug(string message)
+ {
+ lg.Debug(message);
+ }
+
+ ///
+ /// Effettua logging DEBUG corretto impostanto anche la variabile IOB prima di scrivere...
+ ///
+ ///
+ ///
+ protected void lgDebug(string message, params object[] args)
+ {
+ lg.Debug(message, args);
+ }
+
+ ///
+ /// Effettua logging ERROR corretto impostanto anche la variabile IOB prima di scrivere...
+ ///
+ ///
+ protected void lgError(string message)
+ {
+ lg.Error(message);
+ }
+
+ ///
+ /// Effettua logging ERROR corretto impostanto anche la variabile IOB prima di scrivere...
+ ///
+ ///
+ ///
+ protected void lgError(string message, params object[] args)
+ {
+ lg.Error(message, args);
+ }
+
+ ///
+ /// Effettua logging ERROR corretto impostanto anche la variabile IOB prima di scrivere...
+ ///
+ ///
+ ///
+ ///
+ protected void lgError(Exception exception, string message, params object[] args)
+ {
+ lg.Error(exception, message, args);
+ }
+
+ ///
+ /// Effettua logging FATAL corretto impostanto anche la variabile IOB prima di scrivere...
+ ///
+ ///
+ protected void lgFatal(string message)
+ {
+ lg.Fatal(message);
+ }
+
+ ///
+ /// Effettua logging FATAL corretto impostanto anche la variabile IOB prima di scrivere...
+ ///
+ ///
+ ///
+ protected void lgFatal(string message, params object[] args)
+ {
+ lg.Fatal(message, args);
+ }
+
+ ///
+ /// Effettua logging FATAL corretto impostanto anche la variabile IOB prima di scrivere...
+ ///
+ ///
+ ///
+ ///
+ protected void lgFatal(Exception exception, string message, params object[] args)
+ {
+ lg.Fatal(exception, message, args);
+ }
+
+ ///
+ /// Effettua logging INFO corretto impostanto anche la variabile IOB prima di scrivere...
+ ///
+ ///
+ protected void lgInfo(string message)
+ {
+ lg.Info(message);
+ }
+
+ ///
+ /// Effettua logging INFO corretto impostanto anche la variabile IOB prima di scrivere...
+ ///
+ ///
+ ///
+ protected void lgInfo(string message, params object[] args)
+ {
+ lg.Info(message, args);
+ }
+
+ ///
+ /// Effettua logging INFO corretto impostanto anche la variabile IOB prima di scrivere...
+ ///
+ ///
+ protected void lgTrace(string message)
+ {
+ lg.Trace(message);
+ }
+
+ ///
+ /// Effettua logging INFO corretto impostanto anche la variabile IOB prima di scrivere...
+ ///
+ ///
+ ///
+ protected void lgTrace(string message, params object[] args)
+ {
+ lg.Trace(message, args);
+ }
+
+ ///
+ /// Metodo da overridare x scrivere DAVVERO i parametri sul PLC
+ ///
+ ///
+ protected virtual void plcWriteParams(ref List updatedPar)
+ {
+ // non faccio nulla di base...
+ }
+
+ ///
+ /// Processa le richieste di scrittura memoria
+ ///
+ ///
+ protected string processMemWriteRequests()
+ {
+ string answ = "";
+ // li salvo nei parametri in memoria locale (ogni adapter DOVREBBE salvare POI sul VERO PLC)
+ List writeList = new List();
+ List updatedPar = new List();
+ // recupero elenco delle cose da fare
+ string resp = getParams2write();
+ if (!string.IsNullOrEmpty(resp))
+ {
+ try
+ {
+ writeList = JsonConvert.DeserializeObject
>(resp);
+ // se ho da fare chiamo esecuzione..
+ if (writeList.Count > 0)
+ {
+ foreach (var item in writeList)
+ {
+ // scrivo in memoria
+ if (memMap.mMapWrite.ContainsKey(item.uid))
+ {
+ memMap.mMapWrite[item.uid].value = item.reqValue;
+ // accodo in stringa taskVal...
+ answ += $" | Parameter {item.uid} --> {item.reqValue}";
+ // sistemo valori
+ item.value = item.reqValue;
+ lgInfo($"Richiesta update parametro {item.uid} | actVal = {item.value} | reqVal = {item.reqValue}");
+ item.reqValue = "";
+ // salvo in lista da ritrasmettere
+ updatedPar.Add(item);
+ }
+ else
+ {
+ answ += $" | Error: parameter {item.uid} not found";
+ }
+ }
+ // richiamo scrittura parametri su PLC
+ plcWriteParams(ref updatedPar);
+ // invio su cloud parametri!
+ string rawData = JsonConvert.SerializeObject(updatedPar);
+ lgInfo("Notifica a server scrittura parametri");
+#if false
+ utils.callUrl($"{urlUpdateWriteParams}", rawData);
+#endif
+ }
+ }
+ catch (Exception exc)
+ {
+ lgError($"Eccezione in processMemWriteRequests:{Environment.NewLine}{exc}");
+ }
+ }
+ else
+ {
+ lgError("Non è stata ricevuta risposta x task da eseguire");
+ }
+ return answ;
+ }
+
+ ///
+ /// setup parametri da file di conf
+ ///
+ protected void setupMemMap()
+ {
+ lgInfo($"setupMemMap | trovati {memMap.mMapRead.Count} parametri Read (TSVC)");
+ lgInfo($"setupMemMap | trovati {memMap.mMapWrite.Count} parametri Write");
+ if (getCRB("verbose"))
+ {
+ string rawMemConf = JsonConvert.SerializeObject(memMap, Formatting.Indented);
+ lgDebug($"setupMemMap | configurazione memoria R/W:{Environment.NewLine}{rawMemConf}");
+ }
+ // se ho variabili read --> genero dati TSVC...
+ if (memMap.mMapRead.Count > 0)
+ {
+ TSVC_Data.Clear();
+ LastTSVC.Clear();
+ VCData currConf;
+ int periodo = 0;
+ VC_func funz = VC_func.POINT;
+ // accodo nella conf...
+ foreach (var item in memMap.mMapRead)
+ {
+ funz = item.Value.func;
+ periodo = item.Value.period;
+ currConf = new VCData()
+ {
+ Funzione = funz,
+ Period = periodo,
+ DTStart = DateTime.Now.AddHours(-1),
+ dataArray = new List()
+ };
+ TSVC_Data.Add(item.Key, currConf);
+ }
+ // documento...
+ foreach (var item in TSVC_Data)
+ {
+ lgTrace($"TSVC: {item.Key} | periodo: {item.Value.Period} | funz: {item.Value.Funzione}");
+ // salvo i valori PREC...
+ LastTSVC.Add(item.Key, 0);
+ }
+ }
+ }
+
+ #endregion Protected Methods
+ }
+}
\ No newline at end of file
diff --git a/MP.MONO.ADAPTER.OPC/IobOpcUa.cs b/MP.MONO.ADAPTER.OPC/IobOpcUa.cs
new file mode 100644
index 0000000..9285f37
--- /dev/null
+++ b/MP.MONO.ADAPTER.OPC/IobOpcUa.cs
@@ -0,0 +1,1360 @@
+using Microsoft.Extensions.Configuration;
+using MP.MONO.Core;
+using MP.MONO.Core.CONF;
+using Newtonsoft.Json;
+using NLog;
+using Opc.Ua;
+using Opc.Ua.Configuration;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.NetworkInformation;
+using System.Text;
+using System.Threading.Tasks;
+using static MP.MONO.Core.Enums;
+
+namespace MP.MONO.ADAPTER.OPC
+{
+ ///
+ /// Classe di comunicazione OPC-UA
+ ///
+ public class IobOpcUa : IobGeneric
+ {
+ private Logger Log = LogManager.GetCurrentClassLogger();
+
+ ///
+ /// Avvia un oggetto IOB in grado di comunicare con server OPC-UA
+ /// Impiega il pacchetto Nuget OPC-UA foundation https://github.com/OPCFoundation/UA-.NETStandard
+ ///
+ ///
+ public IobOpcUa(string confPath, IConfigurationRoot? config) : base(confPath , config)
+ {
+
+ // gestione data filtering...
+ if (!string.IsNullOrEmpty(config.GetValue("ENABLE_DATA_FILTER")))
+ {
+ bool.TryParse(config.GetValue("ENABLE_DATA_FILTER"), out enableDataFilter);
+ }
+ // gestione restart OpcUa client...
+ if (!string.IsNullOrEmpty(config.GetValue("ENABLE_CLI_RESTART")))
+ {
+ bool.TryParse(config.GetValue("ENABLE_CLI_RESTART"), out enableCliRestart);
+ }
+ // init datetime counters
+ DateTime adesso = DateTime.Now;
+ lastCurrent = adesso;
+ // ora leggo il file di conf specifico....
+ string jsonFileName = config.GetValue("OPC_PARAM_CONF");
+ if (!string.IsNullOrEmpty(jsonFileName))
+ {
+ // leggo il file...
+ loadOpcUaConf(jsonFileName);
+ }
+ }
+
+
+ //#region Public Methods
+
+ ///
+ /// Processo i task richiesti e li elimino dalla coda 1:1
+ ///
+ ///
+ public override Dictionary executeTasks(Dictionary task2exe)
+ {
+ // uso metodo base x ora
+ return base.executeTasks(task2exe);
+ }
+
+ /////
+ ///// Recupera uno specifico dataItem
+ /////
+ /////
+ /////
+ //public string getDataItemValue(string diKey)
+ //{
+ // string answ = "";
+ // if (string.IsNullOrEmpty(diKey))
+ // {
+ // lgError($"Attenzione: richiesta chiave vuota in getDataItemValue{Environment.NewLine}StackTrace: {Environment.StackTrace}");
+ // }
+ // else
+ // {
+ // DateTime adesso = DateTime.Now;
+ // if (dataItemMem.Count == 0)
+ // {
+ // numErroriCheck++;
+ // if (vetoCheckStatus < adesso)
+ // {
+ // lgError($"Errore in getDataItemValue per {diKey} | dataItemMem NON contiene valori");
+ // // imposto veto per vetoSeconds...
+ // vetoCheckStatus = adesso.AddSeconds(vetoSeconds * 2);
+ // }
+ // }
+ // else
+ // {
+ // if (!dataItemMem.ContainsKey(diKey))
+ // {
+ // if (vetoCheckStatus < adesso)
+ // {
+ // lgError($"Errore in getDataItemValue per {diKey} | dataItemMem non contiene la chiave richiesta ma altri {dataItemMem.Count} valori");
+ // // imposto veto per vetoSeconds...
+ // vetoCheckStatus = adesso.AddSeconds(vetoSeconds * 2);
+ // }
+ // numErroriCheck++;
+ // }
+ // else
+ // {
+ // try
+ // {
+ // var currDataItem = dataItemMem[diKey];
+ // answ = currDataItem.value;
+ // }
+ // catch (Exception exc)
+ // {
+ // lgError($"Errore in getDataItemValue per {diKey} | dataItemMem contiene {dataItemMem.Count} valori {Environment.NewLine}{exc}");
+ // if (dataItemMem != null)
+ // {
+ // lgError($"dataItemMem contiene {dataItemMem.Count} valori");
+ // int maxNum = 5;
+ // foreach (var item in dataItemMem)
+ // {
+ // maxNum--;
+ // if (maxNum < 0)
+ // {
+ // break;
+ // }
+ // lgInfo($"{item.Key} --> {item.Value.DisplayName} = {item.Value.value}");
+ // }
+ // }
+ // }
+ // }
+ // }
+ // // se supero soglia errori lettura --> disconnetto e resetto
+ // if (numErroriCheck > maxErroriCheck)
+ // {
+ // lgInfo($"numErroriCheck: {numErroriCheck} --> richiesta disconnessione adapter con tryDisconnect");
+
+ // numErroriCheck = 0;
+ // tryDisconnect();
+ // }
+ // }
+
+ // return answ;
+ //}
+
+ /////
+ ///// Recupero dati dinamici...
+ /////
+ //public override Dictionary getDynData()
+ //{
+ // Dictionary outVal = new Dictionary();
+ // return outVal;
+ //}
+
+ /////
+ ///// Effettua vero processing contapezzi
+ /////
+ //public override void processContapezzi()
+ //{
+ // if (utils.CRB("enableContapezzi"))
+ // {
+ // // da ridefinire la gestione base del contapezzi OPC-UA...
+ // }
+ //}
+
+ /////
+ ///// Effettua lettura semafori principale
+ ///// Parametri da aggiornare x display in form
+ /////
+ //public override void readSemafori(ref newDisplayData currDispData)
+ //{
+ // base.readSemafori(ref currDispData);
+ // try
+ // {
+ // if (verboseLog)
+ // {
+ // lgInfo("inizio read semafori");
+ // }
+
+ // currDispData.semIn = Semaforo.SV;
+
+ // // verifico SE sia necessario forzare la lettura RAW
+ // if (doByteRead)
+ // {
+ // if (DateTime.Now.Subtract(lastCurrent).TotalSeconds > lastCurrentMaxElapsed)
+ // {
+ // // FIXME TODO !!!
+ // foreach (var item in dataItemMem)
+ // {
+ // // restituisce i 115 byte da deserializzare... qui HACK!
+ // var rawVal = UA_ref.ReadNodeRaw(item.Value.StartNodeId);
+ // if (rawVal != null)
+ // {
+ // byteRawData = getByteRaw((DataValue)rawVal);
+ // lastCurrent = DateTime.Now;
+ // currReadErrors = 0;
+ // }
+ // else
+ // {
+ // currReadErrors++;
+ // }
+ // }
+ // }
+ // }
+
+ // // decodifica e gestione
+ // decodeToBaseBitmap();
+ // reportRawInput(ref currDispData);
+ // }
+ // catch (Exception exc)
+ // {
+ // currDispData.semIn = Semaforo.SR;
+ // lgError($"Eccezione in readSemafori:{Environment.NewLine}{exc}");
+ // }
+ // // se > max errori --> disconnetto
+ // if (currReadErrors > maxReadErrors)
+ // {
+ // lgError($"Superato limite errori Read ({currReadErrors}) --> tryDisconnect");
+ // currReadErrors = 0;
+ // tryDisconnect();
+ // }
+ // else
+ // {
+ // // altrimenti pausa forzata
+ // Thread.Sleep(300);
+ // }
+ //}
+
+ /////
+ ///// Effettua reset del contapezzi, NON POSSIBILE in questa versione
+ /////
+ /////
+ //public override bool resetcontapezziPLC()
+ //{
+ // bool answ = false;
+ // return answ;
+ //}
+
+ /////
+ ///// Effettua IMPOSTAZIONE FORZATA del contapezzi, NON POSSIBILE in questa versione
+ /////
+ /////
+ //public override bool setcontapezziPLC(int newPzCount)
+ //{
+ // bool answ = false;
+ // return answ;
+ //}
+
+
+ ///
+ /// Verifica SE si debba fare log periodico (ogni "verboseLogTOut" sec...)
+ ///
+ public bool periodicLog
+ {
+ get
+ {
+ bool answ = false;
+ answ = (DateTime.Now.Subtract(lastPeriodicLog).TotalSeconds > getCRI("verboseLogTOut"));
+ if (answ)
+ {
+ lastPeriodicLog = DateTime.Now;
+ }
+
+ return answ;
+ }
+ }
+
+
+ ///
+ /// Override connessione
+ ///
+ public override void tryConnect()
+ {
+ if (!connectionOk)
+ {
+ // controllo che il ping sia stato tentato almeno pingTestSec fa...
+ if (DateTime.Now.Subtract(lastPING).TotalSeconds > getCRI("pingTestSec"))
+ {
+ if (verboseLog || periodicLog)
+ {
+ lgInfo("OpcUa: ConnKO - tryConnect");
+ }
+ // in primis salvo data ping...
+ lastPING = DateTime.Now;
+ // se passa il ping faccio il resto...
+ if (testPingMachine == IPStatus.Success)
+ {
+ string szStatusConnection = "";
+ try
+ {
+ // ora provo connessione...
+ var task = Task.Run(async () =>
+ {
+ return await doConnect().ConfigureAwait(false);
+ });
+ short esitoLink = task.Result; // use returned result from async method here
+ lgInfo($"szStatusConnection OpcUa, esitoLink: {esitoLink}");
+ connectionOk = true;
+ // refresh stato allarmi!!!
+ if (connectionOk)
+ {
+ if (adpRunning)
+ {
+ lgInfo("Connessione OK");
+ }
+ }
+ else
+ {
+ lgError("Impossibile procedere, connessione mancante...");
+ }
+ }
+ catch (Exception exc)
+ {
+ lgFatal($"Errore nella connessione all'adapter OpcUa: {szStatusConnection}{Environment.NewLine}{exc}");
+ connectionOk = false;
+ lgInfo($"Eccezione in TryConnect, Adapter OpcUa NON running, pausa di {getCRI("waitRecMSec")} msec prima di ulteriori tentativi di riconnessione");
+ }
+ }
+ else
+ {
+ // loggo no risposta ping ...
+ connectionOk = false;
+ if (verboseLog || periodicLog)
+ {
+ lgInfo($"Attenzione: OpcUa controllo PING fallito per IP {cIobConf.IpAddress}");
+ }
+ }
+ }
+ }
+ else
+ {
+ needRefresh = true;
+ }
+ // se non è ancora connesso faccio procesisng memoria caso disconnesso...
+ if (!connectionOk)
+ {
+ // processo semafori ed invio...
+ processMemoryDiscon();
+ }
+ }
+
+ ///
+ /// Processa gestione memoria x invio dati QUANDO IOB è disconnesso da CNC...
+ ///
+ internal void processMemoryDiscon()
+ {
+ // controllo contatore invio "keepalive"... invio solo a scadenza
+ if (DateTime.Now.Subtract(lastDisconnCheck).TotalSeconds > getCRI("disconMaxSec"))
+ {
+ // FIXME TODO !!! FARE
+ // da realizzare un sistema x inviare che il server OPC-UA è disonnesso e la macchina è in stato unknown / server disconnesso
+#if false
+ // resetto tutti i valori BYTE IN/PREV/OUT... così invio macchina spenta...
+ B_input = 0;
+ B_output = 0;
+ B_previous = 0;
+ accodaSigIN(ref currDispData);
+#endif
+ // update controllo
+ lastDisconnCheck = DateTime.Now;
+ }
+ }
+
+ ///
+ /// dataOra ultima verifica CNC disconnesso...
+ ///
+ internal DateTime lastDisconnCheck;
+
+ /////
+ ///// Override disconnessione
+ /////
+ //public override void tryDisconnect()
+ //{
+ // if (connectionOk)
+ // {
+ // if (UA_ref != null)
+ // {
+ // string szStatusConnection = "";
+ // try
+ // {
+ // UA_ref.Disconnect();
+ // connectionOk = false;
+ // lgInfo(szStatusConnection);
+ // lgInfo("Effettuata disconnessione adapter OpcUa!");
+ // }
+ // catch (Exception exc)
+ // {
+ // lgFatal(exc, "Errore nella disconnessione dall'adapter OpcUa");
+ // }
+ // }
+ // else
+ // {
+ // lgDebug("IMPOSSIBILE effettuare disconnessione OpcUa: UA_ref non disponibile...");
+ // }
+ // }
+ // else
+ // {
+ // lgError("IMPOSSIBILE effettuare disconnessione OpcUa: Connessione non disponibile...");
+ // }
+ //}
+
+ //#endregion Public Methods
+
+ //#region Internal Methods
+
+ ///
+ /// Verifica ed invia variazioni
+ ///
+ ///
+ ///
+ ///
+ internal bool checkAndSend(Opc.Ua.Client.MonitoredItem MonIt, string NotifyValue, bool forceSend)
+ {
+ bool changed = false;
+ if (MonIt != null)
+ {
+ if (!string.IsNullOrEmpty(NotifyValue))
+ {
+ string sVal = "";
+ string descr = "";
+ DateTime locTStamp = DateTime.Now;
+ descr = itemTranslation("OPC", MonIt.DisplayName);
+ sVal = $"Change: {locTStamp.ToString()} | descr: {descr} | Id: {MonIt.StartNodeId} | Val: {NotifyValue}";
+ lgInfo(sVal);
+
+ // verifico se salvare
+ changed = checkSaveValue(MonIt, NotifyValue, true);
+ // cerco se non sia un dato filtrato in FLUXLOG...
+ bool isFiltered = opcUaParams.fluxLogVeto.Contains(MonIt.DisplayName);
+ if (isFiltered)
+ {
+ lgTrace($"NON ACCODATO sample per {MonIt.DisplayName} - trovato VETO in fluxLogVeto", false);
+ }
+ else
+ {
+ if (changed || forceSend)
+ {
+ accodaFLog(sVal, qEncodeFLog(descr, $"{NotifyValue}"));
+ }
+ else
+ {
+ lgTrace($"NON ACCODATO sample per {MonIt.DisplayName} - verifica variazione ha dato esito negativo", false);
+ }
+ }
+ }
+ else
+ {
+ lgError($"checkAndSend ERROR | MonIt: {MonIt.DisplayName} | NotifyValue Null!!!");
+ }
+ }
+ else
+ {
+ lgError("checkAndSend ERROR: MonIt null");
+ }
+ return changed;
+ }
+
+ ///
+ /// Verifica ed invia variazioni DAL FORMATO RAW data (byte[])
+ ///
+ ///
+ ///
+ ///
+ internal virtual bool checkAndSendRaw(Opc.Ua.Client.MonitoredItem MonIt, byte[] NotifyValue, bool forceSend)
+ {
+ bool changed = false;
+ if (MonIt != null)
+ {
+ if (NotifyValue != null && NotifyValue.Length > 0)
+ {
+ // versione base: il valore è la stringa composta da TUTTI i valori in BYTE
+ // espressi come comma-sep-string
+ StringBuilder sb = new StringBuilder();
+ foreach (var bVal in NotifyValue)
+ {
+ sb.Append($"{bVal},");
+ }
+ string currVal = sb.ToString();
+
+ string sVal = "";
+ string descr = "";
+ DateTime locTStamp = DateTime.Now;
+ descr = itemTranslation("OPC", MonIt.DisplayName);
+ sVal = $"Change: {locTStamp.ToString()} | descr: {descr} | Id: {MonIt.StartNodeId} | Val: {currVal}";
+ lgInfo(sVal);
+
+ // verifico se salvare
+ changed = checkSaveValue(MonIt, currVal, true) || forceSend;
+ // cerco se non sia un dato filtrato in FLUXLOG...
+ bool isFiltered = opcUaParams.fluxLogVeto.Contains(MonIt.DisplayName);
+ if (isFiltered)
+ {
+ lgTrace($"NON ACCODATO sample per {MonIt.DisplayName} - trovato VETO in fluxLogVeto", false);
+ }
+ else
+ {
+ if (changed || forceSend)
+ {
+ accodaFLog(sVal, qEncodeFLog(descr, $"{NotifyValue}"));
+ }
+ else
+ {
+ lgTrace($"NON ACCODATO sample per {MonIt.DisplayName} - verifica variazione ha dato esito negativo", false);
+ }
+ }
+ }
+ else
+ {
+ lgError($"checkAndSend ERROR | MonIt: {MonIt.DisplayName} | NotifyValue Null!!!");
+ }
+ }
+ else
+ {
+ lgError("checkAndSend ERROR: MonIt null");
+ }
+ return changed;
+ }
+
+ internal void sendDataItemListToServer(NodeId StartNodeId)
+ {
+ // converto gli attuali nell'elenco dataitem...
+ List elencoDataItems = dataItemMem.Select(d => new MachDataItem()
+ {
+ uuid = d.Key,
+ Category = DataItemCategory.EVENT,
+ Name = d.Value.DisplayName,
+ Type = $"{d.Value.NodeClass}",
+ SubType = $"{StartNodeId}"
+ }
+ ).ToList();
+
+ lgInfo($"Richiesta sendDataItemListToServer per {elencoDataItems.Count} dataItems");
+ // aspetta un tempo random da 10-100 ms...
+ Random rnd = new Random();
+ Task.Delay(rnd.Next(10, 100));
+ // invio il dataItem serializzato...
+ sendDataItemsList(elencoDataItems);
+ }
+
+ ///
+ /// Evento rilevazione modifica valori -->; chiamo checkSend
+ ///
+ ///
+ ///
+ internal virtual void UA_ref_eh_MonItChange(object sender, opcUaMonitItemChange e)
+ {
+ string currVal = "";
+ // da verificare decodifica valore byte...
+ if (doByteRead)
+ {
+ var currNot = e.CurrNotify;
+ if (currNot != null)
+ {
+ byteRawData = getByteRaw((DataValue)currNot.Value);
+ checkAndSendRaw(e.CurrMonitoredItem, byteRawData, false);
+ }
+ }
+ else
+ {
+ currVal = $"{e.CurrNotify.Value}";
+ checkAndSend(e.CurrMonitoredItem, currVal, false);
+ }
+ // aggiorno ultima lettura
+ lastCurrent = DateTime.Now;
+ }
+
+ //#endregion Internal Methods
+
+ //#region Protected Fields
+
+ ///
+ /// Struttura dove vengono memorizzati i dataitem ed i rispettivi valori x processing
+ ///
+ protected Dictionary dataItemMem = new Dictionary();
+
+ ///
+ /// Abilitazione restart (da opt par...)
+ ///
+ protected bool enableCliRestart = false;
+
+ ///
+ /// Gestione filtraggio dati
+ ///
+ protected bool enableDataFilter = false;
+
+ ///
+ /// Determina se ha effettuata lettura items in memoria x confronto...
+ ///
+ protected bool hasReadItems = false;
+
+ ///
+ /// Ultimo current received x gestione update periodico...
+ ///
+ protected DateTime lastCurrent = DateTime.Now;
+
+ ///
+ /// Oggetto MAIN x connessione OPC-UA
+ ///
+ protected UAClient UA_ref;
+
+ /////
+ ///// Veto controllo status x log...
+ /////
+ //protected DateTime vetoCheckStatus = DateTime.Now;
+
+ //protected int WatchDog = 0;
+
+ //#endregion Protected Fields
+
+ //#region Protected Properties
+
+ ///
+ /// Area dati raw per lettura encoded (SE presente)
+ ///
+ protected byte[] byteRawData { get; set; } = new byte[1];
+
+ ///
+ /// Indica se si debba leggere un area di tipo "encoded" (byte raw --> obj)
+ ///
+ protected bool doByteRead { get; set; } = false;
+
+ /////
+ ///// Verifico se abbia ALMENO un errore...
+ /////
+ //protected bool hasError
+ //{
+ // get
+ // {
+ // return checkMultiCondition(opcUaParams.condError);
+ // }
+ //}
+
+ /////
+ ///// Indica se abbia emergenza ARMATA (cond normale)
+ /////
+ //protected bool hasEStopArmed
+ //{
+ // get
+ // {
+ // return checkMultiCondition(opcUaParams.condEStop);
+ // }
+ //}
+
+ /////
+ ///// Indica se abbia stato POWER ON (multicondizione)
+ /////
+ //protected virtual bool hasPowerOn
+ //{
+ // get
+ // {
+ // return checkMultiCondition(opcUaParams.condPowerOn);
+ // }
+ //}
+
+ /////
+ ///// Indica se abbia stato MANUAL (condizioni varie, es stopped)
+ /////
+ //protected virtual bool isManual
+ //{
+ // get
+ // {
+ // return checkMultiCondition(opcUaParams.condManual);
+ // }
+ //}
+
+ /////
+ ///// Indica se abbia stato READY (condizioni varie, es ausiliari OK)
+ /////
+ //protected virtual bool isReady
+ //{
+ // get
+ // {
+ // return checkMultiCondition(opcUaParams.condReady);
+ // }
+ //}
+
+ /////
+ ///// Indica se sia in stato Ssetup
+ /////
+ //protected bool isSetup
+ //{
+ // get
+ // {
+ // return checkMultiCondition(opcUaParams.condSetup);
+ // }
+ //}
+
+ /////
+ ///// Indica se sia in stato WarmUp / CoolDown (riscaldamento/raffreddamento)
+ /////
+ //protected bool isWarmUpCoolDown
+ //{
+ // get
+ // {
+ // return checkMultiCondition(opcUaParams.condWarmUpCoolDown);
+ // }
+ //}
+
+ /////
+ ///// Indica se sia in stato Warning
+ /////
+ //protected bool isWarning
+ //{
+ // get
+ // {
+ // return checkMultiCondition(opcUaParams.condWarning);
+ // }
+ //}
+
+ /////
+ ///// Indica se abbia stato READY (condizioni varie, es ausiliari OK)
+ /////
+ //protected bool isWorking
+ //{
+ // get
+ // {
+ // // cerco SE HO cond OPC Ua o classica... nel caso creo e salvo
+ // if (opcUaParams.condWorkOpc.checkList == null || opcUaParams.condWorkOpc.checkList.Count == 0)
+ // {
+ // opcUaParams.condWorkOpc = new diCheckCondSetup()
+ // {
+ // checkList = opcUaParams.condWork,
+ // checkMode = boolCheckMode.AND,
+ // negateValue = false
+ // };
+ // }
+ // return checkMultiCondition(opcUaParams.condWorkOpc);
+ // }
+ //}
+
+ /////
+ ///// Periodo massimo (in sec) per letture dati RAW in mancanza di eventi
+ /////
+ //protected int lastCurrentMaxElapsed { get; set; } = 120;
+
+ ///
+ /// Parametri specifici OPC-UA
+ ///
+ protected OpcUaParamConf opcUaParams { get; set; }
+
+ /////
+ ///// URL x salvataggio elenco dataItems OpcUa
+ /////
+ //protected string urlSaveDataItems
+ //{
+ // get
+ // {
+ // string answ = "";
+ // try
+ // {
+ // string machineName = Environment.MachineName;
+ // answ = $@"{cIobConf.serverData.TRANSP}://{cIobConf.serverData.MPIP}{cIobConf.serverData.MPURL}{cIobConf.serverData.CMDALIVE}/saveDataItems/{cIobConf.codIOB}";
+ // }
+ // catch (Exception exc)
+ // {
+ // lgError(exc, "Errore in composizione urlSaveDataItems");
+ // }
+ // return answ;
+ // }
+ //}
+
+ //#endregion Protected Properties
+
+ //#region Protected Methods
+
+ /////
+ ///// Verifica un DataItem e se il valore corrisponde a quello indicato come "true value"
+ ///// restituisce true
+ /////
+ /////
+ /////
+ /////
+ //protected bool checkDataItem(string itemName, string trueVal)
+ //{
+ // bool answ = false;
+ // OpcUaDataItemExt currValue = null;
+ // try
+ // {
+ // currValue = dataItemMem[itemName];
+ // answ = (currValue.value.Equals(trueVal));
+ // }
+ // catch
+ // {
+ // lgError($"Errore in decodifica valore per {itemName} rispetto a {trueVal} | recuperato {currValue} / {currValue.value}");
+ // }
+ // return answ;
+ //}
+
+ /////
+ ///// Verifica condizione "multipla" secondo setup json
+ /////
+ ///// Set condizioni da validare
+ /////
+ //protected bool checkMultiCondition(diCheckCondSetup reqCondition)
+ //{
+ // bool answ = false;
+ // int numCondOk = 0;
+ // int numCond = 0;
+ // if (reqCondition.checkList != null && reqCondition.checkList.Count > 0)
+ // {
+ // numCond = reqCondition.checkList.Count;
+ // // cerco nell'elenco delle condizioni che indicano lavora se sono ok faccio +1 conteggio......
+ // foreach (var item in reqCondition.checkList)
+ // {
+ // if (string.IsNullOrEmpty(item.keyName))
+ // {
+ // lgError($"Attenzione: item vuoto in checkMultiCondition{Environment.NewLine}StackTrace: {Environment.StackTrace}");
+ // }
+ // else
+ // {
+ // if (getDataItemValue(item.keyName) == item.targetValue)
+ // {
+ // numCondOk++;
+ // }
+ // }
+ // }
+ // if (reqCondition.checkMode == boolCheckMode.AND)
+ // {
+ // answ = (numCond == numCondOk);
+ // }
+ // else if (reqCondition.checkMode == boolCheckMode.OR)
+ // {
+ // answ = numCondOk > 0;
+ // }
+ // }
+ // else
+ // {
+ // answ = true;
+ // }
+ // // verifico se devo negare il valore...
+ // answ = reqCondition.negateValue ? !answ : answ;
+ // // restituisco
+ // return answ;
+ //}
+
+ ///
+ /// Verifica / Salva valore e restitusice SE sia variato (e quindi da inviare...)
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected bool checkSaveValue(Opc.Ua.Client.MonitoredItem dataItem, string NotifyValue, bool sendItemList)
+ {
+ bool answ = !enableDataFilter;
+ double oldVal = 0;
+ double newVal = 0;
+ if (dataItem != null)
+ {
+ if (!string.IsNullOrEmpty(NotifyValue))
+ {
+ lgTrace($"Richiesta checkSaveValue per {dataItem.DisplayName} | id: {dataItem.StartNodeId} | Valore: {NotifyValue}");
+ // verifico in memoria se ho l'oggetto condition ed il suo valore..
+ string uuid = $"{dataItem.DisplayName}";
+ DateTime adesso = DateTime.Now;
+ if (dataItemMem.ContainsKey(uuid))
+ {
+ OpcUaDataItemExt currDataItemMem = dataItemMem[uuid];
+ // controllo SE SIA scaduto il tempo massimo...
+ if (Math.Abs(dataItemMem[uuid].valueTimestamp.Subtract(adesso).TotalSeconds) > currDataItemMem.samplePeriod)
+ {
+ answ = true;
+ }
+ else
+ {
+ // ALTRIMENTI controllo SE diverso
+ if (dataItemMem[uuid].value != $"{NotifyValue}")
+ {
+ lgInfo($"Val uuid: {dataItemMem[uuid].value} | NotifyValue: {NotifyValue}");
+ // controllo SE ho DeadBand...
+ if (dataItemMem[uuid].thresholdDeadBand > 0)
+ {
+ // recupero i valori e testo DeadBand...
+ bool isNum01 = double.TryParse(dataItemMem[uuid].value.Replace(".", ","), out oldVal);
+ bool isNum02 = double.TryParse($"{NotifyValue}".Replace(".", ","), out newVal);
+ // test deadband!
+ if (!(isNum01 && isNum02))
+ {
+ answ = true;
+ }
+ else
+ {
+ if (Math.Abs(newVal - oldVal) > dataItemMem[uuid].thresholdDeadBand)
+ {
+ // indico da salvare..
+ answ = true;
+ }
+ }
+ lgInfo($"Test deadband: oldVal: {oldVal} | newVal: {newVal}");
+ }
+ }
+ }
+ if (answ)
+ {
+ // salvo!
+ dataItemMem[uuid].value = $"{NotifyValue}";
+ dataItemMem[uuid].valueTimestamp = adesso;
+ }
+ }
+ else
+ {
+ // registro non trovato da aggiungere...
+ lgInfo($"DataItem non trovato in checkSaveValue: {dataItem.DisplayName}");
+ // provo a creare oggetto in memoria...
+ try
+ {
+ int dSamplePeriod = 0;
+ int threshDBand = 0;
+ uuid = "";
+ var currDataItem = formatDataItem(ref dSamplePeriod, ref threshDBand, ref uuid, dataItem);
+ // sistemo valore/periodo
+ currDataItem.value = $"{NotifyValue}";
+ currDataItem.valueTimestamp = adesso;
+ // aggiungo
+ dataItemMem.Add(uuid, currDataItem);
+ if (sendItemList)
+ {
+ sendDataItemListToServer(dataItem.StartNodeId);
+ }
+ }
+ catch (Exception exc)
+ {
+ lgError($"Eccezione in checkSaveSample{Environment.NewLine}{exc}");
+ }
+ }
+ }
+ else
+ {
+ lgError("Attenzione: checkSaveItem con Notify null!");
+ }
+ }
+ else
+ {
+ lgError("Attenzione: checkSaveItem con MonIt null!");
+ }
+ return answ;
+ }
+
+ /////
+ ///// Effettua decodifica aree memoria alla bitmap usata x MAPO
+ /////
+ //protected virtual void decodeToBaseBitmap()
+ //{
+ // DateTime adesso = DateTime.Now;
+ // // init a zero...
+ // B_input = 0;
+
+ // /* -----------------------------------------------------
+ // * STATE MACHINE 60 STD / SIMULA
+ // *------------------------------------------------------
+ // * bitmap MAPO
+ // * B0: POWER_ON
+ // * B1: RUN
+ // * B2: pzCount
+ // * B3: allarme
+ // * B4: manuale
+ // * B5: SlowTC (NON gestito qui)
+ // * B6: warm-up / cool-down
+ // * B7: emergenza
+ // ---------------------------------------------------- */
+
+ // // se valido il check ping lo eseguo... altrimenti lo do x buono
+ // bool checkPing = !opcUaParams.pingAsPowerOn;
+ // string currRun = "";
+ // if (!checkPing)
+ // {
+ // checkPing = (testPingMachine == IPStatus.Success);
+ // }
+ // // bit 0 (poweron) imposto a 1 SE pingo + PowerOn=="ON"...
+ // bool powerOnOk = checkPing && hasPowerOn;
+
+ // // controllo se sono poweroff e se non ho dati buoni da > lastCurrentMaxElapsed --> disconnetto
+ // if (!powerOnOk && adesso.Subtract(lastCurrent).TotalSeconds > lastCurrentMaxElapsed)
+ // {
+ // tryDisconnect();
+ // }
+
+ // // solo se non ho veto check
+ // int vFactor = 2;
+ // if (vetoCheckStatus < adesso)
+ // {
+ // lgTrace($"Stato variabili checkPing: {testPingMachine}");
+ // // imposto veto per vetoSeconds...
+ // vetoCheckStatus = adesso.AddSeconds(vetoSeconds * vFactor);
+ // }
+
+ // // se abilitato watchdog...
+ // if (opcUaParams.WatchDog.IsEnabled)
+ // {
+ // lgTrace("WatchDog 01");
+ // if (adesso.Subtract(lastWatchDogPLC).TotalSeconds > 2)
+ // {
+ // lastWatchDogPLC = adesso;
+ // WatchDog++;
+ // WatchDog = WatchDog > opcUaParams.WatchDog.MaxVal ? 0 : WatchDog;
+
+ // lgTrace($"WatchDog val: {WatchDog}");
+ // try
+ // {
+ // WriteValue commWriteVal = new WriteValue();
+ // commWriteVal.NodeId = new NodeId(opcUaParams.WatchDog.MemConfWrite);
+ // commWriteVal.AttributeId = Attributes.Value;
+ // commWriteVal.Value = new DataValue();
+ // commWriteVal.Value.Value = WatchDog;
+
+ // List nodes2Write = new List();
+ // nodes2Write.Add(commWriteVal);
+ // UA_ref.WriteNodes(nodes2Write);
+ // lgTrace("Effettuata scrittura WatchDog");
+ // }
+ // catch (Exception exc)
+ // {
+ // lgError($"Eccezione in gestione WatchDog, valore attuale {WatchDog}{Environment.NewLine}{exc}");
+ // }
+ // }
+ // }
+ // else
+ // {
+ // lgTrace("WatchDog disabilitato");
+ // }
+
+ // // log opzionale!
+ // if (verboseLog)
+ // {
+ // lgDebug($"Trasformazione checkPing: {checkPing} | hasPowerOn: {hasPowerOn} | B_input: {B_input} | currRun = {currRun}");
+ // }
+ //}
+
+ protected byte[] getByteRaw(DataValue obj)
+ {
+ if (obj == null)
+ return null;
+
+ byte[] rawByte = new byte[1];
+ if (obj != null)
+ {
+ try
+ {
+ var wrapVal = ((DataValue)obj).WrappedValue;
+ //rawByte = ObjectToByteArray(rawVal);
+ var bodyVal = ((Opc.Ua.ExtensionObject)wrapVal.Value).Body;
+ rawByte = (byte[])bodyVal;
+ }
+ catch
+ { }
+ }
+ return rawByte;
+ }
+
+ ///
+ /// Effettua traduzione ITEM da LUT parametrica (key: tipo+id) del file di conf, se non
+ /// trovo uso key
+ ///
+ ///
+ ///
+ ///
+ protected string itemTranslation(string tipo, string id)
+ {
+ string answ = "";
+ string lemma = id;
+ if (!string.IsNullOrEmpty(tipo))
+ {
+ lemma = $"{tipo}_{id}";
+ }
+ // cerco nel dizionario delle traduzioni SE esiste un valore e prendo quello, altrimenti
+ // uso il lemma...
+ if (opcUaParams.itemTranslation.ContainsKey(lemma))
+ {
+ answ = opcUaParams.itemTranslation[lemma];
+ }
+ else
+ {
+ answ = lemma;
+ }
+ return answ;
+ }
+
+ ///
+ /// Effettua lettura file di conf specifico OPC-UA da oggetto serializzato json
+ /// Nome file da cui leggere i parametri json
+ ///
+ protected void loadOpcUaConf(string fileName)
+ {
+ string jsonFullPath = Path.Combine(configPath, fileName);
+ lgInfo($"Apertura file {jsonFullPath}");
+ using (StreamReader reader = new StreamReader(jsonFullPath))
+ {
+ string jsonData = reader.ReadToEnd().Replace("\n", "").Replace("\r", "");
+ if (!string.IsNullOrEmpty(jsonData))
+ {
+ lgDebug($"File json composto da {jsonData.Length} caratteri");
+ try
+ {
+ opcUaParams = JsonConvert.DeserializeObject(jsonData);
+ lgDebug($"Decodifica aree OpcUaParamConf: trovati {opcUaParams.paramsEndThresh.Count} valori paramsEndThresh");
+ // sistemo se ci sono dati memMap...
+ memMap = new plcMemMap();
+ if (opcUaParams.mMapWrite != null)
+ {
+ memMap.mMapWrite = opcUaParams.mMapWrite;
+ }
+ if (opcUaParams.mMapRead != null)
+ {
+ memMap.mMapRead = opcUaParams.mMapRead;
+ }
+ setupMemMap();
+ }
+ catch (Exception exc)
+ {
+ lgError($"Eccezione in decodifica conf json OPC-UA:{Environment.NewLine}{exc}");
+ }
+ }
+ else
+ {
+ lgError("Errore in loadOpcUaConf: file json vuoto!");
+ }
+ }
+ }
+
+ //#endregion Protected Methods
+
+ //#region Private Fields
+
+ ///
+ /// Elenco degli items da monitorare come risultato del browse iniziale
+ ///
+ private Dictionary selectedItemList = new Dictionary();
+
+ //#endregion Private Fields
+
+ //#region Private Methods
+
+ ///
+ /// Vera connessione ad OpcUa
+ ///
+ ///
+ private async Task doConnect()
+ {
+ short esitoLink = 0;
+ // reset memoria dataItem..
+ dataItemMem = new Dictionary();
+ // ora avvio
+ try
+ {
+ lgInfo("Start init OpcUa Client");
+ // Define the UA Client application
+ ApplicationInstance application = new ApplicationInstance();
+ application.ApplicationName = "EgalWare MP.MONO Adapter Client";
+ application.ApplicationType = ApplicationType.Client;
+
+ // load the application configuration.
+ string confPath = Path.Combine(Directory.GetCurrentDirectory(), "CONF","MpMonoAdapterClient.Config.xml");
+ await application.LoadApplicationConfiguration(confPath, silent: false).ConfigureAwait(false);
+ // check the application certificate.
+ await application.CheckApplicationInstanceCertificate(silent: false, minimumKeySize: 0).ConfigureAwait(false);
+
+ lgInfo($"New UAClient stasrted with std config: {application.ApplicationConfiguration.ApplicationName}");
+ UA_ref = new UAClient(application.ApplicationConfiguration, opcUaParams.Identity.UserName, opcUaParams.Identity.Passwd, ClientBase.ValidateResponse);
+
+ lgInfo($"Call to OpcUa Server at address: opc.tcp://{cIobConf.IpAddress}:{cIobConf.Port}");
+ UA_ref.ServerUrl = $"opc.tcp://{cIobConf.IpAddress}:{cIobConf.Port}";
+
+ var task = Task.Run(async () =>
+ {
+ return await UA_ref.ConnectAsync().ConfigureAwait(false);
+ });
+ bool connected = task.Result;
+ if (connected)
+ {
+ // faccio un primo browse dei dati...
+ Dictionary nodeIdNameList = new Dictionary();
+ if (!string.IsNullOrEmpty(opcUaParams.BrowseFullVal))
+ {
+ try
+ {
+ UA_ref.Browse(opcUaParams.BrowseFullVal, opcUaParams.filterItemsNodeId, ref nodeIdNameList);
+ }
+ catch
+ { }
+ }
+ else
+ {
+ UA_ref.Browse(opcUaParams.BrowseNSIndex, opcUaParams.BrowseValue, opcUaParams.filterItemsNodeId, ref nodeIdNameList);
+ }
+ // loggo elenco degli item sottocrivibili...
+ lgDebug("---------- AVAILABLE FOR SUBSCRIBE ----------");
+ foreach (var item in nodeIdNameList)
+ {
+ lgDebug(item.Key);
+ }
+ lgDebug("---------- END LIST ----------");
+
+ // se ho un insieme non vuoto degli item sottoscritti carico solo quelli
+ if (opcUaParams.subscribedItems != null && opcUaParams.subscribedItems.Count > 0)
+ {
+ // cerco e aggiungo SOLO quelle indicati
+ foreach (var currItem in opcUaParams.subscribedItems)
+ {
+ var foundItems = nodeIdNameList.Where(x => x.Key.Contains(currItem)).ToList();
+ if (foundItems != null && foundItems.Count > 0)
+ {
+ foreach (var fItem in foundItems)
+ {
+ // verifico di NON duplicare...
+ if (!selectedItemList.ContainsKey(fItem.Key))
+ {
+ selectedItemList.Add(fItem.Key, fItem.Value);
+ }
+ }
+ }
+ else
+ {
+ lgDebug($"subscribedItems non trovato: {currItem}");
+ }
+ }
+ lgDebug($"Aggiunti {selectedItemList.Count} items!");
+ }
+ // altrimenti tutti!
+ else
+ {
+ selectedItemList = nodeIdNameList;
+ }
+
+ // loggo elenco degli item sottocrivibili...
+ lgInfo("---------- SUBSCRIBED NODES ----------");
+ foreach (var item in selectedItemList)
+ {
+ lgInfo(item.Key);
+ }
+ lgInfo("---------- END LIST ----------");
+
+ // sottoscrivo a rilevazione cambio dati solo l'incrocio degli insiemi
+ List subscribedItems = UA_ref.SubscribeToDataChanges(selectedItemList);
+ // aggiungo come DataItems
+ int dSamplePeriod = 0;
+ int threshDBand = 0;
+ string uuid = "";
+ foreach (var item in subscribedItems)
+ {
+ bool changed = false;
+ OpcUaDataItemExt newItem = formatDataItem(ref dSamplePeriod, ref threshDBand, ref uuid, item);
+ // controllo non sia già stato aggiunto
+ if (dataItemMem.ContainsKey(uuid))
+ {
+ lgDebug($"Item ALREADY subscribed: {uuid} | NOT re-adding");
+ }
+ else
+ {
+ dataItemMem.Add(uuid, newItem);
+ lgDebug($"Item subscribed: {uuid}");
+ // verifico se ho i dati complessi by design
+ string currVal = "";
+ if (doByteRead)
+ {
+ // restituisce i 115 byte da deserializzare... qui HACK!
+ var rawVal = UA_ref.ReadNodeRaw(item.StartNodeId);
+ if (rawVal != null)
+ {
+ byteRawData = getByteRaw((DataValue)rawVal);
+ changed = checkAndSendRaw(item, byteRawData, true);
+ }
+ }
+ else
+ {
+ currVal = UA_ref.ReadNode(item.StartNodeId);
+ changed = checkAndSend(item, currVal, true);
+ }
+ }
+ }
+ // gestione eventi change
+ UA_ref.eh_MonItChange += UA_ref_eh_MonItChange;
+ lgInfo("eh_MonItChange event registered");
+ }
+
+ esitoLink = 1;
+
+ // fix tempi!
+ DateTime adesso = DateTime.Now;
+ lastCurrent = adesso;
+ }
+ catch (Exception exc)
+ {
+ lgError($"Eccezione in doConnect{Environment.NewLine}{exc}");
+ }
+ return esitoLink;
+ }
+
+ ///
+ /// Formatta un dataitem x salvataggio in memoria locale
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private OpcUaDataItemExt formatDataItem(ref int dSamplePeriod, ref int threshDBand, ref string uuid, Opc.Ua.Client.MonitoredItem dataItem)
+ {
+ OpcUaDataItemExt currDataItem;
+ // calcolo parametri
+ uuid = $"{dataItem.DisplayName}";
+ // SOLO SE è abilitato il datafiltering...
+ if (enableDataFilter)
+ {
+ threshDBand = 1;
+ // controllo SE ho conf x deadband...
+ if (opcUaParams.paramsEndThresh.Count > 0)
+ {
+ // ciclo su tutti i parametri indicati...
+ foreach (var item in opcUaParams.paramsEndThresh)
+ {
+ if (uuid.EndsWith(item.Key))
+ {
+ threshDBand = item.Value;
+ }
+ }
+ }
+ }
+ else
+ {
+ threshDBand = 0;
+ }
+ dSamplePeriod = 60;
+
+ // salvo oggetto x "uso interno"
+ currDataItem = new OpcUaDataItemExt(dataItem)
+ {
+ uid = uuid,
+ thresholdDeadBand = threshDBand,
+ samplePeriod = dSamplePeriod
+ };
+
+ return currDataItem;
+ }
+
+ ///
+ /// Effettua invio a MP/IO dell'elenco serializzato dei dataItems
+ ///
+ ///
+ private void sendDataItemsList(List dataItems)
+ {
+ // fixme todo fare !!!
+ // da eliminare che forse avanza?
+#if false
+ string rawData = JsonConvert.SerializeObject(dataItems);
+ try
+ {
+ utils.callUrlNow($"{urlSaveDataItems}", rawData);
+ lgInfo($"Effettuata chiamata sendDataItemsList all'url {urlSaveDataItems}");
+ }
+ catch (Exception exc)
+ {
+ lgError($"Eccezione in sendDataItemsList{Environment.NewLine} - url: {urlSaveDataItems}{Environment.NewLine}- payload:{rawData}{Environment.NewLine}Eccezione:{Environment.NewLine}{exc}");
+ }
+#endif
+ }
+
+ //#endregion Private Methods
+
+ }
+}
diff --git a/MP.MONO.ADAPTER.OPC/MP.MONO.ADAPTER.OPC.csproj b/MP.MONO.ADAPTER.OPC/MP.MONO.ADAPTER.OPC.csproj
index b4a2e95..c9139d8 100644
--- a/MP.MONO.ADAPTER.OPC/MP.MONO.ADAPTER.OPC.csproj
+++ b/MP.MONO.ADAPTER.OPC/MP.MONO.ADAPTER.OPC.csproj
@@ -7,13 +7,23 @@
MP.MONO.ADAPTER.OPC
EgalWare
OPC UA Console Client
- Copyright © 2020+ EgalWare
+ Copyright � 2020+ EgalWare
MP.MONO.ADAPTER.OPC
+
+
+
+
+
+
+
+
+
+
@@ -24,9 +34,20 @@
+
+
+
+
+
-
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
PreserveNewest
diff --git a/MP.MONO.ADAPTER.OPC/OpcUaDataItemExt.cs b/MP.MONO.ADAPTER.OPC/OpcUaDataItemExt.cs
new file mode 100644
index 0000000..0f04617
--- /dev/null
+++ b/MP.MONO.ADAPTER.OPC/OpcUaDataItemExt.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MP.MONO.ADAPTER.OPC
+{
+ ///
+ /// Classe di estensione x oggetti DataItems OPC-UA con struttura e valori
+ ///
+ public class OpcUaDataItemExt : Opc.Ua.Client.MonitoredItem
+ {
+ #region Public Constructors
+
+ public OpcUaDataItemExt(Opc.Ua.Client.MonitoredItem MonIt)
+ {
+ if (MonIt != null)
+ {
+ AttributeId = MonIt.AttributeId;
+ CacheQueueSize = MonIt.CacheQueueSize;
+ Handle = MonIt.Handle;
+ DiscardOldest = MonIt.DiscardOldest;
+ DisplayName = MonIt.DisplayName;
+ Encoding = MonIt.Encoding;
+ Filter = MonIt.Filter;
+ IndexRange = MonIt.IndexRange;
+ MonitoringMode = MonIt.MonitoringMode;
+ NodeClass = MonIt.NodeClass;
+ QueueSize = MonIt.QueueSize;
+ RelativePath = MonIt.RelativePath;
+ SamplingInterval = MonIt.SamplingInterval;
+ StartNodeId = MonIt.StartNodeId;
+ }
+ }
+
+ #endregion Public Constructors
+
+ #region Public Properties
+
+ ///
+ /// NodeId univoco dell'oggetto x subscription (StartNodeId)
+ ///
+ public string nodeId { get; set; } = "";
+
+ ///
+ /// Valore (in sec) del periodo di downsampling (0 --> NON usato)
+ ///
+ public int samplePeriod { get; set; } = 60;
+
+ ///
+ /// Valore soglia DeadBand (0 --> non usata)
+ ///
+ public double thresholdDeadBand { get; set; } = 0;
+
+ ///
+ /// UUID univoco dell'oggetto --> flusso (DisplayName)
+ ///
+ public string uid { get; set; } = "";
+
+ ///
+ /// Valore Registrato in formato stringa
+ ///
+ public string value { get; set; } = "";
+
+ ///
+ /// Valore Registrato in formato byte array
+ ///
+ public byte[] rawByte { get; set; } = new byte[1];
+
+ ///
+ /// Timestamp data-ora evento registrato
+ ///
+ public DateTime valueTimestamp { get; set; } = DateTime.Now;
+
+ #endregion Public Properties
+ }
+}
diff --git a/MP.MONO.ADAPTER.OPC/Program.cs b/MP.MONO.ADAPTER.OPC/Program.cs
index a949e0d..239afa5 100644
--- a/MP.MONO.ADAPTER.OPC/Program.cs
+++ b/MP.MONO.ADAPTER.OPC/Program.cs
@@ -1,32 +1,3 @@
-/* ========================================================================
- * Copyright (c) 2005-2021 The OPC Foundation, Inc. All rights reserved.
- *
- * OPC Foundation MIT License 1.00
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use,
- * copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following
- * conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
- *
- * The complete license agreement can be found here:
- * http://opcfoundation.org/License/MIT/1.00/
- * ======================================================================*/
-
using System;
using System.Globalization;
using System.IO;
@@ -35,6 +6,15 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Opc.Ua;
using Opc.Ua.Configuration;
+using Microsoft.Extensions.Configuration;
+using MP.MONO.ADAPTER;
+using MP.MONO.Core;
+using MP.MONO.Core.CONF;
+using MP.MONO.Core.DTO;
+using Newtonsoft.Json;
+using NLog;
+using StackExchange.Redis;
+using System.Collections.Generic;
namespace MP.MONO.ADAPTER.OPC
{
@@ -43,22 +23,85 @@ namespace MP.MONO.ADAPTER.OPC
///
public static class Program
{
+ private static string redisConf = "";
+ private static string confPath = "";
+ private static string lineSep = "---------------------------------------------";
+ private static Logger Log = LogManager.GetCurrentClassLogger();
+ private static bool verboseLog = false;
+ private static bool logWriting = false;
+
+ ///
+ /// Redis channel manager
+ ///
+ private static ISubscriber sub { get; set; }
+
+ ///
+ /// Redis DB
+ ///
+ private static IDatabase? redisDb { get; set; }
+
+ private static Dictionary LogSimulator = new Dictionary();
+ private static Dictionary LastSend = new Dictionary();
+ private static DateTime lastLog = DateTime.Now.AddMinutes(-1);
+
///
/// Main entry point.
///
public static async Task Main(string[] args)
{
TextWriter output = Console.Out;
- output.WriteLine("OPC UA Console Reference Client");
+ output.WriteLine("Egalware | MP.MONO.ADAPTER | OPC UA Console Client");
output.WriteLine("OPC UA library: {0} @ {1} -- {2}",
Utils.GetAssemblyBuildNumber(),
Utils.GetAssemblyTimestamp().ToString("G", CultureInfo.InvariantCulture),
Utils.GetAssemblySoftwareVersion());
+
+ // init parte config, vedere https://blog.hildenco.com/2020/05/configuration-in-net-core-console.html
+ var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
+ var builder = new ConfigurationBuilder()
+ .AddJsonFile($"appsettings.json", true, true)
+ .AddJsonFile($"appsettings.{env}.json", true, true)
+ .AddEnvironmentVariables();
+ var config = builder.Build();
+
+ // imposto variabili di base
+ redisConf = config.GetConnectionString("Redis");
+ confPath = Path.Combine(Directory.GetCurrentDirectory(), "conf");
+ Random rand = new Random();
+ List? statusList = new List();
+ List? modeList = new List();
+
+ // fix numero minimo dei thread pool x evitare collasso chiamate redis
+ ThreadPool.SetMinThreads(10, 10);
+
+
+
+ logInfo(lineSep, true, true);
+ logInfo($"Starting Machine ADAPTER", true, true);
+ logInfo($"Redis server param: {redisConf.Substring(0, 20)}...", false, true);
+ logInfo(lineSep, true, true);
+ logInfo("", true, true);
+
+ // Setup REDIS
+ ConnectionMultiplexer.SetFeatureFlag("preventthreadtheft", true);
+ ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(redisConf);
+ sub = redis.GetSubscriber();
+ redisDb = redis.GetDatabase();
+
+ // salvo configurazioni in redis
+ setupConf();
+
+ // avvio il vero e proprio programma di comunicaizone OPC-UA
+ var currIob = new IobOpcUa(confPath, config);
+
+ logInfo($"Started IOB at path {confPath}", true, true);
+
+#if false
// The application name and config file names
- var applicationName = "ConsoleReferenceClient";
- var configSectionName = "Quickstarts.ReferenceClient";
+ var applicationName = "MP.MONO.ADAPTER.OPC";
+ var configSectionName = "MpMonoAdapterOpc";
var usage = $"Usage: dotnet {applicationName}.dll [OPTIONS]";
// command line options
@@ -102,7 +145,8 @@ namespace MP.MONO.ADAPTER.OPC
// Define the UA Client application
ApplicationInstance.MessageDlg = new ApplicationMessageDlg(output);
CertificatePasswordProvider PasswordProvider = new CertificatePasswordProvider(password);
- ApplicationInstance application = new ApplicationInstance {
+ ApplicationInstance application = new ApplicationInstance
+ {
ApplicationName = applicationName,
ApplicationType = ApplicationType.Client,
ConfigSectionName = configSectionName,
@@ -113,7 +157,7 @@ namespace MP.MONO.ADAPTER.OPC
var config = await application.LoadApplicationConfiguration(silent: false);
// setup the logging
- ConsoleUtils.ConfigureLogging(config, applicationName, logConsole, LogLevel.Information);
+ ConsoleUtils.ConfigureLogging(config, applicationName, logConsole, Microsoft.Extensions.Logging.LogLevel.Information);
// delete old certificate
if (renewCertificate)
@@ -147,7 +191,8 @@ namespace MP.MONO.ADAPTER.OPC
}
// create the UA Client object and connect to configured server.
- UAClient uaClient = new UAClient(application.ApplicationConfiguration, output, ClientBase.ValidateResponse) {
+ UAClient uaClient = new UAClient(application.ApplicationConfiguration, output, ClientBase.ValidateResponse)
+ {
AutoAccept = autoAccept
};
@@ -180,6 +225,208 @@ namespace MP.MONO.ADAPTER.OPC
catch (Exception ex)
{
output.WriteLine(ex.Message);
+ }
+#endif
+
+ logInfo("Running - press CTRL-C to stop ADAPTER", false, true);
+ logInfo("", false, true);
+ // Ciclo infinito x attesa chiusura con CTRL-C
+ do
+ {
+ // se non fosse connesso... riprovo la connessione...
+ if (!currIob.connectionOk)
+ {
+ currIob.tryConnect();
+ }
+ // attesa...
+ Thread.Sleep(100);
+ } while (true);
+
+ }
+
+ ///
+ /// verifica esistenza file oppure lo crea...
+ ///
+ private static void checkFilePresent(string filePath)
+ {
+ // verific presenza file log...
+ if (!File.Exists(filePath))
+ {
+ File.WriteAllText(filePath, $"{filePath} created!");
+ }
+ }
+
+ ///
+ /// Setup e salvataggio redis delle conf (es modi/stati)
+ ///
+ private static void setupConf()
+ {
+#if false
+ // leggo e salvo conf stati
+ string fullPath = Path.Combine(confPath, "StatusList.json");
+ if (File.Exists(fullPath))
+ {
+ var rawData = File.ReadAllText(fullPath);
+ if (!string.IsNullOrEmpty(rawData))
+ {
+ List? statusList = JsonConvert.DeserializeObject>(rawData);
+ // salvo in redis!
+ redisDb.StringSetAsync(Constants.STATUS_CONF_KEY, JsonConvert.SerializeObject(statusList));
+ }
+ }
+
+ // leggo e salvo conf modi
+ fullPath = Path.Combine(confPath, "ModeList.json");
+ if (File.Exists(fullPath))
+ {
+ var rawData = File.ReadAllText(fullPath);
+ if (!string.IsNullOrEmpty(rawData))
+ {
+ var localObj = JsonConvert.DeserializeObject>(rawData);
+ // salvo in redis!
+ redisDb.StringSetAsync(Constants.MODE_CONF_KEY, JsonConvert.SerializeObject(localObj));
+ }
+ }
+
+ // leggo e salvo conf allarmi
+ fullPath = Path.Combine(confPath, "AlarmList.json");
+ if (File.Exists(fullPath))
+ {
+ var rawData = File.ReadAllText(fullPath);
+ if (!string.IsNullOrEmpty(rawData))
+ {
+ var localObj = JsonConvert.DeserializeObject>(rawData);
+ if (localObj != null)
+ {
+ // sistemo allarmi
+ foreach (var item in localObj)
+ {
+ item.setupData();
+ // loggo
+ logInfo($"Decodifica aree alarmMap: {item.description} | {item.memAddr} x {item.size} byte | {item.messages.Count} messaggi allarme", true, true);
+ }
+ }
+ // salvo in redis!
+ redisDb.StringSetAsync(Constants.ALARMS_CONF_KEY, JsonConvert.SerializeObject(localObj));
+ }
+ }
+
+ // leggo e salvo conf parametri
+ fullPath = Path.Combine(confPath, "ParamList.json");
+ if (File.Exists(fullPath))
+ {
+ var rawData = File.ReadAllText(fullPath);
+ if (!string.IsNullOrEmpty(rawData))
+ {
+ var localObj = JsonConvert.DeserializeObject>(rawData);
+ // salvo in redis!
+ redisDb.StringSetAsync(Constants.PARAMS_CONF_KEY, JsonConvert.SerializeObject(localObj));
+ }
+ }
+#endif
+
+ ConfigManager configManager = new ConfigManager(redisConf, confPath);
+ _ = configManager.getAlarmsConf();
+ _ = configManager.getMachineModeConf();
+ _ = configManager.getMachineStatusConf();
+ _ = configManager.getParamsConf();
+ }
+
+ ///
+ /// Effettua log INFO su file e se richiesto su console
+ ///
+ private static void logInfo(string msg, bool log2file = true, bool log2console = false)
+ {
+ if (log2console)
+ {
+ Console.WriteLine(msg);
+ }
+ if (log2file)
+ {
+ Log.Info(msg);
+ }
+ }
+ ///
+ /// Effettua log ERROR su file e se richiesto su console
+ ///
+ private static void logError(string msg, bool log2file = true, bool log2console = false)
+ {
+ if (log2console)
+ {
+ Console.WriteLine(msg);
+ }
+ if (log2file)
+ {
+ Log.Error(msg);
+ }
+ }
+
+ private static void saveAndSendMessage(string memKey, string value, string notifyChannel, string message)
+ {
+ // effettuo la scrittura nell'area di memoria indicata SE passato intervallo minimo
+ bool doSend = true;
+ if (LastSend.ContainsKey(memKey))
+ {
+ if (DateTime.Now.Subtract(LastSend[memKey]).TotalSeconds < 60)
+ {
+ doSend = false;
+ }
+ }
+ else
+ {
+ LastSend.Add(memKey, DateTime.Now);
+ }
+ if (doSend)
+ {
+ redisDb.StringSetAsync(memKey, value);
+ LastSend[memKey] = DateTime.Now;
+ logInfo($"Redis Cache Key: {memKey}");
+ }
+ //redisDb.SetAdd(memKey, value);
+
+ // invio notifica tramite il canale richiesto
+ sub.Publish(notifyChannel, message);
+ if (verboseLog)
+ {
+ logInfo($"[{notifyChannel}] key: {memKey} | val: {value} | message: {message}");
+ }
+ else
+ {
+ try
+ {
+ if (!logWriting)
+ {
+ if (LogSimulator.ContainsKey(notifyChannel))
+ {
+ LogSimulator[notifyChannel]++;
+ }
+ else
+ {
+ LogSimulator.Add(notifyChannel, 1);
+ }
+ logWriting = true;
+ // vedo se loggare...
+ DateTime adesso = DateTime.Now;
+ if (adesso.Subtract(lastLog).TotalSeconds > 15)
+ {
+ lastLog = adesso;
+ logInfo(lineSep);
+
+ // lavoro su copia...
+ var LogSimulatorCopy = new Dictionary(LogSimulator);
+ foreach (var item in LogSimulatorCopy)
+ {
+ logInfo($"Redis mQueue {item.Key,-20}{item.Value,12}");
+ }
+ logInfo(lineSep);
+ }
+ logWriting = false;
+ }
+ }
+ catch (Exception ex)
+ {
+ logError($"ERROR{Environment.NewLine}{ex}");
+ }
}
}
}
diff --git a/MP.MONO.ADAPTER.OPC/UAClient.cs b/MP.MONO.ADAPTER.OPC/UAClient.cs
index 3b815f5..d43ac4e 100644
--- a/MP.MONO.ADAPTER.OPC/UAClient.cs
+++ b/MP.MONO.ADAPTER.OPC/UAClient.cs
@@ -1,93 +1,315 @@
-/* ========================================================================
- * Copyright (c) 2005-2020 The OPC Foundation, Inc. All rights reserved.
- *
- * OPC Foundation MIT License 1.00
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use,
- * copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following
- * conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
- *
- * The complete license agreement can be found here:
- * http://opcfoundation.org/License/MIT/1.00/
- * ======================================================================*/
-
+using NLog;
+using Opc.Ua;
+using Opc.Ua.Client;
using System;
using System.Collections;
using System.Collections.Generic;
-using System.IO;
using System.Threading.Tasks;
-using Opc.Ua;
-using Opc.Ua.Client;
namespace MP.MONO.ADAPTER.OPC
{
+ ///
+ /// 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.
///
- class UAClient
+ public class UAClient
{
- #region Constructors
+ #region Public Constructors
+
///
/// Initializes a new instance of the UAClient class.
///
- public UAClient(ApplicationConfiguration configuration, TextWriter writer, Action validateResponse)
+ public UAClient(ApplicationConfiguration configuration, string user, string pwd, Action validateResponse)
{
m_validateResponse = validateResponse;
- m_output = writer;
+ lg = LogManager.GetCurrentClassLogger();
+ if (!string.IsNullOrEmpty(user) && !string.IsNullOrEmpty(pwd))
+ {
+ CurrUserIdentity = new UserIdentity(user, pwd);
+ }
+ else
+ {
+ CurrUserIdentity = new UserIdentity();
+ }
m_configuration = configuration;
m_configuration.CertificateValidator.CertificateValidation += CertificateValidation;
}
- #endregion
+
+ #endregion Public Constructors
+
+ #region Public Events
+
+ ///
+ /// Evento notifica variazione MonitoredItem
+ ///
+ public event EventHandler eh_MonItChange;
+
+ #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;
- ///
- /// Auto accept untrusted certificates.
- ///
- public bool AutoAccept { get; set; } = false;
- #endregion
+ #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)
+ {
+ lg.Error("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
+ lg.Trace($"Browsing {nodeToBrowse} node...");
+ ReferenceDescriptionCollection browseResults = browser.Browse(nodeToBrowse);
+
+ // Display the results
+ lg.Trace($"Browse returned {browseResults.Count} results:");
+
+ foreach (ReferenceDescription result in browseResults)
+ {
+ lg.Trace($" 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
+ lg.Error($"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)
+ {
+ lg.Error("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
+ lg.Trace($"Browsing {nodeToBrowse} node...");
+ ReferenceDescriptionCollection browseResults = browser.Browse(nodeToBrowse);
+
+ // Display the results
+ lg.Trace($"Browse returned {browseResults.Count} results:");
+
+ foreach (ReferenceDescription result in browseResults)
+ {
+ // se veto --> loggo veto
+ if (vetoBrowse.Contains($"{result.NodeId}"))
+ {
+ lg.Trace($"| FILTERED --> 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
+ {
+ lg.Trace($" 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
+ lg.Error($"Browse Error : {ex.Message}");
+ }
+
+ return fatto;
+ }
+
+ ///
+ /// Call UA method
+ ///
+ public void CallMethod()
+ {
+ if (m_session == null || m_session.Connected == false)
+ {
+ lg.Error("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