ae748ebeb2
- cablata logica Cà - test su WKST-SAM notturno
1313 lines
53 KiB
C#
1313 lines
53 KiB
C#
using Microsoft.Extensions.Configuration;
|
|
using MP.MONO.Core;
|
|
using MP.MONO.Core.CONF;
|
|
using MP.MONO.Core.DTO;
|
|
using Newtonsoft.Json;
|
|
using NLog;
|
|
using Opc.Ua;
|
|
using Opc.Ua.Configuration;
|
|
using StackExchange.Redis;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net.NetworkInformation;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace MP.MONO.ADAPTER.OPC
|
|
{
|
|
/// <summary>
|
|
/// Classe di comunicazione OPC-UA
|
|
/// </summary>
|
|
public class IobOpcUa : IobGeneric
|
|
{
|
|
#region Public Constructors
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
/// <param name="confPath"></param>
|
|
/// <param name="config"></param>
|
|
public IobOpcUa(string confPath, IConfigurationRoot? config) : base(confPath, config)
|
|
{
|
|
// setup redis
|
|
redisConf = config.GetConnectionString("Redis");
|
|
ConnectionMultiplexer.SetFeatureFlag("preventthreadtheft", true);
|
|
ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(redisConf);
|
|
sub = redis.GetSubscriber();
|
|
redisDb = redis.GetDatabase();
|
|
|
|
confManager = new ConfigManager(redisConf, confPath);
|
|
|
|
msVetoRedCache = config.GetValue<int>("OptPar:VetoSendCache");
|
|
MinWait = config.GetValue<int>("OptPar:MinWait");
|
|
MaxWait = config.GetValue<int>("OptPar:MaxWait");
|
|
DataStaleSec = config.GetValue<int>("OptPar:DataStaleSec");
|
|
AlarmCleanPre = config.GetValue<string>("OptPar:AlarmCleanPre");
|
|
AlarmCleanPost = config.GetValue<string>("OptPar:AlarmCleanPost");
|
|
AlarmTrimWSpace = config.GetValue<bool>("OptPar:AlarmTrimWSpace");
|
|
logInfo($"VetoSendCache: {msVetoRedCache}s | MinWait: {MinWait} | MaxWait: {MaxWait} | AlarmCleanPre: {AlarmCleanPre} | AlarmCleanPost: {AlarmCleanPost}");
|
|
|
|
// gestione data filtering...
|
|
if (!string.IsNullOrEmpty(config.GetValue<string>("ENABLE_DATA_FILTER")))
|
|
{
|
|
bool.TryParse(config.GetValue<string>("ENABLE_DATA_FILTER"), out enableDataFilter);
|
|
}
|
|
// gestione restart OpcUa client...
|
|
if (!string.IsNullOrEmpty(config.GetValue<string>("ENABLE_CLI_RESTART")))
|
|
{
|
|
bool.TryParse(config.GetValue<string>("ENABLE_CLI_RESTART"), out enableCliRestart);
|
|
}
|
|
// init datetime counters
|
|
DateTime adesso = DateTime.Now;
|
|
lastCurrent = adesso;
|
|
// ora leggo il file di conf specifico....
|
|
if (!string.IsNullOrEmpty(cIobConf.ConfFile))
|
|
{
|
|
// leggo il file...
|
|
loadOpcUaConf(cIobConf.ConfFile);
|
|
}
|
|
}
|
|
|
|
#endregion Public Constructors
|
|
|
|
#region Public Properties
|
|
|
|
/// <summary>
|
|
/// Verifica SE si debba fare log periodico (ogni "verboseLogTOut" sec...)
|
|
/// </summary>
|
|
public bool periodicLog
|
|
{
|
|
get
|
|
{
|
|
bool answ = false;
|
|
answ = (DateTime.Now.Subtract(lastPeriodicLog).TotalSeconds > getCRI("verboseLogTOut"));
|
|
if (answ)
|
|
{
|
|
lastPeriodicLog = DateTime.Now;
|
|
}
|
|
|
|
return answ;
|
|
}
|
|
}
|
|
|
|
#endregion Public Properties
|
|
|
|
#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 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 > getCRI("pingTestSec"))
|
|
{
|
|
if (verboseLog || periodicLog)
|
|
{
|
|
logInfo("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
|
|
logInfo($"szStatusConnection OpcUa, esitoLink: {esitoLink}");
|
|
connectionOk = true;
|
|
// refresh stato allarmi!!!
|
|
if (connectionOk)
|
|
{
|
|
if (adpRunning)
|
|
{
|
|
logInfo("Connessione OK");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logError("Impossibile procedere, connessione mancante...");
|
|
}
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
logFatal($"Errore nella connessione all'adapter OpcUa: {szStatusConnection}{Environment.NewLine}{exc}");
|
|
connectionOk = false;
|
|
logInfo($"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)
|
|
{
|
|
logInfo($"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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override disconnessione
|
|
/// </summary>
|
|
public override void tryDisconnect()
|
|
{
|
|
if (connectionOk)
|
|
{
|
|
if (UA_ref != null)
|
|
{
|
|
string szStatusConnection = "";
|
|
try
|
|
{
|
|
UA_ref.Disconnect();
|
|
connectionOk = false;
|
|
logInfo(szStatusConnection);
|
|
logInfo("Effettuata disconnessione adapter OpcUa!");
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
logFatal($"Errore nella disconnessione dall'adapter OpcUa{Environment.NewLine}{exc}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logDebug("IMPOSSIBILE effettuare disconnessione OpcUa: UA_ref non disponibile...");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logError("IMPOSSIBILE effettuare disconnessione OpcUa: Connessione non disponibile...");
|
|
}
|
|
}
|
|
|
|
#endregion Public Methods
|
|
|
|
#region Internal Fields
|
|
|
|
/// <summary>
|
|
/// dataOra ultima verifica CNC disconnesso...
|
|
/// </summary>
|
|
protected DateTime lastDisconnCheck;
|
|
|
|
/// <summary>
|
|
/// Valore soglia in secondi x indicare che i dati sono stale = bloccati/non aggiornati
|
|
/// </summary>
|
|
protected int DataStaleSec = 120;
|
|
|
|
/// <summary>
|
|
/// Verifica se i dati siano stale = incastrati (ultimo dato letto oltre 2 minuti fa) e quindi sia encessario effettuare disconnect/reconnect
|
|
/// </summary>
|
|
public bool dataIsStale
|
|
{
|
|
get => DateTime.Now.Subtract(lastCurrent).TotalSeconds > DataStaleSec;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dizionario della DataOra ultimo invio x ogni valore
|
|
/// </summary>
|
|
internal Dictionary<string, DateTime> LastSend = new Dictionary<string, DateTime>();
|
|
|
|
#endregion Internal Fields
|
|
|
|
#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;
|
|
if (MonIt != null)
|
|
{
|
|
if (NotifyValue != null)
|
|
{
|
|
string sVal = "";
|
|
string descr = "";
|
|
DateTime locTStamp = DateTime.Now;
|
|
descr = itemTranslation("OPC", MonIt.DisplayName);
|
|
sVal = $"C&S | Change: {locTStamp} | descr: {descr} | Id: {MonIt.StartNodeId} | Val: {NotifyValue} | forceSend: {forceSend}";
|
|
logInfo(sVal);
|
|
|
|
// verifico se salvare
|
|
changed = checkSaveValue(MonIt, NotifyValue);
|
|
// cerco se non sia un dato filtrato in FLUXLOG...
|
|
bool isFiltDisplName = opcUaParams.fluxLogVeto.Contains(MonIt.DisplayName);
|
|
bool isFiltNodeId = opcUaParams.fluxLogVeto.Contains($"{MonIt.StartNodeId}");
|
|
if (isFiltDisplName || isFiltNodeId)
|
|
{
|
|
logTrace($"C&S | NON ACCODATO sample per {MonIt.DisplayName} - trovato VETO in fluxLogVeto");
|
|
}
|
|
else
|
|
{
|
|
if (changed || forceSend)
|
|
{
|
|
routeAndSend($"{MonIt.StartNodeId}", $"{stringCleanup(NotifyValue)}");
|
|
}
|
|
else
|
|
{
|
|
logTrace($"C&S | NON ACCODATO sample per {MonIt.StartNodeId} - verifica variazione ha dato esito negativo");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logTrace($"C&S | checkAndSend ERROR | MonIt: {MonIt.StartNodeId} | NotifyValue Null!!!");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logTrace("C&S | 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)
|
|
{
|
|
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}";
|
|
logInfo(sVal);
|
|
|
|
// verifico se salvare
|
|
changed = checkSaveValue(MonIt, currVal) || forceSend;
|
|
// cerco se non sia un dato filtrato in FLUXLOG...
|
|
bool isFiltered = opcUaParams.fluxLogVeto.Contains(MonIt.DisplayName);
|
|
if (isFiltered)
|
|
{
|
|
logTrace($"NON ACCODATO sample per {MonIt.DisplayName} - trovato VETO in fluxLogVeto");
|
|
}
|
|
else
|
|
{
|
|
if (changed || forceSend)
|
|
{
|
|
routeAndSend($"{MonIt.StartNodeId}", $"{NotifyValue}");
|
|
//routeAndSend(sVal, qEncodeFLog(descr, $"{NotifyValue}"));
|
|
}
|
|
else
|
|
{
|
|
logTrace($"NON ACCODATO sample per {MonIt.DisplayName} - verifica variazione ha dato esito negativo");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logError($"checkAndSend ERROR | MonIt: {MonIt.DisplayName} | NotifyValue Null!!!");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logError("checkAndSend ERROR: MonIt null");
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
///// <summary>
|
|
///// Effettua reset del contapezzi, NON POSSIBILE in questa versione
|
|
///// </summary>
|
|
///// <returns></returns>
|
|
//public override bool resetcontapezziPLC()
|
|
//{
|
|
// bool answ = false;
|
|
// return answ;
|
|
//}
|
|
/// <summary>
|
|
/// Processa gestione memoria x invio dati QUANDO IOB è disconnesso da CNC...
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
//#region Internal Methods
|
|
/// <summary>
|
|
/// REDIS: salva in cache + invia tramite canale di notifica
|
|
/// </summary>
|
|
/// <param name="memKey">Scrive su Redis se !=""</param>
|
|
/// <param name="notifyChannel"></param>
|
|
/// <param name="message"></param>
|
|
internal void saveAndSendMessage(string memKey, string notifyChannel, string message)
|
|
{
|
|
// se ho una memoria di cache redis in cui scrivere...
|
|
if (!string.IsNullOrEmpty(memKey))
|
|
{
|
|
// 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]).TotalMilliseconds < msVetoRedCache)
|
|
{
|
|
doSend = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LastSend.Add(memKey, DateTime.Now);
|
|
}
|
|
if (doSend)
|
|
{
|
|
redisDb.StringSetAsync(memKey, message);
|
|
LastSend[memKey] = DateTime.Now;
|
|
logInfo($"S&S | Redis Cache Key: {memKey}");
|
|
}
|
|
}
|
|
|
|
// invio notifica tramite il canale richiesto
|
|
sub.Publish(notifyChannel, message);
|
|
if (verboseLog)
|
|
{
|
|
logInfo($"S&S | [{notifyChannel}] key: {memKey} | val/message: {message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Evento rilevazione modifica valori -->; chiamo checkSend
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
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
|
|
{
|
|
// prendo il valore bonificato dei caratteri che vogliamo ignorare
|
|
currVal = stringCleanup($"{e.CurrNotify.Value}");
|
|
checkAndSend(e.CurrMonitoredItem, currVal, false);
|
|
}
|
|
// aggiorno ultima lettura
|
|
lastCurrent = DateTime.Now;
|
|
}
|
|
|
|
#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>();
|
|
|
|
//#region Protected Fields
|
|
/// <summary>
|
|
/// Abilitazione restart (da opt par...)
|
|
/// </summary>
|
|
protected bool enableCliRestart = false;
|
|
|
|
//#endregion Internal Methods
|
|
/// <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>
|
|
/// Oggetto MAIN x connessione OPC-UA
|
|
/// </summary>
|
|
protected UAClient UA_ref;
|
|
|
|
#endregion Protected Fields
|
|
|
|
#region Protected Properties
|
|
|
|
/// <summary>
|
|
/// Area dati raw per lettura encoded (SE presente)
|
|
/// </summary>
|
|
protected byte[] byteRawData { get; set; } = new byte[1];
|
|
|
|
//#region Protected Properties
|
|
/// <summary>
|
|
/// Indica se si debba leggere un area di tipo "encoded" (byte raw --> obj)
|
|
/// </summary>
|
|
protected bool doByteRead { get; set; } = false;
|
|
|
|
/// <summary>
|
|
/// Parametri specifici OPC-UA
|
|
/// </summary>
|
|
protected OpcUaParamConf opcUaParams { get; set; } = new OpcUaParamConf();
|
|
|
|
#endregion Protected Properties
|
|
|
|
#region Protected Methods
|
|
|
|
/// <summary>
|
|
/// Verifica / Salva valore e restitusice SE sia variato (e quindi da inviare...)
|
|
/// </summary>
|
|
/// <param name="dataItem"></param>
|
|
/// <param name="NotifyValue"></param>
|
|
/// <returns></returns>
|
|
protected bool checkSaveValue(Opc.Ua.Client.MonitoredItem dataItem, string NotifyValue)
|
|
{
|
|
bool answ = !enableDataFilter;
|
|
double oldVal = 0;
|
|
double newVal = 0;
|
|
if (dataItem != null)
|
|
{
|
|
if (NotifyValue != null)
|
|
{
|
|
logTrace($"CSV | Richiesta checkSaveValue | id: {dataItem.StartNodeId} | {dataItem.DisplayName} | Valore: {NotifyValue}");
|
|
// verifico in memoria se ho l'oggetto condition ed il suo valore..
|
|
string uuid = $"{dataItem.StartNodeId}";
|
|
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}")
|
|
{
|
|
logInfo($"CSV | uuid: {uuid} | old Val {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;
|
|
}
|
|
}
|
|
logInfo($"CSV | Test deadband: oldVal: {oldVal} | newVal: {newVal} | esito check: {answ}");
|
|
}
|
|
}
|
|
}
|
|
if (answ)
|
|
{
|
|
// salvo!
|
|
dataItemMem[uuid].value = $"{NotifyValue}";
|
|
dataItemMem[uuid].valueTimestamp = adesso;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// registro non trovato da aggiungere...
|
|
logInfo($"CSV | DataItem non trovato in checkSaveValue: {uuid} | {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);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
logError($"CSV | Eccezione in checkSaveSample{Environment.NewLine}{exc}");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logError("CSV | Attenzione: checkSaveItem con Notify null!");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logError("CSV | Attenzione: checkSaveItem con MonIt null!");
|
|
}
|
|
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(rawVal);
|
|
var bodyVal = ((Opc.Ua.ExtensionObject)wrapVal.Value).Body;
|
|
rawByte = (byte[])bodyVal;
|
|
}
|
|
catch
|
|
{ }
|
|
}
|
|
return rawByte;
|
|
}
|
|
|
|
/// <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 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;
|
|
}
|
|
|
|
/// <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 = Path.Combine(configPath, fileName);
|
|
logInfo($"Apertura file {jsonFullPath}");
|
|
using (StreamReader reader = new StreamReader(jsonFullPath))
|
|
{
|
|
string jsonData = reader.ReadToEnd().Replace("\n", "").Replace("\r", "");
|
|
if (!string.IsNullOrEmpty(jsonData))
|
|
{
|
|
logDebug($"File json composto da {jsonData.Length} caratteri");
|
|
try
|
|
{
|
|
opcUaParams = JsonConvert.DeserializeObject<OpcUaParamConf>(jsonData);
|
|
logDebug($"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();
|
|
// faccio setup degli altri valori...
|
|
setupGroupConf();
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
logError($"Eccezione in decodifica conf json OPC-UA:{Environment.NewLine}{exc}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logError("Errore in loadOpcUaConf: file json vuoto!");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Routed send:
|
|
/// - verifica eventuale destinazione x valore ricevuto (se configurato su 1+ canali) + invia
|
|
/// - traduce (se configurato nei canali) il valore key da uuid (=NodeId) a StringKey
|
|
/// - invia nel generico canale eventi globale
|
|
/// </summary>
|
|
/// <param name="uuid">uuid chiave del parametro rilevato (NodeId), da tradurre se necessario</param>
|
|
/// <param name="rawDataVal">VALORE encoded da trasmettere (come string)</param>
|
|
protected void routeAndSend(string uuid, string rawDataVal)
|
|
{
|
|
// cerco se sia nella conf x invio in specifico canale RAW
|
|
|
|
// Activity Log
|
|
if (ActLogKList.Contains(uuid))
|
|
{
|
|
// recupero valore e verifico se sia numerico...
|
|
bool numOk = false;
|
|
double updVal = 0;
|
|
numOk = double.TryParse(rawDataVal, out updVal);
|
|
// aggiorno nella lista dei valori quello cambiato...
|
|
var currItem = ActLogStatus.FirstOrDefault(x => x.ExtCode == uuid);
|
|
if (numOk && currItem.IsNumeric)
|
|
{
|
|
currItem.ValueNum = updVal;
|
|
currItem.Value = updVal.ToString(currItem.DisplFormat);
|
|
}
|
|
else
|
|
{
|
|
currItem.Value = rawDataVal;
|
|
}
|
|
logTrace($"R&S | ActLogKList | {uuid} | {currItem.Value}");
|
|
// prendo tutti i valori del gruppo e li serializzo come DisplayDataDTO
|
|
string payloadActLog = JsonConvert.SerializeObject(ActLogStatus);
|
|
// invio
|
|
saveAndSendMessage(Constants.ACT_LOG_CURR_KEY, Constants.ACT_LOG_M_QUEUE, payloadActLog);
|
|
}
|
|
|
|
// Tools
|
|
if (ToolKList.Contains(uuid))
|
|
{
|
|
// recupero valore e verifico se sia numerico...
|
|
bool numOk = false;
|
|
double updVal = 0;
|
|
numOk = double.TryParse(rawDataVal, out updVal);
|
|
// aggiorno nella lista dei valori quello cambiato...
|
|
var currItem = ToolStatus.FirstOrDefault(x => x.ExtCode == uuid);
|
|
if (numOk && currItem != null && currItem.IsNumeric)
|
|
{
|
|
currItem.ValueNum = updVal;
|
|
currItem.Value = updVal.ToString(currItem.DisplFormat);
|
|
}
|
|
else
|
|
{
|
|
currItem.Value = rawDataVal;
|
|
}
|
|
logTrace($"R&S | ToolKList | {uuid} | {currItem.Value}");
|
|
|
|
#if false
|
|
// prendo tutti i valori del gruppo e li serializzo come DisplayDataDTO
|
|
string payloadTools = JsonConvert.SerializeObject(ToolStatus);
|
|
// invio
|
|
saveAndSendMessage(Constants.TOOLS_CURR_KEY, Constants.TOOLS_M_QUEUE, payloadTools);
|
|
#endif
|
|
|
|
// genero un dictionary di valori stringa / double e lo serializzo...
|
|
var dataDict = ToolStatus.ToDictionary(x => x.Title, x => x.Value);
|
|
string payloadTools = JsonConvert.SerializeObject(dataDict);
|
|
// invio
|
|
saveAndSendMessage(Constants.TOOLS_ACT_KEY, Constants.TOOLS_RAW_QUEUE, payloadTools);
|
|
}
|
|
|
|
// Parameters
|
|
if (ParamKList.Contains(uuid))
|
|
{
|
|
// recupero valore e verifico se sia numerico...
|
|
bool numOk = false;
|
|
double updVal = 0;
|
|
numOk = double.TryParse(rawDataVal, out updVal);
|
|
// aggiorno nella lista dei valori quello cambiato...
|
|
var currItem = ParamStatus.FirstOrDefault(x => x.ExtCode == uuid);
|
|
if (numOk && currItem.IsNumeric)
|
|
{
|
|
currItem.ValueNum = updVal;
|
|
currItem.Value = updVal.ToString(currItem.DisplFormat);
|
|
}
|
|
else
|
|
{
|
|
currItem.Value = rawDataVal;
|
|
}
|
|
logTrace($"R&S | ParamKList | {uuid} | {currItem.Value}");
|
|
|
|
// genero un dictionary di valori stringa / double e lo serializzo...
|
|
var dataDict = ParamStatus.ToDictionary(x => x.Title, x => x.Value);
|
|
string payloadParams = JsonConvert.SerializeObject(dataDict);
|
|
// invio
|
|
saveAndSendMessage(Constants.PARAMS_ACT_KEY, Constants.PARAMS_RAW_QUEUE, payloadParams);
|
|
#if false
|
|
// parametri: inviati come DTO
|
|
string payloadParams = JsonConvert.SerializeObject(ParamStatus);
|
|
// invio
|
|
saveAndSendMessage(Constants.PARAMS_CURR_KEY, Constants.PARAMS_M_QUEUE, payloadParams);
|
|
#endif
|
|
}
|
|
|
|
// Allarmi
|
|
if (AlarmsKList.Contains(uuid))
|
|
{
|
|
// allarmi: effettuo PRE processing x filtrare pre/post { }
|
|
if (!string.IsNullOrEmpty(AlarmCleanPre))
|
|
{
|
|
rawDataVal = rawDataVal.Replace(AlarmCleanPre, "");
|
|
}
|
|
if (!string.IsNullOrEmpty(AlarmCleanPost))
|
|
{
|
|
rawDataVal = rawDataVal.Replace(AlarmCleanPost, "");
|
|
}
|
|
if (AlarmTrimWSpace && string.IsNullOrWhiteSpace(rawDataVal))
|
|
{
|
|
rawDataVal = rawDataVal.Trim();
|
|
}
|
|
// aggiorno nella lista dei valori quello cambiato...
|
|
var currItem = AlarmsStatus.FirstOrDefault(x => x.ExtCode == uuid);
|
|
currItem.Value = rawDataVal;
|
|
// salvo in redis!
|
|
redisDb.StringSet(Constants.ALARMS_ADAP_CONF_KEY, JsonConvert.SerializeObject(AlarmsStatus));
|
|
// genero elenco allarmi attivi
|
|
List<string> adpActiveAlarm = AlarmsStatus
|
|
.Select(x => x.Value)
|
|
.Where(x => !string.IsNullOrEmpty(x))
|
|
.OrderBy(x => x)
|
|
.ToList();
|
|
|
|
logTrace($"R&S | AlarmsKList | {uuid} | {currItem.Value}");
|
|
var payloadAlarms = JsonConvert.SerializeObject(adpActiveAlarm);
|
|
// invio
|
|
saveAndSendMessage(Constants.ALARM_ACT_KEY, Constants.ALARM_RAW_QUEUE, payloadAlarms);
|
|
}
|
|
|
|
// Stato macchina / processo
|
|
if (MPKList.Contains(uuid))
|
|
{
|
|
// recupero valore e verifico se sia numerico...
|
|
bool numOk = false;
|
|
double updVal = 0;
|
|
numOk = double.TryParse(rawDataVal, out updVal);
|
|
// aggiorno nella lista dei valori quello cambiato...
|
|
var currItem = MPStatus.FirstOrDefault(x => x.ExtCode == uuid);
|
|
if (numOk && currItem.IsNumeric)
|
|
{
|
|
currItem.ValueNum = updVal;
|
|
currItem.Value = updVal.ToString(currItem.DisplFormat);
|
|
}
|
|
else
|
|
{
|
|
currItem.Value = rawDataVal;
|
|
}
|
|
logTrace($"R&S | MPKList | {uuid} | {currItem.Value}");
|
|
// genero un dictionary di valori stringa / double e lo serializzo...
|
|
var dataDict = MPStatus.ToDictionary(x => x.Title, x => x.Value);
|
|
string payloadParams = JsonConvert.SerializeObject(dataDict);
|
|
// invio
|
|
saveAndSendMessage(Constants.STATUS_ACT_KEY, Constants.STATUS_RAW_QUEUE, payloadParams);
|
|
}
|
|
|
|
// Dati raw x counters
|
|
if (CountKList.Contains(uuid))
|
|
{
|
|
// recupero valore e verifico se sia numerico...
|
|
bool numOk = false;
|
|
double updVal = 0;
|
|
numOk = double.TryParse(rawDataVal, out updVal);
|
|
// aggiorno nella lista dei valori quello cambiato...
|
|
var currItem = CountStatus.FirstOrDefault(x => x.ExtCode == uuid);
|
|
if (numOk && currItem.IsNumeric)
|
|
{
|
|
currItem.ValueNum = updVal;
|
|
currItem.Value = updVal.ToString(currItem.DisplFormat);
|
|
}
|
|
else
|
|
{
|
|
currItem.Value = rawDataVal;
|
|
}
|
|
logTrace($"R&S | CountKList | {uuid} | {currItem.Value}");
|
|
// genero un dictionary di valori stringa / double e lo serializzo...
|
|
var dataDict = CountStatus.ToDictionary(x => x.Title, x => x.Value);
|
|
string payloadParams = JsonConvert.SerializeObject(dataDict);
|
|
// invio
|
|
saveAndSendMessage(Constants.COUNT_CURR_KEY, Constants.COUNT_RAW_QUEUE, payloadParams);
|
|
}
|
|
}
|
|
|
|
#endregion Protected Methods
|
|
|
|
#region Private Fields
|
|
|
|
private static string redisConf = "";
|
|
|
|
/// <summary>
|
|
/// Char x bonifica stringa allarmi - POST
|
|
/// </summary>
|
|
private string AlarmCleanPost = "";
|
|
|
|
/// <summary>
|
|
/// Char x bonifica stringa allarmi - PRE
|
|
/// </summary>
|
|
private string AlarmCleanPre = "";
|
|
|
|
/// <summary>
|
|
/// Indica se forzare trim dei whitespace da allarmi
|
|
/// </summary>
|
|
private bool AlarmTrimWSpace = false;
|
|
|
|
private Logger Log = LogManager.GetCurrentClassLogger();
|
|
|
|
/// <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 Properties
|
|
|
|
// // decodifica e gestione
|
|
// decodeToBaseBitmap();
|
|
// reportRawInput(ref currDispData);
|
|
// }
|
|
// catch (Exception exc)
|
|
// {
|
|
// currDispData.semIn = Semaforo.SR;
|
|
// logError($"Eccezione in readSemafori:{Environment.NewLine}{exc}");
|
|
// }
|
|
// // se > max errori --> disconnetto
|
|
// if (currReadErrors > maxReadErrors)
|
|
// {
|
|
// logError($"Superato limite errori Read ({currReadErrors}) --> tryDisconnect");
|
|
// currReadErrors = 0;
|
|
// tryDisconnect();
|
|
// }
|
|
// else
|
|
// {
|
|
// // altrimenti pausa forzata
|
|
// Thread.Sleep(300);
|
|
// }
|
|
//}
|
|
//#endregion Public Methods
|
|
/// <summary>
|
|
/// Elenco chiavi dei parametri gestiti come ActConf
|
|
/// </summary>
|
|
private List<string> ActLogKList { get; set; } = new List<string>();
|
|
|
|
// // 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++; } } } }
|
|
/// <summary>
|
|
/// Elenco oggetti DisplayDTO da gestire come ActLog
|
|
/// </summary>
|
|
private List<DisplayDataDTO>? ActLogStatus { get; set; } = new List<DisplayDataDTO>();
|
|
|
|
/// <summary>
|
|
/// Elenco chiavi dei parametri gestiti come Allarmi
|
|
/// </summary>
|
|
private List<string> AlarmsKList { get; set; } = new List<string>();
|
|
|
|
/// <summary>
|
|
/// Elenco oggetti DisplayDTO da gestire come Allarmi
|
|
/// </summary>
|
|
private List<DisplayDataDTO>? AlarmsStatus { get; set; } = new List<DisplayDataDTO>();
|
|
|
|
private ConfigManager confManager { get; set; }
|
|
|
|
/// <summary>
|
|
/// Elenco chiavi dei parametri relativi ai contatori
|
|
/// </summary>
|
|
private List<string> CountKList { get; set; } = new List<string>();
|
|
|
|
/// <summary>
|
|
/// Elenco oggetti DisplayDTO relativi ai contatori
|
|
/// </summary>
|
|
private List<DisplayDataDTO>? CountStatus { get; set; } = new List<DisplayDataDTO>();
|
|
|
|
/// <summary>
|
|
/// Attesa massima (es x discovery)
|
|
/// </summary>
|
|
private int MaxWait { get; set; } = 250;
|
|
|
|
/// <summary>
|
|
/// Attesa minima (es x discovery)
|
|
/// </summary>
|
|
private int MinWait { get; set; } = 50;
|
|
|
|
/// <summary>
|
|
/// Elenco chiavi dei parametri relativi stato macchina/processo
|
|
/// </summary>
|
|
private List<string> MPKList { get; set; } = new List<string>();
|
|
|
|
/// <summary>
|
|
/// Elenco oggetti DisplayDTO relativi stato macchina/processo
|
|
/// </summary>
|
|
private List<DisplayDataDTO>? MPStatus { get; set; } = new List<DisplayDataDTO>();
|
|
|
|
/// <summary>
|
|
/// Durata in ms x veto (ri)scrittura verso REDIS cache
|
|
/// </summary>
|
|
private int msVetoRedCache { get; set; } = 300;
|
|
|
|
/// <summary>
|
|
/// Elenco chiavi dei parametri gestiti come Params
|
|
/// </summary>
|
|
private List<string> ParamKList { get; set; } = new List<string>();
|
|
|
|
/// <summary>
|
|
/// Elenco oggetti DisplayDTO da gestire come Params
|
|
/// </summary>
|
|
private List<DisplayDataDTO>? ParamStatus { get; set; } = new List<DisplayDataDTO>();
|
|
|
|
/// <summary>
|
|
/// Redis DB
|
|
/// </summary>
|
|
private IDatabase? redisDb { get; set; }
|
|
|
|
/// <summary>
|
|
/// Redis channel manager
|
|
/// </summary>
|
|
private ISubscriber sub { get; set; }
|
|
|
|
/// <summary>
|
|
/// Elenco chiavi dei parametri gestiti come Tool
|
|
/// </summary>
|
|
private List<string> ToolKList { get; set; } = new List<string>();
|
|
|
|
/// <summary>
|
|
/// Elenco oggetti DisplayDTO da gestire come Tool
|
|
/// </summary>
|
|
private List<DisplayDataDTO>? ToolStatus { get; set; } = new List<DisplayDataDTO>();
|
|
|
|
#endregion Private Properties
|
|
|
|
///// <summary>
|
|
///// Periodo massimo (in sec) per letture dati RAW in mancanza di eventi
|
|
///// </summary>
|
|
//protected int lastCurrentMaxElapsed { get; set; } = 120;
|
|
|
|
#region Private Methods
|
|
|
|
/// <summary>
|
|
/// Vera connessione ad OpcUa
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private async Task<short> doConnect()
|
|
{
|
|
short esitoLink = 0;
|
|
// reset memoria dataItem..
|
|
dataItemMem = new Dictionary<string, OpcUaDataItemExt>();
|
|
// ora avvio
|
|
try
|
|
{
|
|
logInfo("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);
|
|
|
|
logInfo($"New UAClient stasrted with std config: {application.ApplicationConfiguration.ApplicationName}");
|
|
UA_ref = new UAClient(application.ApplicationConfiguration, opcUaParams.Identity.UserName, opcUaParams.Identity.Passwd, ClientBase.ValidateResponse);
|
|
// se nullo calcolo il fullUrl
|
|
if (string.IsNullOrEmpty(cIobConf.FullUrl))
|
|
{
|
|
cIobConf.FullUrl = $"opc.tcp://{cIobConf.IpAddress}:{cIobConf.Port}";
|
|
}
|
|
logInfo($"Call to OpcUa Server at address: {cIobConf.FullUrl}");
|
|
UA_ref.ServerUrl = cIobConf.FullUrl;
|
|
//logInfo($"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<string, string> nodeIdNameList = new Dictionary<string, string>();
|
|
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...
|
|
logDebug("---------- AVAILABLE FOR SUBSCRIBE ----------");
|
|
foreach (var item in nodeIdNameList)
|
|
{
|
|
logDebug(item.Key);
|
|
}
|
|
logDebug("---------- 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
|
|
{
|
|
logDebug($"subscribedItems non trovato: {currItem}");
|
|
}
|
|
}
|
|
logDebug($"Aggiunti {selectedItemList.Count} items!");
|
|
}
|
|
// altrimenti tutti!
|
|
else
|
|
{
|
|
selectedItemList = nodeIdNameList;
|
|
}
|
|
|
|
// loggo elenco degli item sottocrivibili...
|
|
logInfo("---------- SUBSCRIBED NODES ----------");
|
|
foreach (var item in selectedItemList)
|
|
{
|
|
logInfo(item.Key);
|
|
}
|
|
logInfo("---------- END LIST ----------");
|
|
|
|
// sottoscrivo a rilevazione cambio dati solo l'incrocio degli insiemi
|
|
List<Opc.Ua.Client.MonitoredItem> subscribedItems = UA_ref.SubscribeToDataChanges(selectedItemList);
|
|
// aggiungo come DataItems
|
|
int dSamplePeriod = 0;
|
|
int threshDBand = 0;
|
|
string uuid = "";
|
|
Random rnd = new Random();
|
|
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))
|
|
{
|
|
logDebug($"CONN | Item ALREADY subscribed: {uuid} | NOT re-adding");
|
|
}
|
|
else
|
|
{
|
|
dataItemMem.Add(uuid, newItem);
|
|
logDebug($"CONN | 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);
|
|
var fixString = stringCleanup(currVal);
|
|
changed = checkAndSend(item, fixString, true);
|
|
}
|
|
}
|
|
// attesa x evitare problemi
|
|
logDebug(" --------------- ");
|
|
Thread.Sleep(rnd.Next(MinWait, MaxWait));
|
|
}
|
|
// gestione eventi change
|
|
UA_ref.eh_MonItChange += UA_ref_eh_MonItChange;
|
|
logInfo("CONN | eh_MonItChange event registered");
|
|
}
|
|
|
|
esitoLink = 1;
|
|
|
|
// fix tempi!
|
|
DateTime adesso = DateTime.Now;
|
|
lastCurrent = adesso;
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
logError($"Eccezione in doConnect{Environment.NewLine}{exc}");
|
|
}
|
|
return esitoLink;
|
|
}
|
|
|
|
//#region Private Methods
|
|
/// <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 int threshDBand, ref string uuid, Opc.Ua.Client.MonitoredItem dataItem)
|
|
{
|
|
OpcUaDataItemExt currDataItem;
|
|
// calcolo parametri
|
|
uuid = dataItem.StartNodeId.ToString();
|
|
// 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua setup delle conf dei gruppi di dati
|
|
/// </summary>
|
|
private void setupGroupConf()
|
|
{
|
|
// leggo e salvo conf varie (dati x decodifica e gestione)
|
|
|
|
ActLogStatus = confManager.getActLog();
|
|
ActLogKList = ActLogStatus.Select(x => x.ExtCode).ToList();
|
|
|
|
ToolStatus = confManager.getTools();
|
|
ToolKList = ToolStatus.Select(x => x.ExtCode).ToList();
|
|
|
|
ParamStatus = confManager.getParamsConf();
|
|
ParamKList = ParamStatus.Select(x => x.ExtCode).ToList();
|
|
|
|
AlarmsStatus = confManager.getAlarmsConf();
|
|
AlarmsKList = AlarmsStatus.Select(x => x.ExtCode).ToList();
|
|
|
|
MPStatus = confManager.getMPStatusConf();
|
|
MPKList = MPStatus.Select(x => x.ExtCode).ToList();
|
|
|
|
CountStatus = confManager.getCountersConf();
|
|
CountKList = CountStatus.Select(x => x.ExtCode).ToList();
|
|
}
|
|
|
|
#endregion Private Methods
|
|
}
|
|
} |