2767 lines
118 KiB
C#
2767 lines
118 KiB
C#
using IOB_UT_NEXT;
|
|
using MapoSDK;
|
|
using Newtonsoft.Json;
|
|
using NLog.Time;
|
|
using Opc.Ua;
|
|
using Opc.Ua.Client;
|
|
using Opc.Ua.Configuration;
|
|
using S7.Net.Types;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net.NetworkInformation;
|
|
using System.Security.AccessControl;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
//using System.Web.Util;
|
|
using System.Windows.Forms;
|
|
using static IOB_UT_NEXT.ByteDataConverter;
|
|
|
|
namespace IOB_WIN_NEXT.IobOpc
|
|
{
|
|
public class OpcUa : Iob.GenericNext
|
|
{
|
|
#region Public Constructors
|
|
|
|
/// <summary>
|
|
/// Estende l'init della classe base, impiegando il pacchetto Nuget OPC-UA foundation https://github.com/OPCFoundation/UA-.NETStandard
|
|
/// </summary>
|
|
/// <param name="caller"></param>
|
|
/// <param name="IOBConf"></param>
|
|
public OpcUa(AdapterFormNext caller, IobConfiguration IOBConf) : base(caller, IOBConf)
|
|
{
|
|
lgInfo("Init OpcUa");
|
|
// gestione invio ritardato contapezzi
|
|
pzCountDelay = utils.CRI("pzCountDelay");
|
|
|
|
// inizializzo...
|
|
subscribedItems = new List<Opc.Ua.Client.MonitoredItem>();
|
|
|
|
// gestione restart OpcUa client...
|
|
if (!string.IsNullOrEmpty(getOptPar("MAX_TRY_PING")))
|
|
{
|
|
int.TryParse(getOptPar("MAX_TRY_PING"), out maxTryPing);
|
|
}
|
|
|
|
enablePzCountByApp = utils.CRB("enableContapezzi");
|
|
enablePzCountByIob = (getOptPar("ENABLE_PZCOUNT") == "TRUE");
|
|
disablePzCountByIob = (getOptPar("DISABLE_PZCOUNT") == "TRUE");
|
|
|
|
// gestione data filtering...
|
|
if (!string.IsNullOrEmpty(getOptPar("ENABLE_DATA_FILTER")))
|
|
{
|
|
bool.TryParse(getOptPar("ENABLE_DATA_FILTER"), out enableDataFilter);
|
|
}
|
|
// gestione restart OpcUa client...
|
|
if (!string.IsNullOrEmpty(getOptPar("ENABLE_CLI_RESTART")))
|
|
{
|
|
bool.TryParse(getOptPar("ENABLE_CLI_RESTART"), out enableCliRestart);
|
|
}
|
|
// init lastCurrentMaxElapsed
|
|
if (!string.IsNullOrEmpty(getOptPar("MAX_ELAPSED_TIME_SEC")))
|
|
{
|
|
int.TryParse(getOptPar("MAX_ELAPSED_TIME_SEC"), out lastCurrentMaxElapsed);
|
|
}
|
|
|
|
// init slow data
|
|
string ENABLE_SLOW_DATA = getOptPar("ENABLE_SLOW_DATA");
|
|
if (!string.IsNullOrEmpty(ENABLE_SLOW_DATA))
|
|
{
|
|
bool.TryParse(getOptPar("ENABLE_SLOW_DATA"), out enableSlowData);
|
|
}
|
|
|
|
// init sample period
|
|
string BASE_SAMPLE_PERIOD = getOptPar("BASE_SAMPLE_PERIOD");
|
|
if (!string.IsNullOrEmpty(BASE_SAMPLE_PERIOD))
|
|
{
|
|
int.TryParse(getOptPar("BASE_SAMPLE_PERIOD"), out samplePeriodBase);
|
|
}
|
|
// init gestione reset errori lettura
|
|
if (!string.IsNullOrEmpty(getOptPar("READ_ERROR_MAX")))
|
|
{
|
|
int.TryParse(getOptPar("READ_ERROR_MAX"), out readErrorMax);
|
|
}
|
|
if (!string.IsNullOrEmpty(getOptPar("READ_ERROR_SLEEP_TIME")))
|
|
{
|
|
int.TryParse(getOptPar("READ_ERROR_SLEEP_TIME"), out readErrorSleepTime);
|
|
}
|
|
|
|
// init datetime counters
|
|
DateTime adesso = DateTime.Now;
|
|
lastPzCountSend = adesso;
|
|
lastWarnODL = adesso;
|
|
lastCurrent = adesso;
|
|
lgTrace($"Aggiornato IobOpcUa.lastCurrent: {lastCurrent}");
|
|
// ora leggo il file di conf specifico....
|
|
string jsonFileName = getOptPar("OPC_PARAM_CONF");
|
|
if (!string.IsNullOrEmpty(jsonFileName))
|
|
{
|
|
// leggo il file...
|
|
loadOpcUaConf(jsonFileName);
|
|
}
|
|
}
|
|
|
|
#endregion Public Constructors
|
|
|
|
#region Public Methods
|
|
|
|
/// <summary>
|
|
/// Processo i task richiesti e li elimino dalla coda 1:1
|
|
/// </summary>
|
|
/// <param name="task2exe"></param>
|
|
public override Dictionary<string, string> executeTasks(Dictionary<string, string> task2exe)
|
|
{
|
|
// uso metodo base x ora
|
|
return base.executeTasks(task2exe);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua conversione tra gli allarmi attivi secondo configurazione banco allarmi
|
|
/// </summary>
|
|
/// <param name="memConf">Configurazione banco allarmi</param>
|
|
/// <param name="numAlarms">num allarmi attivi</param>
|
|
/// <returns></returns>
|
|
public override string getAlarmState(BaseAlarmConf memConf, int numAlarms)
|
|
{
|
|
string valTransl = "";
|
|
List<string> descAttivi = new List<string>();
|
|
// dato il num di allarmi leggo i vari messaggi...
|
|
int numRead = numAlarms <= memConf.activeKeys.Count() ? numAlarms : memConf.activeKeys.Count();
|
|
for (int i = 0; i < numAlarms; i++)
|
|
{
|
|
descAttivi.Add(getDataItemValue(memConf.activeKeys[i]));
|
|
}
|
|
// combino string
|
|
valTransl = string.Join(",", descAttivi);
|
|
return valTransl;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recupera uno specifico dataItem
|
|
/// </summary>
|
|
/// <param name="diKey"></param>
|
|
/// <returns></returns>
|
|
public string getDataItemValue(string diKey)
|
|
{
|
|
int currCheckErrors = 0;
|
|
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)
|
|
{
|
|
currCheckErrors++;
|
|
lgTrace($"Errore in getDataItemValue per {diKey} | dataItemMem NON contiene valori");
|
|
if (vetoCheckStatus < adesso)
|
|
{
|
|
lgError($"Errore in getDataItemValue per {diKey} | dataItemMem NON contiene valori");
|
|
// imposto veto per vetoSeconds...
|
|
vetoCheckStatus = adesso.AddSeconds(vetoSeconds * 2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (diKey.Contains(opcUaParams.kvDelim))
|
|
{
|
|
int iPos = diKey.IndexOf(opcUaParams.kvDelim);
|
|
string kRef = diKey.Substring(0, iPos);
|
|
string varDef = diKey.Substring(iPos + 1);
|
|
// recupero byte
|
|
byte[] bArray = getDataItemValueBytes(kRef);
|
|
answ = ConvFromBArr(bArray, varDef, opcUaParams.memDelim);
|
|
}
|
|
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");
|
|
lgTrace($"Esempio valori (first-last): ");
|
|
lgTrace($"{dataItemMem.First().Key}");
|
|
lgTrace($"{dataItemMem.Last().Key}");
|
|
// imposto veto per vetoSeconds...
|
|
vetoCheckStatus = adesso.AddSeconds(vetoSeconds * 2);
|
|
}
|
|
currCheckErrors++;
|
|
}
|
|
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 ho errori sommo, sennò abbasso numero check...
|
|
if (currCheckErrors > 0)
|
|
{
|
|
numErroriCheck += currCheckErrors;
|
|
lgDebug($"currCheckErrors: {currCheckErrors} | numErroriCheck: {numErroriCheck}");
|
|
}
|
|
else
|
|
{
|
|
numErroriCheck = numErroriCheck > 0 ? numErroriCheck-- : 0;
|
|
}
|
|
// se supero soglia errori lettura --> disconnetto e resetto
|
|
if (numErroriCheck > maxErroriCheck)
|
|
{
|
|
lgInfo($"numErroriCheck: {numErroriCheck} --> richiesta disconnessione adapter con tryDisconnect");
|
|
|
|
numErroriCheck = 0;
|
|
tryDisconnect();
|
|
}
|
|
}
|
|
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recupera valore OPC-UA di un singolo byte da un byte[]
|
|
/// </summary>
|
|
/// <param name="diKey"></param>
|
|
/// <param name="bitNum"></param>
|
|
/// <param name="vTrue">Valore da restituire se true, default "1"</param>
|
|
/// <param name="vFalse">Valore da restituire se false, default "0"</param>
|
|
/// <returns>Restituisce string "1" / "0" secondo true/false</returns>
|
|
public string getDataItemValueBit(string diKey, int bitNum, string vTrue = "1", string vFalse = "0")
|
|
{
|
|
string answ = "";
|
|
byte[] rawValue = new byte[1];
|
|
bool testBit = false;
|
|
int currCheckErrors = 0;
|
|
if (string.IsNullOrEmpty(diKey))
|
|
{
|
|
lgError($"Attenzione: richiesta chiave vuota in getDataItemValueBit{Environment.NewLine}StackTrace: {Environment.StackTrace}");
|
|
}
|
|
else
|
|
{
|
|
DateTime adesso = DateTime.Now;
|
|
if (dataItemMem.Count == 0)
|
|
{
|
|
currCheckErrors++;
|
|
lgTrace($"Errore in getDataItemValueBit per {diKey} | dataItemMem NON contiene valori");
|
|
if (vetoCheckStatus < adesso)
|
|
{
|
|
lgError($"Errore in getDataItemValueBit 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 getDataItemValueBit per {diKey} | dataItemMem non contiene la chiave richiesta ma altri {dataItemMem.Count} valori");
|
|
lgTrace($"Esempio valori (first-last): ");
|
|
lgTrace($"{dataItemMem.First().Key}");
|
|
lgTrace($"{dataItemMem.Last().Key}");
|
|
// imposto veto per vetoSeconds...
|
|
vetoCheckStatus = adesso.AddSeconds(vetoSeconds * 2);
|
|
}
|
|
currCheckErrors++;
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
// MAIN TEST
|
|
var currDataItem = dataItemMem[diKey];
|
|
rawValue = currDataItem.rawByte;
|
|
if (rawValue.Length > bitNum)
|
|
{
|
|
testBit = rawValue[bitNum] == 1;
|
|
}
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError($"Errore in getDataItemValueBit 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 ho errori sommo, sennò abbasso numero check...
|
|
if (currCheckErrors > 0)
|
|
{
|
|
numErroriCheck += currCheckErrors;
|
|
lgDebug($"currCheckErrors: {currCheckErrors} | numErroriCheck: {numErroriCheck}");
|
|
}
|
|
else
|
|
{
|
|
numErroriCheck = numErroriCheck > 0 ? numErroriCheck-- : 0;
|
|
}
|
|
// se supero soglia errori lettura --> disconnetto e resetto
|
|
if (numErroriCheck > maxErroriCheck)
|
|
{
|
|
lgInfo($"numErroriCheck: {numErroriCheck} --> richiesta disconnessione adapter con tryDisconnect");
|
|
|
|
numErroriCheck = 0;
|
|
tryDisconnect();
|
|
}
|
|
}
|
|
answ = testBit ? vTrue : vFalse;
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recupera valore OPC-UA come array di byte[] x successivo processing
|
|
/// </summary>
|
|
/// <param name="diKey">Chiave da recuperare</param>
|
|
/// <returns>Restituisce string "1" / "0" secondo true/false</returns>
|
|
public byte[] getDataItemValueBytes(string diKey)
|
|
{
|
|
byte[] answ = new byte[1];
|
|
byte[] rawValue = new byte[8];
|
|
int currCheckErrors = 0;
|
|
if (string.IsNullOrEmpty(diKey))
|
|
{
|
|
lgError($"Attenzione: richiesta chiave vuota in getDataItemValueBytes{Environment.NewLine}StackTrace: {Environment.StackTrace}");
|
|
}
|
|
else
|
|
{
|
|
DateTime adesso = DateTime.Now;
|
|
if (dataItemMem.Count == 0)
|
|
{
|
|
currCheckErrors++;
|
|
lgTrace($"Errore in getDataItemValueBytes per {diKey} | dataItemMem NON contiene valori");
|
|
if (vetoCheckStatus < adesso)
|
|
{
|
|
lgError($"Errore in getDataItemValueBytes 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 getDataItemValueBytes per {diKey} | dataItemMem non contiene la chiave richiesta ma altri {dataItemMem.Count} valori");
|
|
lgTrace($"Esempio valori (first-last): ");
|
|
lgTrace($"{dataItemMem.First().Key}");
|
|
lgTrace($"{dataItemMem.Last().Key}");
|
|
// imposto veto per vetoSeconds...
|
|
vetoCheckStatus = adesso.AddSeconds(vetoSeconds * 2);
|
|
}
|
|
currCheckErrors++;
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
// MAIN TEST
|
|
var currDataItem = dataItemMem[diKey];
|
|
answ = currDataItem.rawByte;
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError($"Errore in getDataItemValueBytes 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 ho errori sommo, sennò abbasso numero check...
|
|
if (currCheckErrors > 0)
|
|
{
|
|
numErroriCheck += currCheckErrors;
|
|
lgDebug($"currCheckErrors: {currCheckErrors} | numErroriCheck: {numErroriCheck}");
|
|
}
|
|
else
|
|
{
|
|
numErroriCheck = numErroriCheck > 0 ? numErroriCheck-- : 0;
|
|
}
|
|
// se supero soglia errori lettura --> disconnetto e resetto
|
|
if (numErroriCheck > maxErroriCheck)
|
|
{
|
|
lgInfo($"numErroriCheck: {numErroriCheck} --> richiesta disconnessione adapter con tryDisconnect");
|
|
|
|
numErroriCheck = 0;
|
|
tryDisconnect();
|
|
}
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recupera valore OPC-UA direttamente in formato double
|
|
/// </summary>
|
|
/// <param name="diKey"></param>
|
|
/// <returns></returns>
|
|
public double getDataItemValueDouble(string diKey)
|
|
{
|
|
double answ = 0;
|
|
string rawVal = getDataItemValue(diKey);
|
|
if (rawVal != null)
|
|
{
|
|
double.TryParse(diKey, out answ);
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recupero dati dinamici...
|
|
/// </summary>
|
|
public override Dictionary<string, string> getDynData()
|
|
{
|
|
Dictionary<string, string> outVal = new Dictionary<string, string>();
|
|
return outVal;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua vero processing contapezzi
|
|
/// </summary>
|
|
public override void processContapezzi()
|
|
{
|
|
if ((enablePzCountByApp || enablePzCountByIob) && !(disablePzCountByIob))
|
|
{
|
|
if (string.IsNullOrEmpty(opcUaParams.keyPartCount))
|
|
{
|
|
lgTrace("Gestione contapezzi: manca parametro per keyPartCount");
|
|
}
|
|
else
|
|
{
|
|
// cerco parametro contapezzi... gest alternativa se è un parametro SPECIALE
|
|
// (con "#")
|
|
string currPzCount = "";
|
|
if (opcUaParams.keyPartCount.Contains(opcUaParams.kvDelim))
|
|
{
|
|
int iPos = opcUaParams.keyPartCount.IndexOf(opcUaParams.kvDelim);
|
|
string kRef = opcUaParams.keyPartCount.Substring(0, iPos);
|
|
string varDef = opcUaParams.keyPartCount.Substring(iPos + 1);
|
|
// recupero byte
|
|
byte[] bArray = getDataItemValueBytes(kRef);
|
|
currPzCount = ConvFromBArr(bArray, varDef, opcUaParams.memDelim);
|
|
}
|
|
else
|
|
{
|
|
currPzCount = getDataItemValue(opcUaParams.keyPartCount);
|
|
}
|
|
|
|
// se ho un contapezzi... processo...
|
|
if (!string.IsNullOrEmpty(currPzCount))
|
|
{
|
|
int newVal = -1;
|
|
bool fatto = Int32.TryParse(currPzCount, out newVal);
|
|
|
|
if (fatto)
|
|
{
|
|
// gestione decremento contapezzi: viene "messo via" solo SE c'è un
|
|
// effettivo decremento contapezzi...
|
|
if (newVal < contapezziPLC)
|
|
{
|
|
pzCountResetted = true;
|
|
// incremento contatore richiesta
|
|
countKeyRichiesta = countKeyRichiesta + 1;
|
|
// log
|
|
lgInfo("Contapezzi resettato (PLC) --> pzCountResetted = true");
|
|
}
|
|
|
|
// salvo nuovo valore contapezziPLC
|
|
contapezziPLC = newVal > -1 ? newVal : contapezziPLC;
|
|
}
|
|
else
|
|
{
|
|
lgError($"Errore in decodifica valore contapezzi, valore rilevato: {currPzCount}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgError("Errore in decodifica valore contapezzi, valore vuoto!");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua lettura semafori principale <paramref name="currDispData">Parametri da
|
|
/// aggiornare x display in form</paramref>
|
|
/// </summary>
|
|
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)
|
|
{
|
|
try
|
|
{
|
|
// 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;
|
|
lgTrace($"Aggiornato readSemafori.lastCurrent: {lastCurrent}");
|
|
currReadErrors = 0;
|
|
}
|
|
else
|
|
{
|
|
currReadErrors++;
|
|
}
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError($"Eccezione durante UA_ref.ReadNodeRaw per {item.Value.StartNodeId}{Environment.NewLine}{exc}");
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua reset del contapezzi, NON POSSIBILE in questa versione
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public override bool resetContapezziPLC()
|
|
{
|
|
bool answ = false;
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua IMPOSTAZIONE FORZATA del contapezzi, NON POSSIBILE in questa versione
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public override bool setcontapezziPLC(int newPzCount)
|
|
{
|
|
bool answ = false;
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override connessione
|
|
/// </summary>
|
|
public override void tryConnect()
|
|
{
|
|
if (!connectionOk)
|
|
{
|
|
// controllo che il ping sia stato tentato almeno pingTestSec fa...
|
|
if (DateTime.Now.Subtract(lastPING).TotalSeconds > utils.CRI("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 || opcUaParams.forcePingOk)
|
|
{
|
|
string szStatusConnection = "";
|
|
try
|
|
{
|
|
// ora provo connessione...
|
|
parentForm.commPlcActive = true;
|
|
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}");
|
|
parentForm.commPlcActive = false;
|
|
// connesso se esito == 1...
|
|
connectionOk = (esitoLink == 1);
|
|
// refresh stato allarmi!!!
|
|
if (connectionOk)
|
|
{
|
|
queueInEnabCurr = true;
|
|
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 {utils.CRI("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.cncPingAddr}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
needRefresh = true;
|
|
}
|
|
// se non è ancora connesso faccio procesisng memoria caso disconnesso...
|
|
if (!connectionOk)
|
|
{
|
|
// processo semafori ed invio...
|
|
processMemoryDiscon();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override disconnessione
|
|
/// </summary>
|
|
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
|
|
{
|
|
lgDebug("IMPOSSIBILE effettuare disconnessione OpcUa: Connessione non disponibile...");
|
|
}
|
|
queueInEnabCurr = false;
|
|
}
|
|
|
|
#endregion Public Methods
|
|
|
|
#region Internal Properties
|
|
|
|
/// <summary>
|
|
/// Dizionario chiavi OpcUa bonificate da char non standard
|
|
/// </summary>
|
|
internal Dictionary<string, string> DictOpcFixKeys { get; set; } = new Dictionary<string, string>();
|
|
|
|
#endregion Internal Properties
|
|
|
|
#region Internal Methods
|
|
|
|
/// <summary>
|
|
/// Verifica ed invia variazioni
|
|
/// </summary>
|
|
/// <param name="MonIt"></param>
|
|
/// <param name="NotifyValue"></param>
|
|
/// <param name="forceSend"></param>
|
|
internal bool checkAndSend(Opc.Ua.Client.MonitoredItem MonIt, string NotifyValue, bool forceSend)
|
|
{
|
|
bool changed = false;
|
|
bool hasVetoFL = false;
|
|
bool hasVetoLK = false;
|
|
bool hasVetoLKV = false;
|
|
if (MonIt != null)
|
|
{
|
|
string uuid = calcID($"{MonIt.StartNodeId.Identifier}", $"{MonIt.DisplayName}");
|
|
if (!string.IsNullOrEmpty(NotifyValue))
|
|
{
|
|
string sVal = "";
|
|
string descr = "";
|
|
DateTime locTStamp = DateTime.Now;
|
|
|
|
descr = itemTranslation("OPC", uuid);
|
|
sVal = $"Change 01: {locTStamp.ToString()} | descr: {descr} | Id: {MonIt.StartNodeId} | Val: {NotifyValue}";
|
|
lgTrace(sVal);
|
|
|
|
// verifico se salvare variazione (in LUT locale)
|
|
changed = checkSaveValue(MonIt, NotifyValue, true);
|
|
// cerco se non sia un dato filtrato x invio FLUXLOG in toto...
|
|
hasVetoFL = opcUaParams.fluxLogVeto.Contains(uuid);
|
|
// cerco filtro ANCHE PER solo display name x contains...
|
|
if (!hasVetoFL)
|
|
{
|
|
hasVetoFL = opcUaParams.fluxLogVetoContains.Contains(uuid) || opcUaParams.fluxLogVetoContains.Contains(MonIt.DisplayName);
|
|
}
|
|
if (hasVetoFL)
|
|
{
|
|
lgTrace($"NON ACCODATO sample per {uuid} - trovato VETO in fluxLogVeto", false);
|
|
}
|
|
else
|
|
{
|
|
// cerco se sia un dato/valore
|
|
hasVetoLK = opcUaParams.fluxLogKeyValVeto.ContainsKey(descr);
|
|
if (hasVetoLK)
|
|
{
|
|
// se c'è controllo il valore...
|
|
var valList = opcUaParams.fluxLogKeyValVeto[descr];
|
|
hasVetoLKV = valList.Contains(NotifyValue);
|
|
}
|
|
if (hasVetoLKV)
|
|
{
|
|
lgTrace($"NON ACCODATO sample: veto trovato per {descr}/{NotifyValue} in fluxLogKeyValVeto ", false);
|
|
}
|
|
else
|
|
{
|
|
if (changed || forceSend)
|
|
{
|
|
accodaFLog(sVal, qEncodeFLog(descr, NotifyValue));
|
|
}
|
|
else
|
|
{
|
|
lgTrace($"NON ACCODATO sample per {uuid} - verifica variazione ha dato esito negativo", false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgDebug($"checkAndSend ERROR 01 | MonIt: {uuid} | NotifyValue Null!!!");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgError("checkAndSend ERROR: MonIt null");
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifica ed invia variazioni DAL FORMATO RAW data (byte[])
|
|
/// </summary>
|
|
/// <param name="MonIt"></param>
|
|
/// <param name="NotifyValue"></param>
|
|
/// <param name="forceSend"></param>
|
|
internal virtual bool checkAndSendRaw(Opc.Ua.Client.MonitoredItem MonIt, byte[] NotifyValue, bool forceSend)
|
|
{
|
|
bool changed = false;
|
|
if (MonIt != null)
|
|
{
|
|
string uuid = calcID($"{MonIt.StartNodeId.Identifier}", $"{MonIt.DisplayName}");
|
|
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();
|
|
// tolgo ultima string
|
|
if (currVal.Length > 1)
|
|
{
|
|
currVal = currVal.Substring(0, currVal.Length - 1);
|
|
}
|
|
|
|
string sVal = "";
|
|
string descr = "";
|
|
DateTime locTStamp = DateTime.Now;
|
|
descr = itemTranslation("OPC", uuid);
|
|
// calcolo nomi "bonificati"
|
|
string startNodeId = stringFixOpcUaName($"{MonIt.StartNodeId}");
|
|
string displayName = stringFixOpcUaName($"{MonIt.DisplayName}");
|
|
sVal = $"Change 02: {locTStamp.ToString()} | descr: {descr} | Id: {startNodeId} | Val: {currVal}";
|
|
lgDebug(sVal);
|
|
|
|
// verifico se salvare
|
|
changed = checkSaveValue(MonIt, currVal, NotifyValue, true) || forceSend;
|
|
// cerco se non sia un dato filtrato in FLUXLOG...
|
|
bool isFiltered = opcUaParams.fluxLogVeto.Contains(uuid);
|
|
// cerco filtro ANCHE PER solo display name x contains...
|
|
if (!isFiltered)
|
|
{
|
|
isFiltered = opcUaParams.fluxLogVetoContains.Contains(uuid) || opcUaParams.fluxLogVetoContains.Contains(displayName);
|
|
}
|
|
if (isFiltered)
|
|
{
|
|
lgTrace($"NON ACCODATO sample per {uuid} - trovato VETO in fluxLogVeto", false);
|
|
}
|
|
else
|
|
{
|
|
if (changed || forceSend)
|
|
{
|
|
//accodaFLog(sVal, qEncodeFLog(descr, $"{NotifyValue}"));
|
|
bool parentSent = false;
|
|
// invia SOLO SE i dati raw sono configurati x invio...
|
|
if (opcUaParams.sendSubscribItemsRaw)
|
|
{
|
|
accodaFLog(sVal, qEncodeFLog(descr, currVal));
|
|
parentSent = true;
|
|
}
|
|
|
|
// se abilitata decodifica area mMapRead da byte...
|
|
if (opcUaParams.mMapReadFromRawByte)
|
|
{
|
|
// calcolo eventuali variazioni dati "collegati" in memRead/memWrite...
|
|
if (memMap.mMapRead != null && memMap.mMapRead.Count > 0)
|
|
{
|
|
// verifica se il dato "parent" è esplicitamente abilitato x invio...
|
|
var parentRev = memMap.mMapRead.Where(x => x.Value.memAddr.Equals(uuid)).ToList();
|
|
if (!parentSent && parentRev.Count == 1)
|
|
{
|
|
// verifico invio abilitato
|
|
if (sendEnabledFLog(uuid))
|
|
{
|
|
accodaFLog(sVal, qEncodeFLog(descr, currVal));
|
|
parentSent = true;
|
|
}
|
|
}
|
|
// elenco aree collegate...
|
|
var listRelated = memMap.mMapRead.Where(x => x.Value.memAddr.Contains(uuid)).ToList();
|
|
if (listRelated != null && listRelated.Count() > 0)
|
|
{
|
|
foreach (var item in listRelated)
|
|
{
|
|
// verifico invio abilitato
|
|
if (sendEnabledFLog(item.Key))
|
|
{
|
|
// ciclo e calcolo valore
|
|
var rByte = dataItemMem[uuid].rawByte;
|
|
int iPos = item.Value.memAddr.IndexOf(opcUaParams.kvDelim);
|
|
string varDef = item.Value.memAddr.Substring(iPos + 1);
|
|
string convVal = ConvFromBArr(rByte, varDef, opcUaParams.memDelim);
|
|
// verifica SE ci sia deadBand da validare...
|
|
if (valueOverDBand(item.Key, convVal))
|
|
{
|
|
// metto in array memData...
|
|
if (upsertKey(item.Key, convVal))
|
|
{
|
|
// accodo se variato!
|
|
accodaFLog($"mMapRead | {item.Key} | {convVal}", qEncodeFLog(item.Key, convVal));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// se abilitata decodifica area mMapWrite da byte...
|
|
if (opcUaParams.mMapWriteFromRawByte)
|
|
{
|
|
if (memMap.mMapWrite != null && memMap.mMapWrite.Count > 0)
|
|
{
|
|
// elenco aree collegate...
|
|
var listRelated = memMap.mMapWrite.Where(x => x.Value.memAddr.Contains(uuid)).ToList();
|
|
if (listRelated != null && listRelated.Count() > 0)
|
|
{
|
|
foreach (var item in listRelated)
|
|
{
|
|
// verifico invio abilitato
|
|
if (sendEnabledFLog(item.Key))
|
|
{
|
|
// ciclo e calcolo valore
|
|
var rByte = dataItemMem[uuid].rawByte;
|
|
int iPos = item.Value.memAddr.IndexOf(opcUaParams.kvDelim);
|
|
string varDef = item.Value.memAddr.Substring(iPos + 1);
|
|
string convVal = ConvFromBArr(rByte, varDef, opcUaParams.memDelim);
|
|
// verifica SE ci sia deadBand da validare...
|
|
if (valueOverDBand(item.Key, convVal))
|
|
{
|
|
// metto in array memData...
|
|
if (upsertKey(item.Key, convVal))
|
|
{
|
|
// accodo se variato!
|
|
accodaFLog($"mMapRead | {item.Key} | {convVal}", qEncodeFLog(item.Key, convVal));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgTrace($"NON ACCODATO sample per {uuid} - verifica variazione ha dato esito negativo", false);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgError($"checkAndSend ERROR 02 | MonIt: {uuid} | NotifyValue Null!!!");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgError("checkAndSend ERROR: MonIt null");
|
|
}
|
|
return changed;
|
|
}
|
|
/// <summary>
|
|
/// Cerca la DBand specifica di un parametro
|
|
/// </summary>
|
|
/// <param name="keyName"></param>
|
|
/// <returns></returns>
|
|
protected float specDBand(string keyName)
|
|
{
|
|
float answ = 0;
|
|
// cerco nel dizionario...
|
|
if (dictActDBand.ContainsKey(keyName))
|
|
{
|
|
answ = dictActDBand[keyName];
|
|
}
|
|
// altrimenti proseguo...
|
|
else
|
|
{
|
|
// cerco key in parametri end
|
|
if (opcUaParams.paramsEndThresh.Count > 0)
|
|
{
|
|
// ciclo su tutti i parametri END indicati...
|
|
foreach (var item in opcUaParams.paramsEndThresh)
|
|
{
|
|
if (keyName.EndsWith(item.Key))
|
|
{
|
|
answ = item.Value;
|
|
}
|
|
}
|
|
}
|
|
// cerco key in parametri contains
|
|
if (opcUaParams.paramsContainsThresh.Count > 0)
|
|
{
|
|
// ciclo su tutti i parametri CONTAINS indicati...
|
|
foreach (var item in opcUaParams.paramsContainsThresh)
|
|
{
|
|
if (keyName.Contains(item.Key))
|
|
{
|
|
answ = item.Value;
|
|
}
|
|
}
|
|
}
|
|
// aggiungo comunque al dizionario x velocizzare poi...
|
|
dictActDBand.Add(keyName, answ);
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override OPC-UA della verifica se il parametro passi il limite della DeadBand (globale o specifica se configurata)
|
|
/// Il riferimento è al prec valore currProdData
|
|
/// </summary>
|
|
/// <param name="keyName">nome del parametro da verificare</param>
|
|
/// <param name="actVal">valore attuale da testare</param>
|
|
/// <returns>true = passa controllo, è da inviare / false = variazione entro deadband non da inviare</returns>
|
|
protected override bool valueOverDBand(string keyName, string actVal)
|
|
{
|
|
bool answ = true;
|
|
float oldVal = 0;
|
|
float newVal = 0;
|
|
// DEVE esserci un valore global deadband > 0 o uno specifico x la key...
|
|
float spDBand = specDBand(keyName);
|
|
float currDBand = spDBand > GLOBAL_DBAND ? spDBand : GLOBAL_DBAND;
|
|
if (currDBand > 0)
|
|
{
|
|
// in primis deve essere in currProdData
|
|
if (currProdData.ContainsKey(keyName))
|
|
{
|
|
// se c'è DEVE passare il test decodifica float...
|
|
bool isFloatOld = float.TryParse(currProdData[keyName], out oldVal);
|
|
bool isFloatNew = float.TryParse(actVal, out newVal);
|
|
if (isFloatOld && isFloatNew)
|
|
{
|
|
// in questo caso verifico la differenza sia superiore alla deadband
|
|
answ = Math.Abs(newVal - oldVal) > currDBand;
|
|
}
|
|
}
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
internal void sendDataItemListToServer(NodeId StartNodeId)
|
|
{
|
|
// converto gli attuali nell'elenco dataitem...
|
|
List<machDataItem> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fix stringa da versione "raw " a "bonificata"
|
|
/// </summary>
|
|
/// <param name="origName"></param>
|
|
/// <returns></returns>
|
|
internal string stringFixOpcUaName(string origName)
|
|
{
|
|
string answ = origName;
|
|
// se ho dizionari di sostituzione caratteri sistemo!
|
|
if (opcUaParams.DictOpcNameReplace.Count > 0)
|
|
{
|
|
// cerco nel dizionario LUT di traduzione
|
|
if (DictOpcFixKeys.ContainsKey(origName))
|
|
{
|
|
answ = DictOpcFixKeys[origName];
|
|
}
|
|
else
|
|
{
|
|
// altrimenti sitemo e metto in dizionario
|
|
foreach (var item in opcUaParams.DictOpcNameReplace)
|
|
{
|
|
answ = answ.Replace(item.Key, item.Value);
|
|
}
|
|
DictOpcFixKeys.Add(origName, answ);
|
|
}
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Evento rilevazione modifica valori effettua checkSend
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
internal virtual void UA_ref_eh_MonItChange(object sender, opcUaMonitItemChange e)
|
|
{
|
|
lgTrace("Richiamato UA_ref_eh_MonItChange");
|
|
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;
|
|
lgTrace($"Aggiornato UA_ref_eh_MonItChange.lastCurrent: {lastCurrent}");
|
|
}
|
|
|
|
#endregion Internal Methods
|
|
|
|
#region Protected Fields
|
|
|
|
/// <summary>
|
|
/// Struttura dove vengono memorizzati i dataitem ed i rispettivi valori x processing
|
|
/// </summary>
|
|
protected Dictionary<string, OpcUaDataItemExt> dataItemMem = new Dictionary<string, OpcUaDataItemExt>();
|
|
|
|
/// <summary>
|
|
/// Abilitazione restart (da opt par...)
|
|
/// </summary>
|
|
protected bool enableCliRestart = false;
|
|
|
|
/// <summary>
|
|
/// Gestione filtraggio dati
|
|
/// </summary>
|
|
protected bool enableDataFilter = false;
|
|
|
|
/// <summary>
|
|
/// Determina se ha effettuata lettura items in memoria x confronto...
|
|
/// </summary>
|
|
protected bool hasReadItems = false;
|
|
|
|
/// <summary>
|
|
/// Ultimo current received x gestione update periodico...
|
|
/// </summary>
|
|
protected DateTime lastCurrent = DateTime.Now;
|
|
|
|
/// <summary>
|
|
/// Periodo massimo (in sec) per letture dati RAW in mancanza di eventi
|
|
/// </summary>
|
|
protected int lastCurrentMaxElapsed = 120;
|
|
|
|
/// <summary>
|
|
/// Oggetto MAIN x connessione OPC-UA
|
|
/// </summary>
|
|
protected UAClient UA_ref;
|
|
|
|
/// <summary>
|
|
/// Veto controllo status x log...
|
|
/// </summary>
|
|
protected DateTime vetoCheckStatus = DateTime.Now;
|
|
|
|
protected int WatchDog = 0;
|
|
|
|
#endregion Protected Fields
|
|
|
|
#region Protected Properties
|
|
|
|
/// <summary>
|
|
/// Area dati raw per lettura encoded (SE presente)
|
|
/// </summary>
|
|
protected byte[] byteRawData { get; set; } = new byte[1];
|
|
|
|
/// <summary>
|
|
/// ProgName corrente
|
|
/// </summary>
|
|
protected string currProgName
|
|
{
|
|
get
|
|
{
|
|
string answ = "";
|
|
if (!string.IsNullOrEmpty(opcUaParams.keyProgName))
|
|
{
|
|
answ = getDataItemValue(opcUaParams.keyProgName);
|
|
}
|
|
return answ;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indica se si debba leggere un area di tipo "encoded" (byte raw --> obj)
|
|
/// </summary>
|
|
protected bool doByteRead { get; set; } = false;
|
|
|
|
/// <summary>
|
|
/// Verifico se abbia ALMENO un errore...
|
|
/// </summary>
|
|
protected bool hasError
|
|
{
|
|
get
|
|
{
|
|
return checkMultiCondition(opcUaParams.condError);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indica se abbia emergenza ARMATA (cond normale)
|
|
/// </summary>
|
|
protected bool hasEStopArmed
|
|
{
|
|
get
|
|
{
|
|
return checkMultiCondition(opcUaParams.condEStop);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indica se abbia stato POWER ON (multicondizione)
|
|
/// </summary>
|
|
protected virtual bool hasPowerOn
|
|
{
|
|
get
|
|
{
|
|
return checkMultiCondition(opcUaParams.condPowerOn);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indica se abbia stato MANUAL (condizioni varie, es stopped)
|
|
/// </summary>
|
|
protected virtual bool isManual
|
|
{
|
|
get
|
|
{
|
|
return checkMultiCondition(opcUaParams.condManual);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indica se abbia stato READY (condizioni varie, es ausiliari OK)
|
|
/// </summary>
|
|
protected virtual bool isReady
|
|
{
|
|
get
|
|
{
|
|
return checkMultiCondition(opcUaParams.condReady);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indica se sia in stato Ssetup
|
|
/// </summary>
|
|
protected bool isSetup
|
|
{
|
|
get
|
|
{
|
|
return checkMultiCondition(opcUaParams.condSetup);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indica se sia in stato WarmUp / CoolDown (riscaldamento/raffreddamento)
|
|
/// </summary>
|
|
protected bool isWarmUpCoolDown
|
|
{
|
|
get
|
|
{
|
|
return checkMultiCondition(opcUaParams.condWarmUpCoolDown);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indica se sia in stato Warning
|
|
/// </summary>
|
|
protected bool isWarning
|
|
{
|
|
get
|
|
{
|
|
return checkMultiCondition(opcUaParams.condWarning);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indica se abbia stato READY (condizioni varie, es ausiliari OK)
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parametri specifici OPC-UA
|
|
/// </summary>
|
|
protected OpcUaParamConf opcUaParams { get; set; }
|
|
|
|
/// <summary>
|
|
/// item sottoscritti x lettura periodica in caso di dati "lentamente varianti"....
|
|
/// </summary>
|
|
protected List<Opc.Ua.Client.MonitoredItem> subscribedItems { get; set; } = new List<Opc.Ua.Client.MonitoredItem>();
|
|
|
|
/// <summary>
|
|
/// URL x salvataggio elenco dataItems OpcUa
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Calcola UUID per i vari items data configurazione OPC-UA corrente
|
|
/// </summary>
|
|
/// <param name="fullCode">Codice univoco completo (tipicamente quello che viene dopo ns=2;s=)</param>
|
|
/// <param name="shortCode">Codice leaf finale (NON necessariamente univoco)</param>
|
|
/// <returns></returns>
|
|
protected string calcID(string fullCode, string shortCode)
|
|
{
|
|
// parto assegnando il valore "short" che è il vecchio default...
|
|
string answ = shortCode;
|
|
if (opcUaParams.UseFullId)
|
|
{
|
|
if (string.IsNullOrEmpty(opcUaParams.BaseIdMask))
|
|
{
|
|
answ = fullCode;
|
|
}
|
|
else
|
|
{
|
|
answ = fullCode.Replace(opcUaParams.BaseIdMask, "");
|
|
}
|
|
answ = stringFixOpcUaName(answ);
|
|
// trimmo spazi x ridurre dimensione se > 50...
|
|
if (answ.Length > 50)
|
|
{
|
|
answ = answ.Replace(" ", "");
|
|
}
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifica un DataItem e se il valore corrisponde a quello indicato come "true value"
|
|
/// restituisce true
|
|
/// </summary>
|
|
/// <param name="itemName"></param>
|
|
/// <param name="trueVal"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifica condizione "multipla" secondo setup json
|
|
/// </summary>
|
|
/// <param name="reqCondition">Set condizioni da validare</param>
|
|
/// <returns></returns>
|
|
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
|
|
{
|
|
// verifico se ci sia richiesta x test bit...
|
|
if (item.bitNum >= 0)
|
|
{
|
|
if (getDataItemValueBit(item.keyName, item.bitNum) == item.targetValue)
|
|
{
|
|
numCondOk++;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifica / Salva valore e restitusice SE sia variato (e quindi da inviare...)
|
|
/// </summary>
|
|
/// <param name="dataItem"></param>
|
|
/// <param name="NotifyValue"></param>
|
|
/// <param name="RawByteVal"></param>
|
|
/// <param name="SendItemList"></param>
|
|
/// <returns></returns>
|
|
protected bool checkSaveValue(Opc.Ua.Client.MonitoredItem dataItem, string NotifyValue, byte[] RawByteVal, bool SendItemList)
|
|
{
|
|
bool answ = checkSaveValue(dataItem, NotifyValue, SendItemList);
|
|
if (answ)
|
|
{
|
|
string uuid = calcID($"{dataItem.StartNodeId.Identifier}", $"{dataItem.DisplayName}");
|
|
dataItemMem[uuid].rawByte = RawByteVal;
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifica / Salva valore e restitusice SE sia variato (e quindi da inviare...)
|
|
/// </summary>
|
|
/// <param name="dataItem"></param>
|
|
/// <param name="NotifyValue"></param>
|
|
/// <param name="SendItemList"></param>
|
|
/// <returns></returns>
|
|
protected bool checkSaveValue(Opc.Ua.Client.MonitoredItem dataItem, string NotifyValue, bool SendItemList)
|
|
{
|
|
bool answ = !enableDataFilter;
|
|
double oldVal = 0;
|
|
double newVal = 0;
|
|
if (dataItem != null)
|
|
{
|
|
string uuid = calcID($"{dataItem.StartNodeId.Identifier}", $"{dataItem.DisplayName}");
|
|
|
|
if (!string.IsNullOrEmpty(NotifyValue) && !NotifyValue.Contains("null"))
|
|
{
|
|
lgTrace($"Richiesta checkSaveValue per {dataItem.DisplayName} | id: {dataItem.StartNodeId} | Valore: {NotifyValue}");
|
|
// verifico in memoria se ho l'oggetto condition ed il suo valore..
|
|
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}")
|
|
{
|
|
lgTrace($"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;
|
|
}
|
|
}
|
|
if (answ)
|
|
{
|
|
lgDebug($"Test deadband reached| uuid: {uuid} | DisplayName: {dataItem.DisplayName} | oldVal: {oldVal} | newVal: {newVal} | dBand: {dataItemMem[uuid].thresholdDeadBand}");
|
|
}
|
|
else
|
|
{
|
|
lgTrace($"Test deadband NOT exceeded | uuid: {uuid} | DisplayName: {dataItem.DisplayName} | 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: {uuid}");
|
|
// provo a creare oggetto in memoria...
|
|
try
|
|
{
|
|
int dSamplePeriod = 0;
|
|
float 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
|
|
{
|
|
lgTrace("Attenzione: checkSaveItem con Notify null!");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgError("Attenzione: checkSaveItem con MonIt null!");
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua decodifica aree memoria alla bitmap usata x MAPO
|
|
/// </summary>
|
|
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 = "";
|
|
int currTryPing = maxTryPing;
|
|
while (currTryPing > 0 && !checkPing)
|
|
{
|
|
if (!checkPing)
|
|
{
|
|
checkPing = (testPingMachine == IPStatus.Success);
|
|
}
|
|
currTryPing--;
|
|
// se ping KO --> aspetto prima di ripetere test
|
|
if (!checkPing)
|
|
{
|
|
Random rand = new Random();
|
|
Thread.Sleep(rand.Next(pingServerMsTimeout * 2));
|
|
}
|
|
}
|
|
|
|
// 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))
|
|
// controllo se non ho dati buoni da > lastCurrentMaxElapsed sec --> disconnetto
|
|
if (adesso.Subtract(lastCurrent).TotalSeconds > lastCurrentMaxElapsed)
|
|
{
|
|
lgInfo($"Timeout per mancata comunicazione da oltre {lastCurrentMaxElapsed} sec --> disconnessione adapter OpcUa!");
|
|
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<WriteValue> nodes2Write = new List<WriteValue>();
|
|
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}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recupero allarmi specifico x OPC-UA, in formato BITMAP (se è gestito a bitmap) o come
|
|
/// INT (counter tipo BLM)
|
|
/// </summary>
|
|
/// <param name="item"></param>
|
|
/// <returns></returns>
|
|
protected override int getAlarmStatus(BaseAlarmConf item)
|
|
{
|
|
int answ = 0;
|
|
// recupero valore in formato stringa...
|
|
string currStatus = getDataItemValue(item.memAddr);
|
|
lgDebug($"getAlarmStatus {item.memAddr} | currStatus: {currStatus}");
|
|
// se non nullo --> recupero int
|
|
if (!string.IsNullOrEmpty(currStatus))
|
|
{
|
|
int.TryParse(currStatus, out answ);
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
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(sVal);
|
|
var bodyVal = ((Opc.Ua.ExtensionObject)wrapVal.Value).Body;
|
|
rawByte = (byte[])bodyVal;
|
|
}
|
|
catch
|
|
{ }
|
|
}
|
|
return rawByte;
|
|
}
|
|
#if false
|
|
protected DataValue setByteRaw(DataValue obj, byte[] newData, int startPos)
|
|
{
|
|
if (obj != null)
|
|
{
|
|
byte[] rawByte = new byte[1];
|
|
if (obj != null)
|
|
{
|
|
try
|
|
{
|
|
var wrapVal = ((DataValue)obj).WrappedValue;
|
|
var bodyVal = ((Opc.Ua.ExtensionObject)wrapVal.Value).Body;
|
|
rawByte = (byte[])bodyVal;
|
|
// sostituisco
|
|
Array.Copy(newData, 0, rawByte, startPos, newData.Length);
|
|
// aggiorno oggetto originale...
|
|
((Opc.Ua.ExtensionObject)wrapVal.Value).Body = rawByte;
|
|
}
|
|
catch
|
|
{ }
|
|
}
|
|
}
|
|
return obj;
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Effettua traduzione ITEM da LUT parametrica (key: tipo+id) del file di conf, se non
|
|
/// trovo uso key
|
|
/// </summary>
|
|
/// <param name="tipo"></param>
|
|
/// <param name="id"></param>
|
|
/// <returns></returns>
|
|
protected override string itemTranslation(string tipo, string id)
|
|
{
|
|
string answ = "";
|
|
// se c'è gestione fix nomi sistemo...
|
|
if (opcUaParams.DictOpcNameReplace.Count > 0)
|
|
{
|
|
foreach (var item in opcUaParams.DictOpcNameReplace)
|
|
{
|
|
id = id.Replace(item.Key, item.Value);
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua lettura file di conf specifico OPC-UA da oggetto serializzato json <paramref
|
|
/// name="fileName">Nome file da cui leggere i parametri json</paramref>
|
|
/// </summary>
|
|
protected void loadOpcUaConf(string fileName)
|
|
{
|
|
string jsonFullPath = $"{Application.StartupPath}/DATA/CONF/{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<OpcUaParamConf>(jsonData);
|
|
lgDebug($"Decodifica aree OpcUaParamConf: trovati {opcUaParams.paramsEndThresh.Count} valori paramsEndThresh");
|
|
// sistemo se ci sono dati memMap...
|
|
memMap = new plcMemMapExt();
|
|
if (opcUaParams.mMapWrite != null)
|
|
{
|
|
memMap.mMapWrite = opcUaParams.mMapWrite;
|
|
}
|
|
if (opcUaParams.mMapRead != null)
|
|
{
|
|
memMap.mMapRead = opcUaParams.mMapRead;
|
|
}
|
|
setupMemMap();
|
|
setupOptMemPar();
|
|
setupFileDecod();
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError($"Eccezione in decodifica conf json OPC-UA:{Environment.NewLine}{exc}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgError("Errore in loadOpcUaConf: file json vuoto!");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Per sicurezza rileggo TUTTI i valori x eventuali variazioni
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
protected override bool processSlowDataRead()
|
|
{
|
|
bool answ = false;
|
|
if (enableSlowData)
|
|
{
|
|
int fatto = 0;
|
|
stopwatch.Restart();
|
|
foreach (var item in subscribedItems)
|
|
{
|
|
bool changed = false;
|
|
// verifico se ho i dati complessi by design
|
|
string currVal = "";
|
|
if (doByteRead)
|
|
{
|
|
try
|
|
{
|
|
// 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);
|
|
if (changed)
|
|
{
|
|
fatto++;
|
|
}
|
|
}
|
|
if (readErrorCurr > 0)
|
|
{
|
|
readErrorCurr--;
|
|
}
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError($"Eccezione durante OpcUa.processSlowDataRead per {item.StartNodeId}{Environment.NewLine}{exc}");
|
|
readErrorCurr++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
currVal = UA_ref.ReadNodeString(item.StartNodeId);
|
|
changed = checkAndSend(item, currVal, true);
|
|
if (changed)
|
|
{
|
|
fatto++;
|
|
}
|
|
if (readErrorCurr > 0)
|
|
{
|
|
readErrorCurr--;
|
|
}
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError($"Eccezione durante OpcUa.processSlowDataRead per {item.StartNodeId}{Environment.NewLine}{exc}");
|
|
readErrorCurr++;
|
|
}
|
|
}
|
|
}
|
|
answ = subscribedItems.Count > 0;
|
|
stopwatch.Stop();
|
|
lgInfo($"End processSlowDataRead | {fatto} items changed | {stopwatch.ElapsedMilliseconds}ms");
|
|
// controllo read error ed eventuale disconnect
|
|
if (readErrorCurr > readErrorMax)
|
|
{
|
|
lgError($"Effettuo disconnessione x superamento errori lettura UA_ref.ReadNodeString, sleetp 15 sec");
|
|
tryDisconnect();
|
|
Thread.Sleep(readErrorSleepTime);
|
|
readErrorCurr = 0;
|
|
}
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
protected virtual void procRunMode(ref string currRun)
|
|
{
|
|
// variabili RUN... se richiesto invio runMode
|
|
if (opcUaParams.runModeSend)
|
|
{
|
|
bool hasVetoLK = false;
|
|
bool hasVetoLKV = false;
|
|
if (!string.IsNullOrEmpty(opcUaParams.keyRunMode))
|
|
{
|
|
currRun = getDataItemValue(opcUaParams.keyRunMode);
|
|
if (!string.IsNullOrEmpty(currRun))
|
|
{
|
|
// se ho valore --> invio
|
|
string sVal = "";
|
|
string descr = $"RunModeVal";
|
|
DateTime locTStamp = DateTime.Now;
|
|
sVal = $"Change 03: {locTStamp.ToString()} | descr: {descr} | Id: {opcUaParams.keyRunMode} | Val: {currRun}";
|
|
// cerco se sia un dato/valore
|
|
hasVetoLK = opcUaParams.fluxLogKeyValVeto.ContainsKey(descr);
|
|
if (hasVetoLK)
|
|
{
|
|
// se c'è controllo il valore...
|
|
var valList = opcUaParams.fluxLogKeyValVeto[descr];
|
|
hasVetoLKV = valList.Contains(currRun);
|
|
}
|
|
if (hasVetoLKV)
|
|
{
|
|
lgTrace($"NON ACCODATO sample: veto trovato per {descr}/{currRun} in fluxLogKeyValVeto ", false);
|
|
}
|
|
else
|
|
{
|
|
accodaFLog(sVal, qEncodeFLog(descr, currRun));
|
|
|
|
// se richiesto provo a tradurre
|
|
if (opcUaParams.runModeTrad)
|
|
{
|
|
string RunModeDescr = itemTranslation("RunMode", currRun);
|
|
descr = $"RunMode";
|
|
accodaFLog(sVal, qEncodeFLog(descr, RunModeDescr));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion Protected Methods
|
|
|
|
#region Private Fields
|
|
|
|
/// <summary>
|
|
/// Num errori lettura correnti
|
|
/// </summary>
|
|
private int readErrorCurr = 0;
|
|
|
|
/// <summary>
|
|
/// Max num errori lettura prima di disconnettere
|
|
/// </summary>
|
|
private int readErrorMax = 10;
|
|
|
|
/// <summary>
|
|
/// </summary>
|
|
private int readErrorSleepTime = 15000;
|
|
|
|
/// <summary>
|
|
/// Elenco degli items da monitorare come risultato del browse iniziale
|
|
/// </summary>
|
|
private Dictionary<string, string> selectedItemList = new Dictionary<string, string>();
|
|
|
|
#endregion Private Fields
|
|
|
|
#region Private Methods
|
|
|
|
/// <summary>
|
|
/// Conversione da valore byteArray a string del tipo obj richiesto
|
|
/// </summary>
|
|
/// <param name="bArray">Array byte originale</param>
|
|
/// <param name="varDef">Stringa definizione variabile</param>
|
|
/// <param name="pSep">Separatore nella definizione</param>
|
|
/// <returns></returns>
|
|
private string ConvFromBArr(byte[] bArray, string varDef, char pSep)
|
|
{
|
|
string answ = "";
|
|
// converto prendendo solo parte richiesta...
|
|
if (bArray.Length > 0)
|
|
{
|
|
// splitto in primis la definizione
|
|
MemStruct currMem = new MemStruct(varDef, pSep);
|
|
|
|
// S7.Double.B0 ora eseguo riconoscimento (da portare in base? FixMe ToDo...)
|
|
if (currMem.Product == "S7")
|
|
{
|
|
// converto!
|
|
plcDataType tVal = plcDataType.Int;
|
|
Enum.TryParse(currMem.Type, out tVal);
|
|
// ora gestisco seconda parte...
|
|
switch (tVal)
|
|
{
|
|
case plcDataType.Boolean:
|
|
var boolVal = S7.Net.Types.Boolean.GetValue(bArray[currMem.Idx], currMem.Num);
|
|
answ = $"{boolVal}";
|
|
break;
|
|
|
|
case plcDataType.Int:
|
|
var intVal = S7.Net.Types.Int.FromByteArray(bArray.Skip(currMem.Idx).Take(2).ToArray());
|
|
answ = $"{intVal}";
|
|
break;
|
|
//case plcDataType.IntLH:
|
|
// break;
|
|
case plcDataType.DInt:
|
|
var dIntVal = S7.Net.Types.Int.FromByteArray(bArray.Skip(currMem.Idx).Take(4).ToArray());
|
|
answ = $"{dIntVal}";
|
|
break;
|
|
//case plcDataType.DIntLH:
|
|
// break;
|
|
case plcDataType.Word:
|
|
var wordVal = S7.Net.Types.Word.FromByteArray((bArray.Skip(currMem.Idx).Take(2).ToArray()));
|
|
answ = $"{wordVal}";
|
|
break;
|
|
|
|
case plcDataType.DWord:
|
|
var dWordVal = S7.Net.Types.Word.FromByteArray((bArray.Skip(currMem.Idx).Take(4).ToArray()));
|
|
answ = $"{dWordVal}";
|
|
break;
|
|
|
|
case plcDataType.Real:
|
|
var dblVal = S7.Net.Types.Double.FromByteArray(bArray.Skip(currMem.Idx).Take(4).ToArray());
|
|
answ = $"{dblVal:N3}";
|
|
break;
|
|
//case plcDataType.RealHL:
|
|
// break;
|
|
//case plcDataType.RealLH:
|
|
// break;
|
|
case plcDataType.String:
|
|
answ = S7.Net.Types.String.FromByteArray(bArray.Skip(currMem.Idx).Take(currMem.Num).ToArray());
|
|
break;
|
|
//case plcDataType.HLPInt:
|
|
// break;
|
|
//case plcDataType.FloatABCD:
|
|
// break;
|
|
//case plcDataType.FloatBADC:
|
|
// break;
|
|
//case plcDataType.FloatCDAB:
|
|
// break;
|
|
//case plcDataType.FloatDCBA:
|
|
// break;
|
|
//case plcDataType.BitMap:
|
|
// break;
|
|
//case plcDataType.Byte:
|
|
// break;
|
|
//case plcDataType.UInt:
|
|
// break;
|
|
//case plcDataType.UDInt:
|
|
// break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else if (currMem.Product == "GEN")
|
|
{
|
|
// ora gestisco seconda parte...
|
|
switch (currMem.Type)
|
|
{
|
|
case "Byte":
|
|
bool bVal = false;
|
|
if (bArray.Length > currMem.Idx)
|
|
{
|
|
bVal = bArray[currMem.Idx] == 1;
|
|
}
|
|
answ = bVal ? "1" : "0";
|
|
break;
|
|
|
|
case "Float":
|
|
// restituisco
|
|
answ = $"{BitConverter.ToSingle(bArray, currMem.Idx)}";
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// gestire casi NON S7...
|
|
}
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Conversione da valore string a corrispettivo byteArray nel tipo obj richiesto
|
|
/// </summary>
|
|
/// <param name="sVal">Valore da inviare come string</param>
|
|
/// <param name="varDef">Stringa definizione variabile</param>
|
|
/// <param name="pSep">Separatore nella definizione</param>
|
|
/// <returns></returns>
|
|
private byte[] ConvToBArr(string sVal, string varDef, char pSep)
|
|
{
|
|
byte[] answ = new byte[1];
|
|
// converto prendendo solo parte richiesta...
|
|
if (!string.IsNullOrEmpty(sVal))
|
|
{
|
|
// splitto in primis la definizione
|
|
MemStruct currMem = new MemStruct(varDef, pSep);
|
|
|
|
// S7.Double.B0 ora eseguo riconoscimento (da portare in base? FixMe ToDo...)
|
|
if (currMem.Product == "S7")
|
|
{
|
|
#if false
|
|
// converto!
|
|
plcDataType tVal = plcDataType.Int;
|
|
Enum.TryParse(currMem.Type, out tVal);
|
|
// ora gestisco seconda parte...
|
|
switch (tVal)
|
|
{
|
|
case plcDataType.Boolean:
|
|
var boolVal = S7.Net.Types.Boolean.GetValue(bArray[currMem.Idx], currMem.Num);
|
|
answ = $"{boolVal}";
|
|
break;
|
|
|
|
case plcDataType.Int:
|
|
var intVal = S7.Net.Types.Int.FromByteArray(bArray.Skip(currMem.Idx).Take(2).ToArray());
|
|
answ = $"{intVal}";
|
|
break;
|
|
//case plcDataType.IntLH:
|
|
// break;
|
|
case plcDataType.DInt:
|
|
var dIntVal = S7.Net.Types.Int.FromByteArray(bArray.Skip(currMem.Idx).Take(4).ToArray());
|
|
answ = $"{dIntVal}";
|
|
break;
|
|
//case plcDataType.DIntLH:
|
|
// break;
|
|
case plcDataType.Word:
|
|
var wordVal = S7.Net.Types.Word.FromByteArray((bArray.Skip(currMem.Idx).Take(2).ToArray()));
|
|
answ = $"{wordVal}";
|
|
break;
|
|
|
|
case plcDataType.DWord:
|
|
var dWordVal = S7.Net.Types.Word.FromByteArray((bArray.Skip(currMem.Idx).Take(4).ToArray()));
|
|
answ = $"{dWordVal}";
|
|
break;
|
|
|
|
case plcDataType.Real:
|
|
var dblVal = S7.Net.Types.Double.FromByteArray(bArray.Skip(currMem.Idx).Take(4).ToArray());
|
|
answ = $"{dblVal:N3}";
|
|
break;
|
|
//case plcDataType.RealHL:
|
|
// break;
|
|
//case plcDataType.RealLH:
|
|
// break;
|
|
case plcDataType.String:
|
|
answ = S7.Net.Types.String.FromByteArray(bArray.Skip(currMem.Idx).Take(currMem.Num).ToArray());
|
|
break;
|
|
//case plcDataType.HLPInt:
|
|
// break;
|
|
//case plcDataType.FloatABCD:
|
|
// break;
|
|
//case plcDataType.FloatBADC:
|
|
// break;
|
|
//case plcDataType.FloatCDAB:
|
|
// break;
|
|
//case plcDataType.FloatDCBA:
|
|
// break;
|
|
//case plcDataType.BitMap:
|
|
// break;
|
|
//case plcDataType.Byte:
|
|
// break;
|
|
//case plcDataType.UInt:
|
|
// break;
|
|
//case plcDataType.UDInt:
|
|
// break;
|
|
default:
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
else if (currMem.Product == "GEN")
|
|
{
|
|
// ora gestisco seconda parte...
|
|
switch (currMem.Type)
|
|
{
|
|
case "Byte":
|
|
answ = new byte[1];
|
|
bool bVal = false;
|
|
bool.TryParse(sVal, out bVal);
|
|
answ[0] = bVal ? (byte)1 : (byte)0;
|
|
break;
|
|
|
|
case "Float":
|
|
float fVal = 0;
|
|
float.TryParse(sVal, out fVal);
|
|
answ = BitConverter.GetBytes(fVal);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// gestire casi NON S7...
|
|
}
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Vera connessione ad OpcUa
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private async Task<short> doConnect()
|
|
{
|
|
short esitoLink = 0;
|
|
// reset memoria dataItem..
|
|
dataItemMem = new Dictionary<string, OpcUaDataItemExt>();
|
|
// predisposizione conf oggetto di comunicazione MTC
|
|
int port = 4840;
|
|
int.TryParse(cIobConf.cncPort, out port);
|
|
// ora avvio
|
|
try
|
|
{
|
|
lgInfo("Start init OpcUa Client");
|
|
// Define the UA Client application
|
|
ApplicationInstance application = new ApplicationInstance();
|
|
application.ApplicationName = "Steamware IOB-WIN Client";
|
|
application.ApplicationType = ApplicationType.Client;
|
|
|
|
// load the application configuration.
|
|
string confPath = $"{Application.StartupPath}\\DATA\\IobOpcUaClient.Config.xml";
|
|
await application.LoadApplicationConfiguration(confPath, silent: false).ConfigureAwait(false);
|
|
// check the application certificate.
|
|
await application.CheckApplicationInstanceCertificate(silent: false, minimumKeySize: 0).ConfigureAwait(false);
|
|
|
|
lgInfo($"Chiamata UAClient con configurazione standard: {application.ApplicationConfiguration.ApplicationName}");
|
|
string userName = opcUaParams == null || opcUaParams.Identity == null ? "" : opcUaParams.Identity.UserName;
|
|
string passwd = opcUaParams == null || opcUaParams.Identity == null ? "" : opcUaParams.Identity.Passwd;
|
|
UA_ref = new UAClient(application.ApplicationConfiguration, cIobConf.codIOB, userName, passwd, isVerboseLog, ClientBase.ValidateResponse, opcUaParams.maxNullRead);
|
|
|
|
lgInfo($"Chiamata apertura OpcUa Client: {cIobConf.cncIpAddr}:{port}");
|
|
UA_ref.ServerUrl = $"opc.tcp://{cIobConf.cncIpAddr}:{port}";
|
|
|
|
var task = Task.Run(async () =>
|
|
{
|
|
return await UA_ref.ConnectAsync().ConfigureAwait(false);
|
|
});
|
|
bool connected = task.Result;
|
|
if (connected)
|
|
{
|
|
// registro esito a positivo
|
|
esitoLink = 1;
|
|
// faccio un primo browse dei dati...
|
|
Dictionary<string, string> nodeIdNameList = new Dictionary<string, string>();
|
|
// se deve forzare lettura da subscribed parte da quelli...
|
|
if (opcUaParams.BrowseNodeList != null && opcUaParams.BrowseNodeList.Count > 0)
|
|
{
|
|
// effettuo browse 1:1 dei subscribed...
|
|
if (opcUaParams.subscribedItems != null && opcUaParams.subscribedItems.Count > 0)
|
|
{
|
|
lgInfo($"Start browse da elenco di subscribedItems: {opcUaParams.subscribedItems.Count} da esplorare");
|
|
Dictionary<string, string> tempList = new Dictionary<string, string>();
|
|
foreach (var item in opcUaParams.BrowseNodeList)
|
|
{
|
|
tempList = new Dictionary<string, string>();
|
|
try
|
|
{
|
|
UA_ref.Browse(item, opcUaParams.filterItemsNodeId, ref tempList);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError($"Eccezione in browsing subscribedItems:{Environment.NewLine}{exc}");
|
|
}
|
|
// aggiungo!
|
|
foreach (var foundItem in tempList)
|
|
{
|
|
if (!nodeIdNameList.ContainsKey(foundItem.Key))
|
|
{
|
|
nodeIdNameList.Add(foundItem.Key, foundItem.Value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// se fosse ancora vuoto....
|
|
if (nodeIdNameList.Count == 0)
|
|
{
|
|
if (!string.IsNullOrEmpty(opcUaParams.BrowseFullVal))
|
|
{
|
|
lgInfo($"Start browse da BrowseFullVal: {opcUaParams.BrowseFullVal}");
|
|
try
|
|
{
|
|
UA_ref.Browse(opcUaParams.BrowseFullVal, opcUaParams.filterItemsNodeId, ref nodeIdNameList);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError($"Eccezione in browsing BrowseFullVal:{Environment.NewLine}{exc}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgInfo($"Start browse | BrowseNSIndex: {opcUaParams.BrowseNSIndex} | BrowseValue: {opcUaParams.BrowseValue} | filterItemsNodeId: {opcUaParams.filterItemsNodeId} | ");
|
|
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($"Avail: {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)
|
|
{
|
|
lgDebug($"SubNo: {item.Key}");
|
|
}
|
|
lgInfo($"---------- END LIST | {selectedItemList.Count} items ----------");
|
|
|
|
// sottoscrivo a rilevazione cambio dati solo l'incrocio degli insiemi
|
|
subscribedItems = UA_ref.SubscribeToDataChanges(selectedItemList);
|
|
// aggiungo come DataItems
|
|
int dSamplePeriod = 0;
|
|
float 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)
|
|
{
|
|
try
|
|
{
|
|
// restituisce i 115 byte da deserializzare... qui HACK!
|
|
var rawVal = UA_ref.ReadNodeRaw(item.StartNodeId);
|
|
if (rawVal != null)
|
|
{
|
|
byteRawData = getByteRaw((DataValue)rawVal);
|
|
//lgTrace($"{uuid} | {byteRawData.Length} byte");
|
|
changed = checkAndSendRaw(item, byteRawData, true);
|
|
}
|
|
if (readErrorCurr > 0)
|
|
{
|
|
readErrorCurr--;
|
|
}
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError($"Eccezione durante UA_ref.ReadNodeRaw per {item.StartNodeId}{Environment.NewLine}{exc}");
|
|
readErrorCurr++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
currVal = UA_ref.ReadNodeString(item.StartNodeId);
|
|
changed = checkAndSend(item, currVal, true);
|
|
if (readErrorCurr > 0)
|
|
{
|
|
readErrorCurr--;
|
|
}
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError($"Eccezione durante UA_ref.ReadNodeString per {item.StartNodeId}{Environment.NewLine}{exc}");
|
|
readErrorCurr++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// gestione ricezione null
|
|
UA_ref.eh_nullExceed += UA_ref_eh_nullExceed;
|
|
// gestione eventi change
|
|
UA_ref.eh_MonItChange += UA_ref_eh_MonItChange;
|
|
lgInfo("eh_MonItChange event registered");
|
|
}
|
|
|
|
// fix tempi!
|
|
DateTime adesso = DateTime.Now;
|
|
lastPzCountSend = adesso;
|
|
lastWarnODL = adesso;
|
|
lastCurrent = adesso;
|
|
lgTrace($"Aggiornato doConnect.lastCurrent: {lastCurrent}");
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError($"Eccezione in doConnect{Environment.NewLine}{exc}");
|
|
}
|
|
|
|
// controllo read error ed eventuale disconnect
|
|
if (readErrorCurr > readErrorMax)
|
|
{
|
|
lgError($"Effettuo disconnessione x superamento errori lettura UA_ref.ReadNodeString, sleep {readErrorSleepTime}msec");
|
|
tryDisconnect();
|
|
Thread.Sleep(readErrorSleepTime);
|
|
readErrorCurr = 0;
|
|
}
|
|
return esitoLink;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Formatta un dataitem x salvataggio in memoria locale
|
|
/// </summary>
|
|
/// <param name="dSamplePeriod"></param>
|
|
/// <param name="threshDBand"></param>
|
|
/// <param name="uuid"></param>
|
|
/// <param name="dataItem"></param>
|
|
/// <returns></returns>
|
|
private OpcUaDataItemExt formatDataItem(ref int dSamplePeriod, ref float threshDBand, ref string uuid, Opc.Ua.Client.MonitoredItem dataItem)
|
|
{
|
|
OpcUaDataItemExt currDataItem;
|
|
// calcolo parametri secondo conf UniqueID
|
|
uuid = calcID($"{dataItem.StartNodeId.Identifier}", $"{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 END indicati...
|
|
foreach (var item in opcUaParams.paramsEndThresh)
|
|
{
|
|
if (uuid.EndsWith(item.Key))
|
|
{
|
|
threshDBand = item.Value;
|
|
}
|
|
}
|
|
}
|
|
if (opcUaParams.paramsContainsThresh.Count > 0)
|
|
{
|
|
// ciclo su tutti i parametri CONTAINS indicati...
|
|
foreach (var item in opcUaParams.paramsContainsThresh)
|
|
{
|
|
if (uuid.Contains(item.Key))
|
|
{
|
|
threshDBand = item.Value;
|
|
}
|
|
}
|
|
}
|
|
dSamplePeriod = samplePeriodBase * 10;
|
|
}
|
|
else
|
|
{
|
|
threshDBand = 0;
|
|
dSamplePeriod = samplePeriodBase;
|
|
}
|
|
lgDebug($"Deadband | item {uuid} | DBand {threshDBand} | samplePeriod {dSamplePeriod}");
|
|
|
|
// salvo oggetto x "uso interno"
|
|
currDataItem = new OpcUaDataItemExt(dataItem)
|
|
{
|
|
uid = uuid,
|
|
thresholdDeadBand = threshDBand,
|
|
samplePeriod = dSamplePeriod
|
|
};
|
|
|
|
return currDataItem;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua invio a MP/IO dell'elenco serializzato dei dataItems
|
|
/// </summary>
|
|
/// <param name="dataItems"></param>
|
|
private void sendDataItemsList(List<machDataItem> dataItems)
|
|
{
|
|
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}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ricevuto troppi null --> disconnetto e traccio
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
/// <exception cref="NotImplementedException"></exception>
|
|
private void UA_ref_eh_nullExceed(object sender, int e)
|
|
{
|
|
lgError($"Effettuo disconnessione x superamento letture UA_ref.ReadNodeString a NULL ({e}), sleep {2 * readErrorSleepTime}msec");
|
|
tryDisconnect();
|
|
Thread.Sleep(2 * readErrorSleepTime);
|
|
readErrorCurr = 0;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Effettua vera scrittura parametri
|
|
/// </summary>
|
|
/// <param name="updatedPar"></param>
|
|
protected override void plcWriteParams(ref List<objItem> updatedPar)
|
|
{
|
|
// solo se connesso!
|
|
if (connectionOk)
|
|
{
|
|
dataConf currMem = null;
|
|
int byteSize = 0;
|
|
string memAddrWrite = "";
|
|
string serObj = "";
|
|
if (updatedPar != null)
|
|
{
|
|
// valori da scrivere in memoria
|
|
Dictionary<string, WriteValue> nodes2Write = new Dictionary<string, WriteValue>();
|
|
Dictionary<string, Dictionary<string, string>> nodesUdt2Write = new Dictionary<string, Dictionary<string, string>>();
|
|
Dictionary<string, string> currUdsDict = new Dictionary<string, string>();
|
|
// controllo i parametri... ne gestisco 4...
|
|
foreach (var item in updatedPar)
|
|
{
|
|
if (connectionOk)
|
|
{
|
|
Opc.Ua.DataValue currNodeVal = new DataValue();
|
|
try
|
|
{
|
|
memAddrWrite = "";
|
|
int valInt = 0;
|
|
double valReal = 0;
|
|
// cerco in area memMapWrite...
|
|
if (memMap.mMapWrite.ContainsKey(item.uid))
|
|
{
|
|
// recupero!
|
|
currMem = memMap.mMapWrite[item.uid];
|
|
byteSize = currMem.size;
|
|
memAddrWrite = currMem.memAddr;
|
|
|
|
// init obj scrittura
|
|
WriteValue commWriteVal = new WriteValue();
|
|
commWriteVal.Value = new DataValue();
|
|
commWriteVal.AttributeId = Attributes.Value;
|
|
|
|
// faccio preliminarmente upsertKey...
|
|
upsertKey(memAddrWrite, currMem.value);
|
|
serObj = JsonConvert.SerializeObject(item);
|
|
lgInfo($"Inizio processing plcWriteParams per {currMem.name} | valore richiesto {currMem.value}");
|
|
lgInfo($"{Environment.NewLine}---------------UPDATED PARAM:{Environment.NewLine}{serObj}{Environment.NewLine}---------------");
|
|
serObj = JsonConvert.SerializeObject(currMem);
|
|
lgInfo($"{Environment.NewLine}---------------MEMORY CONTENT:{Environment.NewLine}{serObj}{Environment.NewLine}---------------");
|
|
|
|
// verifico se devo processare byte[]...
|
|
bool isUds = memAddrWrite.Contains(opcUaParams.kvDelim);
|
|
|
|
if (isUds)
|
|
{
|
|
// ciclo e calcolo valore
|
|
int iPos = memAddrWrite.IndexOf(opcUaParams.kvDelim);
|
|
string nodeId = memAddrWrite.Substring(0, iPos);
|
|
string varName = memAddrWrite.Substring(iPos + 1);
|
|
|
|
//cerco se ci sia già un record Uds...
|
|
if (nodesUdt2Write.ContainsKey(nodeId))
|
|
{
|
|
currUdsDict = nodesUdt2Write[nodeId];
|
|
// controllo valore...
|
|
if (currUdsDict.ContainsKey(varName))
|
|
{
|
|
currUdsDict[varName] = item.reqValue;
|
|
}
|
|
else
|
|
{
|
|
currUdsDict.Add(varName, item.reqValue);
|
|
}
|
|
nodesUdt2Write[nodeId] = currUdsDict;
|
|
}
|
|
else
|
|
{
|
|
currUdsDict = new Dictionary<string, string>();
|
|
currUdsDict.Add(varName, item.reqValue);
|
|
nodesUdt2Write.Add(nodeId, currUdsDict);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// init generico x scrittura nell'oggetto OPC-UA direttamente
|
|
commWriteVal.NodeId = new NodeId(memAddrWrite);
|
|
commWriteVal.Value.Value = item.reqValue;
|
|
|
|
// check tipo specifico
|
|
switch (currMem.tipoMem)
|
|
{
|
|
case plcDataType.Boolean:
|
|
break;
|
|
|
|
case plcDataType.Int:
|
|
case plcDataType.DInt:
|
|
case plcDataType.Word:
|
|
case plcDataType.DWord:
|
|
int.TryParse(item.reqValue, out valInt);
|
|
commWriteVal.Value.Value = valInt;
|
|
break;
|
|
|
|
case plcDataType.Real:
|
|
double.TryParse(item.reqValue, out valReal);
|
|
commWriteVal.Value.Value = valReal;
|
|
break;
|
|
|
|
case plcDataType.String:
|
|
commWriteVal.Value.Value = item.reqValue;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
lgInfo($"---------------{Environment.NewLine}OPC-UA data:{Environment.NewLine}NodeId: {commWriteVal.NodeId}{Environment.NewLine}Value: {commWriteVal.Value.Value}{Environment.NewLine}---------------");
|
|
|
|
// se supero soglia errori lettura --> disconnetto e resetto
|
|
if (numErroriCheck >= maxErroriCheck)
|
|
{
|
|
lgInfo($"numErroriCheck: {numErroriCheck} --> richiesta disconnessione adapter con tryDisconnect");
|
|
|
|
numErroriCheck = 0;
|
|
tryDisconnect();
|
|
}
|
|
else
|
|
{
|
|
if (!string.IsNullOrEmpty(memAddrWrite) && !string.IsNullOrEmpty($"{commWriteVal.NodeId}"))
|
|
{
|
|
// cerco se ci fosse già --> aggiorno
|
|
if (nodes2Write.ContainsKey($"{commWriteVal.NodeId}"))
|
|
{
|
|
nodes2Write[$"{commWriteVal.NodeId}"] = commWriteVal;
|
|
}
|
|
else
|
|
{
|
|
nodes2Write.Add($"{commWriteVal.NodeId}", commWriteVal);
|
|
}
|
|
// 2023.09.19 resetto richiesta
|
|
item.reqValue = "";
|
|
}
|
|
else
|
|
{
|
|
lgInfo($"Errore: memAddrWrite vuoto!");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgInfo($"Errore uid non trovato in area write memory: {item.uid}, ci sono {memMap.mMapWrite.Count} in area write");
|
|
}
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError($"Eccezione in fase di plcWriteParams per item {item.uid} con valore {item.value}{Environment.NewLine}{exc}");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (connectionOk && (nodes2Write.Count + nodesUdt2Write.Count) > 0)
|
|
{
|
|
List<WriteValue> listWriteReq = nodes2Write.Select(x => x.Value).ToList();
|
|
// valori "diretti"
|
|
if (listWriteReq.Count > 0)
|
|
{
|
|
var toWrite = new Dictionary<NodeId, IEnumerable<string>>();
|
|
foreach (var item in listWriteReq)
|
|
{
|
|
toWrite.Add(item.NodeId, new List<string>() { $"{item.Value.Value}" });
|
|
}
|
|
|
|
UA_ref.WriteValues(toWrite);
|
|
// modalità calssica alternativa?!?
|
|
#if false
|
|
UA_ref.WriteNodes(listWriteReq);
|
|
#endif
|
|
}
|
|
// se valori Udt con modalità alternativa
|
|
if (nodesUdt2Write.Count > 0)
|
|
{
|
|
foreach (var item in nodesUdt2Write)
|
|
{
|
|
UA_ref.WriteNodesUdt(item.Key, item.Value);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#endregion Private Methods
|
|
}
|
|
} |