Files
Mapo-IOB-WIN/IOB-WIN-FORM/Iob/Generic.cs
T
Samuele Locatelli bcce68c93e Correzioni namespace
2026-05-20 15:29:48 +02:00

10173 lines
409 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using EgwProxy.Ftp;
using IOB_UT_NEXT;
using IOB_UT_NEXT.Config;
using IOB_WIN_FORM.Iob.Services;
using MapoSDK;
using MathNet.Numerics.Statistics;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using NLog.Targets;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.AccessControl;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using YamlDotNet.Core.Tokens;
using static IOB_UT_NEXT.BaseAlarmConf;
using static IOB_UT_NEXT.CustomObj;
using static IOB_UT_NEXT.DataModel.Fimat;
using static MapoSDK.WharehouseData;
namespace IOB_WIN_FORM.Iob
{
public partial class Generic : BaseObj
{
#region Public Fields
public int numPzReqOdl = 0;
#endregion Public Fields
#region Public Constructors
/// <summary>
/// inizializzo l'oggetto sulla form SULLA BASE DEL FILE DI CONFIGURAZIONE letto
/// </summary>
/// <param name="caller">Form chiamante</param>
/// <param name="IobConfNew">Configurazione (v 4.x)</param>
public Generic(AdapterForm caller, IobConfTree IobConfNew)
{
// salvo il form chiamante
parentForm = caller;
if (IobConfNew != null)
{
// salvo configurazione...
IOBConfFull = IobConfNew;
// init oggetto redis...
redisMan = new RedisIobCache(IobConfNew.MapoMes.IpAddr, IobConfNew.General.FilenameIOB, $"{IobConfNew.General.IobType}", IobConfNew.General.MinDeltaSec);
// init RedisService per astrarre le chiamate alla cache
redisService = new RedisService(redisMan, IobConfNew.General.FilenameIOB, IobConfNew.General.IobType);
// init code
SetupQueue();
// initi oggetto TCMan
tcMan = new TCMan(IobConfNew.TCDataConf.Lambda, IobConfNew.TCDataConf.MaxDelayFactor, IobConfNew.TCDataConf.MaxIncrPz);
lastConnectTry = DateTime.Now;
lgInfo("Avvio preliminare AdapterGeneric");
lastLogStartup = DateTime.Now;
// setup currProdData & last prod data
currProdData = redisMan.redGetHashDict(rKeyCurrProdData);
// i last li avvio a VUOTI... x evitare errore mancata riscrittura SIMEC che ha memorie NON ritentive
lastProdData = new Dictionary<string, string>();
//lastProdData = new Dictionary<string, string>(currProdData);
// aggiungo altri defaults
setDefaults(true);
// imposta valori memoria (e resetta parametri su server)
setParamPlc();
// checkLogDir x shrink!
checkShrinkDir();
// imposto veto invio per i prossimi sec
dtVetoQueueIN = DateTime.Now.AddSeconds(vetoQueueIn);
string msgVeto = $"Impostato veto QUEUE-IN a {vetoQueueIn}s | fino alle {dtVetoQueueIN:yyyy.MM.dd HH:mm:ss}";
lgInfoStartup(msgVeto);
lgInfo(msgVeto);
// invio info IOB
SendM2IOB();
// invio altri dati accessori...
SendMachineConf();
if (resetAlarmOnStart)
{
SendAlarmReset();
}
// concluso!
lgInfoStartup("Istanziata classe preliminare IOBGeneric");
}
else
{
lgError("Error: IobCOnf is null!");
}
}
#endregion Public Constructors
#region Public Events
/// <summary>
/// Evento Iob ha subito un refresh
/// </summary>
public event EventHandler<iobRefreshedEventArgs> eh_refreshed;
#endregion Public Events
#region Public Properties
/// <summary>
/// Salva verifica stato connessione OK con macchina (PLC/CNC)
/// </summary>
/// <returns></returns>
public virtual bool connectionOk
{
get
{
return _connOk || DemoIn;
}
set
{
_connOk = value;
}
}
/// <summary>
/// Contapezzi attuale
/// </summary>
public Int32 contapezziIOB
{
get
{
return tcMan.pzCountIOB;
}
set
{
tcMan.pzCountIOB = value;
}
}
/// <summary>
/// Ultima lettura variabile contapezzi da CNC
/// </summary>
public Int32 contapezziPLC
{
get
{
return tcMan.pzCountPLC;
}
set
{
tcMan.pzCountPLC = value;
}
}
/// <summary>
/// Contatore x invio dati FluxLog
/// </summary>
public int counterFLog { get; set; }
/// <summary>
/// Contatore x invio dati RawTransf
/// </summary>
public int counterRawTransf { get; set; }
/// <summary>
/// Contatore x invio dati SignalIN
/// </summary>
public int counterSigIN { get; set; }
/// <summary>
/// Contatore x invio dati UserLog
/// </summary>
public int counterULog { get; set; }
/// <summary>
/// nome Programma corrente
/// </summary>
public string currPrgName { get; set; }
/// <summary>
/// Verifica se sia in modalità DEMO --&gt; da tipo IOB SIMULA...
/// </summary>
public bool DemoIn
{
get => IOBConfFull.General.IobType == tipoAdapter.SIMULA;
}
/// <summary>
/// Dizionario contapezzi Macchina (valori da impianto) x macchine multi tavola/pallet
/// </summary>
public Dictionary<string, int> DictPzCountImp { get; set; } = new Dictionary<string, int>();
/// <summary>
/// Dizionario contapezzi MES (valori salvati su server) x macchine multi tavola/pallet
/// </summary>
public Dictionary<string, int> DictPzCountMes { get; set; } = new Dictionary<string, int>();
/// <summary>
/// Indica se la chiamata WDST dit racking watchdog sia disabilitata dall'invio nel FluxLog
/// </summary>
public bool disableWdst { get; set; } = false;
/// <summary>
/// Indica lo stato Online/Offline della IOB
/// </summary>
public bool IobOnline
{
get
{
return utils.IOB_Online;
}
set
{
utils.IOB_Online = value;
}
}
/// <summary>
/// Verifica se sia macchina multi = DoppioPallet da CONF
/// </summary>
public bool isMulti
{
get => IOBConfFull.Device.IsMulti;
}
/// <summary>
/// Log verboso da configurazione (SOLO CHIAVE "verbose"...)
/// </summary>
public bool isVerboseLog { get; set; } = utils.CRB("verbose");
/// <summary>
/// Ultimo Alarm letto
/// </summary>
public string lastAlarm { get; set; }
/// <summary>
/// Ultimo ARRAY DynData letto
/// </summary>
public Dictionary<string, string> lastDynData { get; set; } = new Dictionary<string, string>();
/// <summary>
/// Ultimo DynData (sunto) letto
/// </summary>
public string lastDynDataCtrlVal { get; set; }
/// <summary>
/// Ultimo Override set letto
/// </summary>
public string lastOverrideFS { get; set; }
/// <summary>
/// Ultimo Override set letto
/// </summary>
public string lastOverrideRapid { get; set; }
/// <summary>
/// Ultimo programma letto
/// </summary>
public string lastPrgName { get; set; }
/// <summary>
/// Ultimo SysInfo letto
/// </summary>
public string lastSysInfo { get; set; }
/// <summary>
/// Ultimo URL
/// </summary>
public string lastUrl { get; set; }
/// <summary>
/// Valore massimo accettato x incremento pezzi letti dal PLC (valore moltiplicato per
/// 100... 200% --&gt; 200)
/// </summary>
public int maxPzDeltaPerc
{
get => IOBConfFull.Counters.MaxIncrPzCountPerc;
}
/// <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 > utils.CRI("verboseLogTOut"));
if (answ)
{
lastPeriodicLog = DateTime.Now;
}
return answ;
}
}
/// <summary>
/// Valore medio del TC rilevato x verifica derive sul delta variazione contapezzi
/// </summary>
public double plcAvgTc
{
get
{
double answ = tcMan.avgTC > 0 ? tcMan.avgTC : 1;
return answ;
}
}
/// <summary>
/// DataOra dell'ultima lettura variabile contapezzi da CNC
/// </summary>
public DateTime plcLastPzRead
{
get
{
return tcMan.lastObservedData;
}
}
/// <summary>
/// Determina se il contapezzi plc sia valido (lo è se data avvio adapter è prima di ultimo dato registrato in contapezzi)
/// </summary>
public bool plcPzCountValid
{
get => tcMan.lastObservedData > dtAvvioAdp;
}
/// <summary>
/// Abilitazione coda segnali ingresso
/// </summary>
public bool queueInEnabCurr
{
get => qInEnabCurr;
set
{
qInEnabCurr = value;
lgInfo($"SET queueInEnabCurr: {value} | {DateTime.Now:HHmmss}");
}
}
/// <summary>
/// Finestra dei byte da mostrare di default x il RawDataInput
/// </summary>
public int RawDataInputSize { get; set; } = 8;
/// <summary>
/// Indice di partenza della memoria RawData Input da mostrare
/// </summary>
public int RawDataInputStart { get; set; } = 0;
/// <summary>
/// URL per segnalazione reboot...
/// </summary>
public string urlReboot
{
get => $@"{urlCommandIobFile("sendReboot")}?mac={GetMACAddress()}";
}
/// <summary>
/// URL per salvataggio dati conf YAML completi IOB...
/// </summary>
public string urlSaveConfYaml
{
get => $@"{urlCommandIobFile("saveConfYaml")}";
}
/// <summary>
/// Verifica SE si debba fare log verboso (verboso + ogni tot letture IN)
/// </summary>
public bool verboseLog
{
get
{
bool answ = false;
int logEvery = utils.CRI("logEvery");
if (logEvery < 1)
{
logEvery = 10;
}
answ = utils.CRB("verbose") && (nReadIN % logEvery == 0);
return answ;
}
}
#endregion Public Properties
#region Public Methods
/// <summary>
/// Esegue conversione in un dizionario di tipo string/string serializzando e deserializzando
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static Dictionary<string, string> ConvertToStringDict(Dictionary<string, object> input)
{
return DataSerializer.ToDictionary(input);
}
/// <summary>
/// Accumula in coda i valori ALARM e logga...
/// </summary>
/// <param name="val">VALORE RAW (x display)</param>
/// <param name="encodedVal">VALORE già processato con qEncodeFLog(...)</param>
public void accodaAlarmLog(string val, string encodedVal)
{
// mostro dati variati letti...
displayOtherData(val);
// accodo IN PRIMIS al FluxLog --> accodo (valore già formattato)!
QueueFLog.Enqueue(encodedVal);
// accodo ANCHE alla coda allarmi che trasmetterò SOLO SE a scadenza impostata (10 sec?)
// ho allarmi perdurati...
// loggo!
lgInfo(string.Format("[QUEUE-ALARM-LOG] {0}", encodedVal));
counterFLog++;
if (counterFLog > 9999)
{
counterFLog = 0;
}
}
/// <summary>
/// Accumula in coda i valori Flux Log e logga. Restituisce true se inviato (x track su REDIS)
/// </summary>
/// <param name="codFlux">Nome del flusso da inviare (per verifica veto invio)</param>
/// <param name="val">VALORE RAW (x display)</param>
/// <param name="encodedVal">VALORE già processato con qEncodeFLog(...)</param>
public bool accodaFLog(string codFlux, string val, string encodedVal)
{
bool enabled = false;
// verifico se il parametro sia abilitato...
if (IOBConfFull.Memory != null && IOBConfFull.Memory.mMapRead != null && IOBConfFull.Memory.mMapRead.ContainsKey(codFlux))
{
enabled = IOBConfFull.Memory.mMapRead[codFlux].sendEnabled;
if (!enabled)
{
lgDebug($"accodaFLog : invio bloccato per conf parametro sendEnabled | codFlux: {codFlux}");
}
else
{
// processo SOLO SE ho dei valori non nulli x encodedVal...
if (!string.IsNullOrEmpty(encodedVal))
{
// mostro dati variati letti...
displayOtherData(val);
// --> accodo (valore già formattato)!
QueueFLog.Enqueue(encodedVal);
// se abilitato controllo coda FLog (superiore a 0...)
if (maxQueueFLog > 0)
{
// se ho una coda superiore a max ammesso
if (QueueFLog.Count > maxQueueFLog)
{
// elimino valori iniziali fino a tornare al max ammesso...
while (QueueFLog.Count > maxQueueFLog)
{
string currVal = "";
QueueFLog.TryDequeue(out currVal);
lgInfo($"Eliminazione da coda FLog per superamento maxLengh: {currVal}");
}
}
}
// loggo!
lgTrace($"[QUEUE-FLOG] {encodedVal}");
counterFLog++;
if (counterFLog > 9999)
{
counterFLog = 0;
}
}
else
{
lgTrace($"ERRORE in [QUEUE-FLOG] : encodedVal vuoto | val: {val}");
}
}
}
else
{
// registro nel dizionario dei valori FluxLog filtrati
SaveFiltFluxLog(codFlux);
// verifico se registrare elenco valori filtrati in log...
FiltFluxLogCheckSave(100);
}
return enabled;
}
/// <summary>
/// Accoda (visualizzando in cima allo stack) la nuova stringa di output per area OTHER DATA
/// </summary>
/// <param name="newLine"></param>
public void accodaOtherData(string newLine)
{
// inserisco in cima allo stack
parentForm.WriteTextSafe(newLine);
}
/// <summary>
/// Accumula in coda i valori RawData + log
/// </summary>
/// <param name="mesType"></param>
/// <param name="mesContent"></param>
public void accodaRawData(rawTransfType mesType, object mesContent)
{
/*--------------------------------
* nuova gestione coda dictionary
* fixme todo da fare !!!
*
* - conterrà una lista di oggetti baseRawTransf
* - i dati vanno poi "scodati" dal + vecchio ed inviati a MP/IO
* - mostra un sunto delle info da inviare
* - accodamento vero e proprio
* - verifica (opzionale) coda massima x gestire roundRobin ultimi eventi
* - trace della coda
* - counter invio??? valutare se c'è dataora e poi sono da salvare su MongoDb / Redis
*
* */
// serializzo il valore...
JObject njObj;
if (mesType == rawTransfType.IcoelBatch || mesType == rawTransfType.IcoelVarInfo)
{
njObj = (JObject)mesContent;
}
else
{
njObj = (JObject)JToken.FromObject(mesContent);
}
BaseRawTransf newVal = new BaseRawTransf(DateTime.Now, njObj, mesType);
string encodedVal = DataSerializer.Serialize(newVal);
// --> accodo (valore già formattato)!
QueueRawTransf.Enqueue(encodedVal);
// se abilitato controllo coda Max (superiore a 0...)
if (maxQueueRawTransf > 0)
{
// se ho una coda superiore a max ammesso
if (QueueRawTransf.Count > maxQueueRawTransf)
{
// elimino valori iniziali fino a tornare al max ammesso...
while (QueueRawTransf.Count > maxQueueRawTransf)
{
string currVal = "";
QueueRawTransf.TryDequeue(out currVal);
lgInfo($"Eliminazione da coda RawTransf per superamento maxLengh: {currVal}");
}
}
}
// loggo!
lgTrace(string.Format("[QUEUE-RTRANSF] {0}", encodedVal));
counterRawTransf++;
if (counterRawTransf > 9999)
{
counterRawTransf = 0;
}
}
/// <summary>
/// Accumula in coda i valori Signal IN e logga...
/// <paramref name="currDispData">Parametri da aggiornare x display in form</paramref>
/// </summary>
public void accodaSigIN(ref newDisplayData currDispData)
{
DateTime adesso = DateTime.Now;
lgDebug($"accodaSigIN 01 | qEncodeIN: {qEncodeIN}");
// mostro dati variati letti...
displayInData(ref currDispData);
// verifico veto a invio status macchina
if (IOBConfFull.Device.DisabSigIn)
{
lgTrace($"Filtrato accodamento valore da conf IOB | DisabSigIn | [QUEUE-IN] {qEncodeIN}");
}
else
{
// verifico non sia in veto invio iniziale...
if (queueInEnabCurr)
{
// --> accodo (valore già formattato)!
QueueIN.Enqueue(qEncodeIN);
// loggo!
lgDebug($"[QUEUE-IN] {qEncodeIN}");
}
else
{
lgTrace($"[VETO FOR QUEUE-IN] | {qEncodeIN} - MESSAGE NOT SENT | {adesso:yyyyMMdd_HHmmss}");
checkVetoQueueIn();
}
// aggiorno counters ed eventuale reset
nReadFilt++;
if (nReadFilt > int.MaxValue - 1)
{
nReadFilt = 0; // per evitare buffer overflow...
}
counterSigIN++;
if (counterSigIN > 9999)
{
counterSigIN = 0;
}
}
lgDebug($"accodaSigIN 02 | nReadFilt: {nReadFilt} | counterSigIN: {counterSigIN} | QueueIN len: {QueueIN.Count}");
}
/// <summary>
/// Accumula in coda i valori USER LOG e logga...
/// </summary>
/// <param name="val">VALORE RAW (x display)</param>
/// <param name="encodedVal">VALORE già processato con qEncodeULog(...)</param>
public void accodaUserLog(string val, string encodedVal)
{
// mostro dati variati letti...
displayOtherData(val);
// accodo IN PRIMIS al FluxLog --> accodo (valore già formattato)!
QueueULog.Enqueue(encodedVal);
// loggo!
lgInfo(string.Format("[QUEUE-USER-LOG] {0}", encodedVal));
counterULog++;
if (counterULog > 9999)
{
counterFLog = 0;
}
}
/// <summary>
/// Verifica se la IOB sia ENABLED (da server o Demo)
/// </summary>
public async Task<bool> CheckIobEnabled()
{
// 1. Controllo Veto (Sincrono)
if (dtVetoCheckIOB >= DateTime.Now)
return IobOnline;
bool currentAnsw = false;
if (DemoOut)
{
currentAnsw = (QueueIN.Count + QueueFLog.Count >= nMaxSend);
}
else
{
currentAnsw = await ExecuteIobCheckWithRetry(maxRetries: 5);
}
// 3. Gestione Stato e Logica UI
UpdateIobState(currentAnsw);
return currentAnsw;
}
/// <summary>
/// Verifica veto invio per una chiave specifica in Redis
/// </summary>
/// <param name="keyReq"></param>
/// <returns></returns>
public bool CheckSendVeto(string keyReq, TimeSpan vetoReq)
{
DateTime adesso = DateTime.Now;
DateTime lastSend = LastSendGet(keyReq);
bool sendEnab = lastSend.Add(vetoReq) <= adesso;
return sendEnab;
}
/// <summary>
/// Verifica se il server sia ALIVE (tramite PING)
/// </summary>
public bool CheckServerAlive() // Rimosso async, restituisce bool
{
// 1. Controllo Veto (Sincrono)
if (dtVetoPing >= DateTime.Now) return MPOnline;
if (DemoOut) return true;
// 2. Eseguo la parte asincrona forzando l'attesa
// Usiamo Task.Run per far girare il codice asincrono su un thread del pool
// evitando deadlock con il thread della UI
bool isAlive = Task.Run(async () => await ExecuteApiCheckWithRetryAsync(maxRetries: 7))
.GetAwaiter()
.GetResult();
// 3. Gestione Stato (Sincrono)
UpdateServerState(isAlive);
return isAlive;
}
/// <summary>
/// Verifica se il server sia ALIVE (tramite PING)
/// </summary>
public async Task<bool> CheckServerAliveAsync()
{
// 1. Controllo Veto (Sincrono)
if (dtVetoPing >= DateTime.Now) return MPOnline;
if (DemoOut) return true;
bool isAlive = await ExecuteApiCheckWithRetryAsync(maxRetries: 7);
// 3. Gestione Stato (Sincrono)
UpdateServerState(isAlive);
return isAlive;
}
/// <summary>
/// Verifica veto coda QueueIN ed aggiorna abilitazione su variabile
/// </summary>
public void checkVetoQueueIn()
{
queueInEnabCurr = dtVetoQueueIN < DateTime.Now;
}
/// <summary>
/// Update visualizzaizone BIT in ingresso <paramref name="currDispData">Parametri da
/// aggiornare x display in form</paramref>
/// </summary>
public void displayInData(ref newDisplayData currDispData)
{
if (currDispData != null)
{
// mostro update...
string newString = string.Format("{0:0000}|{1}", counterSigIN, utils.IntToBinStr(B_output, 8));
currDispData.newInData = $"{B_output:X}";
currDispData.newSignalData = newString;
}
}
/// <summary>
/// Mostra cosa ha/avrebbe inviato
/// </summary>
/// <param name="newData"></param>
public void displayOtherData(string newData)
{
// mostro update...
accodaOtherData(newData);
}
/// <summary>
/// Effettua i task di comunicazione IN/OUT con la macchina
/// </summary>
/// <param name="ciclo"></param>
/// <returns></returns>
public void doMachineTask(gatherCycle ciclo)
{
// init obj display
newDisplayData currDispData = new newDisplayData();
// controllo connessione/connettività
if (connectionOk)
{
// controllo non sia già in esecuzione...
if (!adpCommAct)
{
// provo ad avviare
try
{
// imposto flag adapter running..
adpCommAct = true;
adpStartRun = DateTime.Now;
}
catch (Exception exc)
{
string errore = $"Adapter NOT STARTED!!!{Environment.NewLine}{exc}";
adpCommAct = false;
adpStartRun = DateTime.Now;
currDispData.newLiveLogData = errore;
}
if (adpCommAct)
{
// try / catch generale altrimenti segno che è disconnesso...
try
{
if (ciclo == gatherCycle.VHF)
{
//processVHF();
}
// processing dati memoria (lettura, filtraggio, enqueque)
else if (ciclo == gatherCycle.HF)
{
processWhatchDog();
//Thread.Sleep(5);
processAllMemory();
}
else if (ciclo == gatherCycle.MF)
{
processMode();
//Thread.Sleep(5);
ExecServerRequests();
//Thread.Sleep(5);
processCustomTaskMF();
//Thread.Sleep(5);
processOverride();
//Thread.Sleep(5);
processContapezzi();
//Thread.Sleep(5);
processCncAlarms();
//Thread.Sleep(5);
processDynData();
//Thread.Sleep(5);
processMem2Write();
}
else if (ciclo == gatherCycle.LF)
{
processCustomTaskLF();
//Thread.Sleep(5);
processOtherCounters().GetAwaiter().GetResult(); ;
//Thread.Sleep(5);
processProgram();
}
else if (ciclo == gatherCycle.VLF)
{
//processRecipeSyncArch();
// recupero dati SETUP (sysinfo) e li invio/mostro se variati...
processSysInfo();
if (enableSlowData)
{
//Thread.Sleep(5);
processSlowDataRead();
}
}
}
catch (Exception exc)
{
// segnalo eccezione e indico disconnesso...
lgError($"Exception doMachineTask | {ciclo} | fermo adapter{Environment.NewLine}{exc}");
parentForm.fermaAdapter(true, false, true);
}
// tolgo flag running
adpCommAct = false;
}
else
{
if (periodicLog)
{
lgDebug("ADP not running...");
}
}
}
else
{
// log ADP running
lgInfo("Non eseguo chiamata: ADP ancora in running");
// se è bloccato da oltre maxSec lo sblocco...
if (DateTime.Now.Subtract(adpStartRun).TotalSeconds > utils.CRI("maxAdapterLockSec"))
{
// tolgo flag running
adpCommAct = false;
adpStartRun = DateTime.Now;
}
}
}
else
{
// provo a riconnettere SE abilitato tryRestart...
if (adpTryRestart && !connectionOk)
{
// controllo se sia scaduto periodi di veto al tryConnect...
int waitRecMSec = utils.CRI("waitRecMSec");
// cerco se ci sia un valore in ovverride x il singolo IOB...
if (IOBConfFull.General.WaitRecMsec > 0)
{
waitRecMSec = IOBConfFull.General.WaitRecMsec;
}
DateTime dtVeto = lastConnectTry.AddMilliseconds(waitRecMSec);
if (DateTime.Now > dtVeto)
{
lgInfo($"Veto Time Elapsed | lastConnectTry: {lastConnectTry} | waitRecMSec {waitRecMSec} ms) | NOW tryConnect");
lastConnectTry = DateTime.Now;
tryConnect();
}
}
currDispData.semIn = Semaforo.SR;
processDisconnectedTask();
processMemoryDiscon();
}
raiseRefresh(currDispData);
}
/// <summary>
/// Effettua i task di comunicazione IN/OUT con il server MAPO
/// </summary>
/// <param name="ciclo"></param>
/// <returns></returns>
public async Task doServerTaskAsync(gatherCycle ciclo)
{
// init obj display
newDisplayData currDispData = new newDisplayData();
// IN OGNI CASO a prima di tutto EFFETTUO GESTIONE INVII dati da code!!!
try
{
await TrySendValuesAsync();
}
catch (Exception exc)
{
lgError(exc, "Errore in gestione svuotamento/invio preliminare code memoria");
currDispData.semOut = Semaforo.SR;
}
// controllo connessione/connettività verso PLC...
if (connectionOk)
{
// try / catch generale altrimenti segno che è disconnesso...
try
{
bool showDebugData = false;
if (ciclo == gatherCycle.VHF)
{
processVHF();
}
// processing dati memoria (lettura, filtraggio, enqueque)
else if (ciclo == gatherCycle.HF)
{
// recupera ed invia risposte al server
await ServerPutRespAsync();
}
else if (ciclo == gatherCycle.MF)
{
// recupero elenco richieste
await ServerGetRequestsAsync();
}
else if (ciclo == gatherCycle.LF)
{
// verifico se devo gestire cambio ODL in modo automatico
await ProcessAutoOdlAsync();
// verifico se devo gestire auto generazione dossier quotidiana
ProcessAutoDossier();
// effettua gestione import file se configurato...
await ProcessFileImportAsync();
// effettua process ritorno ricette
await ProcessRecipeFileRetAsync();
}
else if (ciclo == gatherCycle.VLF)
{
if (utils.CRB("enableContapezzi"))
{
// rilettura contapezzi da server...
lgTrace("Ciclo MsVLF: pzCntReload(true)");
if (!isMulti)
{
pzCntReload(true);
}
// refresh associazione Macchina - IOB
await SendM2IobAsync();
// invio altri dati accessori...
await SendMachineConfAsync();
}
// checkLogDir x shrink!
checkShrinkDir();
// eventuale log!
if (utils.CRB("recTime"))
{
try
{
logTimeResults();
}
catch
{ }
}
processRecipeSyncArch();
}
// mostra eventuali altri dati di processo...
reportDataProc();
if (showDebugData)
{
// verifica se debba salvare e mostrare dati
checkSavePersDataLayer();
}
}
catch (Exception exc)
{
// segnalo eccezione e indico disconnesso...
lgError($"Exception | doServerTaskAsync | {ciclo}{Environment.NewLine}{exc}");
}
}
else
{
// anche se NON connesso alcuni task di bassa freq li eseguo...
if (ciclo == gatherCycle.LF)
{
// verifico se devo gestire cambio ODL in modo automatico
await ProcessAutoOdlAsync();
// verifico se devo gestire auto generazione dossier quotidiana
ProcessAutoDossier();
// effettua gestione import file se configurato...
await ProcessFileImportAsync();
// effettua process ritorno ricette
await ProcessRecipeFileRetAsync();
}
else if (ciclo == gatherCycle.VLF)
{
processRecipeSyncArch();
}
//// provo a riconnettere SE abilitato tryRestart...
//if (adpTryRestart && !connectionOk)
//{
// // controllo se sia scaduto periodi di veto al tryConnect...
// int waitRecMSec = utils.CRI("waitRecMSec");
// // cerco se ci sia un valore in ovverride x il singolo IOB...
// if (IOBConfFull.General.WaitRecMsec > 0)
// {
// waitRecMSec = IOBConfFull.General.WaitRecMsec;
// }
// DateTime dtVeto = lastConnectTry.AddMilliseconds(waitRecMSec);
// if (DateTime.Now > dtVeto)
// {
// lgInfo($"Veto Time Elapsed | lastConnectTry: {lastConnectTry} | waitRecMSec {waitRecMSec} ms) | NOW tryConnect");
// lastConnectTry = DateTime.Now;
// tryConnect();
// }
//}
//currDispData.semIn = Semaforo.SR;
//processDisconnectedTask();
//processMemoryDiscon();
}
raiseRefresh(currDispData);
}
public void ExecServerRequests()
{
// verifica se ci siano richieste da eseguire
if (QueueSrvReq.Count > 0)
{
if (MPOnline)
{
if (IobOnline)
{
// prendo elenco
List<string> listaValori = QueueSrvReq.ToList();
foreach (var rawJob in listaValori)
{
// deserializzo...
JobTaskData jobTaskReq = JsonConvert.DeserializeObject<JobTaskData>(rawJob);
// processo!
var reqDict = JobTaskData.TaskDict(jobTaskReq.RawData);
if (reqDict.Count > 0)
{
var taskDone = ProcessTask(JobTaskData.TaskDict(jobTaskReq.RawData), jobTaskReq.CodTav);
// accodo task eseguiti...
string serVal = JsonConvert.SerializeObject(taskDone);
accodaServResp(jobTaskReq.CodTav, serVal);
}
}
// svuoto!
QueueSrvReq = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueServResp", IOBConfFull.General.EnabRedisQue, redisMan);
}
}
}
}
/// <summary>
/// Esecuzione dei task richiesti e pulizia coda richieste eseguite
/// </summary>
/// <param name="task2exe">Elenco task da eseguire</param>
/// <param name="codTav">Codice TAV (per macchine multi pallet) - opzionale</param>
public virtual Dictionary<string, string> executeTasks(Dictionary<string, string> task2exe, string codTav)
{
string logMsg = $"Generic: call executeTasks | {task2exe.Count} task";
if (!string.IsNullOrEmpty(codTav))
{
logMsg += $" | codTav: {codTav}";
}
lgInfo(logMsg);
// Verificare il protocollo: dovrebbe togliere SOLO i task eseguiti...
Dictionary<string, string> taskDone = new Dictionary<string, string>();
if (task2exe != null)
{
// controllo se memMap != null...
if (memMap != null)
{
bool taskOk = false;
string taskVal = "";
string newVal = "";
// cerco task specifici: se ho startSetup --> imposto bit DBB701.DBB0.4
foreach (var item in task2exe)
{
taskOk = false;
taskVal = "";
// converto richiesta in enum...
taskType tName = taskType.nihil;
Enum.TryParse(item.Key, out tName);
string iKey = item.Key;
// se è DP aggiungo in chiave il valore della TAV richiesta... ad es setComm --> setComm#TAV_1
if (!string.IsNullOrEmpty(codTav))
{
iKey += $"#{codTav}";
}
// controllo sulla KEY...
switch (tName)
{
case taskType.setArt:
case taskType.setComm:
case taskType.setProg:
case taskType.setPzComm:
// recupero dati da memMap...
if (memMap != null && memMap.mMapWrite != null)
{
if (memMap.mMapWrite.ContainsKey(iKey))
{
dataConf currMem = memMap.mMapWrite[iKey];
string addr = currMem.memAddr;
taskVal = $"SET task: {iKey} --> {item.Value} | mem: {currMem.memAddr} - {currMem.size} byte";
// salvo il nuovo valore nella memoria... così prox invio lo trasmetterà
memMap.mMapWrite[iKey].value = item.Value;
}
else
{
taskVal = $"NO DATA MEM, SET task: {iKey} --> {item.Value}";
}
}
else
{
taskVal = $"NO BankConf found, SET task: {iKey} --> {item.Value}";
}
// salvo in currProd..
upsertKey(iKey, item.Value);
break;
case taskType.endProd:
// reset contapezzi inizio setup
if (IOBConfFull.Counters.ResetOnProdEnd)
{
lgInfo($"Generic.executeTasks {tName} | resetContapezziPLC");
taskOk = resetContapezziPLC(codTav);
}
break;
case taskType.forceResetPzCount:
// recupero dati da memMap...
if (memMap != null && memMap.mMapWrite != null)
{
lgInfo($"Generic.executeTasks {tName} | resetContapezziPLC | memMap");
if (memMap.mMapWrite.ContainsKey(iKey))
{
dataConf currMem = memMap.mMapWrite[iKey];
string addr = currMem.memAddr;
taskVal = $"SET task: {iKey} --> {item.Value} | mem: {currMem.memAddr} - {currMem.size} byte";
// salvo il nuovo valore nella memoria... così prox invio lo trasmetterà
memMap.mMapWrite[iKey].value = item.Value;
taskOk = true;
}
else
{
taskVal = $"NO DATA MEM, SET task: {iKey} --> {item.Value}";
}
// imposto reset gestione pzcount da parametri
vetoQueuePzCount = DateTime.Now.AddMinutes(IOBConfFull.General.DelayReadPzCountSetup);
dtVetoReadPzCount = DateTime.Now.AddMinutes(delayMinReadPzCount);
}
else
{
// reset contapezzi senza memMap
lgInfo($"Generic.executeTasks {tName} | resetContapezziPLC | NO memMap");
taskOk = resetContapezziPLC(codTav);
taskVal = taskOk ? "forceResetPzCount | RESET PZ COUNT OK" : "forceResetPzCount | PZ RESET DISABLED | NO EXEC";
}
lgInfo($"Chiamata forceResetPzCount: taskOk: {taskOk} | taskVal: {taskVal}");
break;
case taskType.forceSetPzCount:
// recupero dati da memMap...
if (memMap != null && memMap.mMapWrite != null)
{
if (memMap.mMapWrite.ContainsKey(iKey))
{
dataConf currMem = memMap.mMapWrite[iKey];
string addr = currMem.memAddr;
taskVal = $"SET task: {iKey} --> {item.Value} | mem: {currMem.memAddr} - {currMem.size} byte";
// salvo il nuovo valore nella memoria... così prox invio lo trasmetterà
memMap.mMapWrite[iKey].value = item.Value;
}
else
{
taskVal = $"NO DATA MEM, SET task: {iKey} --> {item.Value}";
}
}
else
{
taskVal = $"NO BankConf found, SET task: {iKey} --> {item.Value}";
}
lgInfo($"Chiamata forceSetPzCount: taskVal: {taskVal}");
break;
case taskType.setArtNum:
// in primis faccio una chiamata per tutta la tab SE fosse vuoto il
// dict di traduzione
if (DictNumArt == null || DictNumArt.Count == 0)
{
getNumArt("");
}
// chiamo server x avere decodifica valore INT
newVal = getNumArt(item.Value);
// procedo come il resto cercando mappatura in memMap: recupero dati
// da memMap...
if (memMap != null && memMap.mMapWrite != null)
{
if (memMap.mMapWrite.ContainsKey(iKey))
{
dataConf currMem = memMap.mMapWrite[iKey];
string addr = currMem.memAddr;
taskVal = $"SET task: {iKey} --> {newVal} | mem: {currMem.memAddr} - {currMem.size} byte";
// salvo il nuovo valore nella memoria... così prox invio lo trasmetterà
memMap.mMapWrite[iKey].value = newVal;
}
else
{
taskVal = $"NO DATA MEM, SET task: {iKey} --> {newVal} ({item.Value})";
}
}
else
{
taskVal = $"NO BankConf found, SET task: {iKey} --> {newVal} ({item.Value})";
}
// salvo in currProd..
upsertKey(iKey, newVal);
break;
case taskType.setCommNum:
// chiamo server x avere decodifica valore INT
newVal = getNumComm(item.Value);
// procedo come il resto cercando mappatura in memMap: recupero dati
// da memMap...
if (memMap != null && memMap.mMapWrite != null)
{
if (memMap.mMapWrite.ContainsKey(iKey))
{
dataConf currMem = memMap.mMapWrite[iKey];
string addr = currMem.memAddr;
taskVal = $"SET task: {iKey} --> {newVal} | mem: {currMem.memAddr} - {currMem.size} byte";
// salvo il nuovo valore nella memoria... così prox invio lo trasmetterà
memMap.mMapWrite[iKey].value = newVal;
}
else
{
taskVal = $"NO DATA MEM, SET task: {iKey} --> {newVal} ({item.Value})";
}
}
else
{
taskVal = $"NO BankConf found, SET task: {iKey} --> {newVal} ({item.Value})";
}
// salvo in currProd..
upsertKey(iKey, newVal);
break;
case taskType.startSetup:
// reset contapezzi inizio setup
if (IOBConfFull.Counters.ResetOnSetupStart)
{
lgDebug($"Generic.executeTasks {tName} | resetContapezziPLC");
taskOk = resetContapezziPLC(codTav);
vetoQueuePzCount = DateTime.Now.AddMinutes(IOBConfFull.General.DelayReadPzCountSetup);
dtVetoReadPzCount = DateTime.Now.AddMinutes(delayMinReadPzCount);
}
taskVal = taskOk ? "startSetup | RESET: SETUP START" : "startSetup | PZ RESET DISABLED | NO EXEC";
lgInfo($"Chiamata startSetup: taskOk: {taskOk} | taskVal: {taskVal}");
break;
case taskType.stopSetup:
// reset contapezzi fine setup SE ESPLICITAMENTE IMPOSTATO
if (IOBConfFull.Counters.ResetOnSetupStop)
{
lgDebug($"Generic.executeTasks {tName} | resetContapezziPLC");
taskOk = resetContapezziPLC(codTav);
}
taskVal = taskOk ? "stopSetup | RESET: SETUP END" : "stopSetup | PZ RESET DISABLED | NO EXEC";
lgInfo($"Chiamata stopSetup: taskOk: {taskOk} | taskVal: {taskVal}");
break;
case taskType.setParameter:
// richiedo da URL i parametri WRITE da popolare
lgInfo("Chiamata setParameter --> processMemWriteRequests");
taskVal = processMemWriteRequests();
// se restituiscce "" faccio altra prova...
if (string.IsNullOrEmpty(taskVal))
{
// i parametri me li aspetto come stringa composta paramName|paramvalue
if (item.Value.Contains("|"))
{
string[] paramsJob = item.Value.Split('|');
taskVal = $"REQUEST SET PARAMETERS: {paramsJob[0]} --> {paramsJob[1]}";
}
else
{
taskVal = $"WRONG REQUEST FOR SET PARAMETERS: {item.Value} doesnt contain pipe for splitting key/value";
}
}
break;
case taskType.syncDbData:
ProcessDataSync();
break;
case taskType.processOtherInfo:
bool okProc = ProcessOtherInfo(iKey, item.Value);
taskVal = okProc ? $"OK ProcessOtherInfoAsync | {iKey} | {item.Value}" : $"ERROR ProcessOtherInfoAsync | {iKey} | {item.Value}";
#if false
try
{
Task.Run(async () => okProc = await ProcessOtherInfoAsync(iKey, item.Value))
.GetAwaiter()
.GetResult();
taskVal = okProc ? $"OK ProcessOtherInfoAsync | {iKey} | {item.Value}" : $"ERROR ProcessOtherInfoAsync | {iKey} | {item.Value}";
}
catch (Exception ex)
{
lgError("ProcessOtherInfoAsync | Crash nel ponte Sync/Async: " + ex.Message);
}
#endif
break;
default:
taskVal = $"taskReq: {tName} | key: {iKey} | val: {item.Value} | SKIPPED | NO EXEC";
lgInfo($"Chiamata senza processing: taskOk: {taskOk} | taskVal: {taskVal}");
break;
}
// aggiungo task!
taskDone.Add(item.Key, taskVal);
}
}
else
{
foreach (var item in task2exe)
{
// converto richiesta in enum...
taskType tName = taskType.nihil;
Enum.TryParse(item.Key, out tName);
string taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | SKIPPED (no BankConf) | NO EXEC";
// aggiungo task!
taskDone.Add(item.Key, taskVal);
}
lgError($"Attenzione! memMap è nullo, non posso eseguire task2exe! Tutte le richieste sono state chiuse senza esecuzione");
}
}
return taskDone;
}
/// <summary>
/// Cerca parametri opzionali in modalità "like" del nome
/// </summary>
/// <param name="keyStartSearch"></param>
/// <returns></returns>
public Dictionary<string, string> findOptPar(string keyStartSearch = "")
{
Dictionary<string, string> answ = new Dictionary<string, string>();
// controllo SE keySearch !=""
if (!string.IsNullOrWhiteSpace(keyStartSearch))
{
if (IOBConfFull.OptPar.Count > 0)
{
// ciclo su tutti e cerco occorrenze che INIZINO...
foreach (var item in IOBConfFull.OptPar)
{
if (item.Key.StartsWith(keyStartSearch))
{
answ.Add(item.Key, item.Value);
}
}
}
}
return answ;
}
/// <summary>
/// forza reset ODL
/// </summary>
public void forceResetOdl()
{
lgInfo("Registrato richiesta forzatura reset ODL");
pzCountResetted = true;
}
/// <summary>
/// Effettua chiamata x split ODL
/// </summary>
/// <returns></returns>
public async Task<bool> forceSplitOdl()
{
bool fatto = false;
if (vetoSplit < DateTime.Now)
{
lgInfo("Richiesto forceSplitOdl");
// imposto veto x 1 minuto ad altre chiamate...
vetoSplit = DateTime.Now.AddMinutes(1);
// eseguo SOLO SE sono online...
if (MPOnline && IobOnline)
{
string fullUrl = "";
string rawSplit = "";
try
{
/***************************************************
* Descrizione procedura (OK X SIMULATORI... da provare x DP come OpcUaSiemensSW)
*
* - chiamata su MP/IO
* - verifica che su DB sia abilitato AUTO ODL
* - il server inserisce un evento fine prod HW 1 minuto prima e inizio setup HW
* - viene duplicato e chiuso ODL corrente
* - viene fatto partire ODL nuovo ADESSO
* - num pezzi come ODL precedente (o da media 3 ODL precedenti)
* - conferme pezzi & co... gestione NULL (NON SERVONO si tratta di impianti SENZA gestione vera ODL)
* - reset contapezzi PLC locale...
*
*
*
* DA VALUTARE (x macchine tipo linea con + impianti... es valvital) SE
* - creare una gestione ALTERNATIVA sul server che preveda impianto LEADER e impianti follower (RIGIDAMENTE CONFIGURATI)
* - ogni volta che si fa setup LEADER --> si ripete su impianti FOLLOWER (eventi!!!)
* - viene okReport reset contapezzi sui follower (+ altre operazioni opzionali, ES imposstazione nome commessa, quantità, articolo...)
* - viene okReport reset + nuovo ODL (con stessi articoli e quantità) su follower, SENZA avere gestione x ODL di un codice esterno (quindi registra TUTTO ma NON RITORNERA' dati non avendo link verso esterno)
* - serve NUOVA TABELLA delle macchine LEADER | FOLLOWER (1:n) e gestione da MP/IO
*
* ***************************************************/
// se normale splitto!
if (!isMulti)
{
// invio chiamata URL x reset ODL su macchina
rawSplit = await callUrl(urlForceSplit, false);
fatto = (rawSplit != "KO") ? true : false;
}
// se multi gestisco il bit delle tavole...
else
{
foreach (string item in IOBConfFull.Device.MultiIobList)
{
// invio chiamata URL x reset ODL su macchina, ATTENZIONE scriviamo
// | al posto di "#" che in URL sarebbe filtrato...
fullUrl = $"{urlForceSplit}&multi={item}";
rawSplit = await callUrl(fullUrl, false);
lgDebug($"Esecuzione forceSplit | URL: {fullUrl} | esito: {rawSplit}");
}
fatto = (rawSplit == "OK") ? true : false;
}
}
catch (Exception exc)
{
lgError($"Eccezione in forceSplitOdl{Environment.NewLine}{exc}");
}
// se okReport --> resetto contapezzi!!!
if (fatto)
{
contapezziPLC = 0;
contapezziIOB = 0;
}
}
else
{
lgError("Richiesto forceSplitOdl ma MP/IOB offline --> NON eseguito");
}
}
else
{
lgError("Richiesto forceSplitOdl ma veto attivo --> NON eseguito");
}
return fatto;
}
/// <summary>
/// Processing degli allarmi (se presenti e gestiti)
/// </summary>
/// <returns></returns>
public virtual Dictionary<string, string> getAlarmData()
{
Dictionary<string, string> outVal = new Dictionary<string, string>();
// ora aggiungo (se ci fossero) gli allarmi...
if (alarmMaps != null && alarmMaps.Count > 0)
{
if (hasAlarms())
{
string bankVal = "";
foreach (var item in alarmMaps)
{
bankVal = "";
// verifico ed eseguo secondo il tipo di allarme gestito...
if (alarmType == AlarmBlockType.Bitmap)
{
var currState = currAlarmsState(item);
// calcolo vettore stringa degli allarmi...
bankVal = getAlarmState(item, currState);
}
else if (alarmType == AlarmBlockType.ActiveList)
{
// cerco se sia superiore al livello minimo da conf
if (item.blockLevel >= alarmLevelMin)
{
int numActive = getAlarmStatus(item);
// calcolo vettore stringa degli allarmi...
bankVal = getAlarmState(item, numActive);
}
}
// sistemo se OK e/o invio
bankVal = string.IsNullOrEmpty(bankVal) ? "OK" : bankVal;
saveAlarmString(ref outVal, bankVal, item.description);
}
}
}
else
{
lgTrace("No alarm found in alarmMaps");
}
if (periodicLog || outVal.Count > 0)
{
lgDebug($"Esito getAlarmData: {outVal.Count} allarmi attivi/validi in outVal");
}
return outVal;
}
/// <summary>
/// Effettua conversione da valore bitmap ai valori configurati x allarme
/// </summary>
/// <param name="memConf">Configurazione banco allarmi</param>
/// <param name="bState">BitState allarmi</param>
/// <returns></returns>
public string getAlarmState(BaseAlarmConf memConf, byte[] bState)
{
string valTransl = "";
List<string> descAttivi = new List<string>();
BitArray bitAttivi = new BitArray(bState);
bool[] bitStati = new bool[bitAttivi.Count];
bitAttivi.CopyTo(bitStati, 0);
// cerco bit attivi
int idx = 0;
foreach (var item in bitStati)
{
if (item && memConf.messages.Count > idx)
{
string currState = memConf.messages[idx];
descAttivi.Add(currState);
}
idx++;
}
// combino string
valTransl = string.Join(",", descAttivi);
return valTransl;
}
/// <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 virtual string getAlarmState(BaseAlarmConf memConf, int numAlarms)
{
string valTransl = "";
List<string> descAttivi = new List<string>();
// FAKE!!!: dovrebbe cercare in aree memoria x ogni valore configurato, vedere
// implementazione specifica es OpcUa
// combino string
valTransl = string.Join(",", descAttivi);
return valTransl;
}
/// <summary>
/// Effettua conversione da valore bitmap ai valori configurati
/// </summary>
/// <param name="memConf"></param>
/// <param name="bState"></param>
/// <returns></returns>
public string getBitmapState(dataConfTSVC memConf, byte[] bState)
{
string valTransl = "";
List<string> descAttivi = new List<string>();
BitArray bitAttivi = new BitArray(bState);
bool[] bitStati = new bool[bitAttivi.Count];
bitAttivi.CopyTo(bitStati, 0);
// cerco bit attivi
int idx = 0;
foreach (var item in bitStati)
{
if (item && memConf.decodeMap.Count > idx)
{
string currState = memConf.decodeMap[idx];
descAttivi.Add(currState);
}
idx++;
}
// combino string
valTransl = string.Join(",", descAttivi);
return valTransl;
}
/// <summary>
/// Recupera eventuali allarmi CNC...
/// </summary>
public virtual Dictionary<string, string> getCncAlarms()
{
Dictionary<string, string> outVal = new Dictionary<string, string>();
return outVal;
}
/// <summary>
/// Restituisce info DINAMICHE
/// </summary>
/// <returns></returns>
public virtual Dictionary<string, string> getDynData()
{
Dictionary<string, string> outVal = new Dictionary<string, string>();
return outVal;
}
/// <summary>
/// Cerca se esiste il parametro opzionale nei KVP (JSON) e lo restituisce
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public string getOptJsonKVP(string key)
{
string answ = "";
// controllo SE HO il parametro
if (memMap != null && memMap.OptKVP != null && memMap.OptKVP.Count > 0)
{
if (memMap.OptKVP.ContainsKey(key))
{
answ = memMap.OptKVP[key];
}
}
return answ;
}
/// <summary>
/// Cerca se esiste il parametro opzionale e lo restituisce
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public string getOptPar(string key)
{
return IOBConfFull.OptParGet(key);
}
/// <summary>
/// Cerca se esiste un link tra aree di memoria in write e lo restituisce
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public string getOptWriteLink(string key)
{
string answ = "";
// controllo SE HO il parametro
if (memMap != null && memMap.mMapWriteLink != null && memMap.mMapWriteLink.Count > 0)
{
if (memMap.mMapWriteLink.ContainsKey(key))
{
answ = memMap.mMapWriteLink[key];
}
}
return answ;
}
/// <summary>
/// Restituisce info OVERRIDES
/// </summary>
/// <returns></returns>
public virtual Dictionary<string, string> getOverrides()
{
Dictionary<string, string> outVal = new Dictionary<string, string>();
return outVal;
}
/// <summary>
/// Restituisce programma in esecuzione
/// </summary>
public virtual string getPrgName()
{
return "";
}
/// <summary>
/// Restituisce info sistema
/// </summary>
/// <returns></returns>
public virtual Dictionary<string, string> getSysInfo()
{
Dictionary<string, string> outVal = new Dictionary<string, string>();
return outVal;
}
/// <summary>
/// Recupera la VC x TS, svuotando lista e resettando periodo partenza
/// </summary>
/// <param name="VCName">Nome della VC</param>
/// <param name="doReset">Reimposta e resetta array VC</param>
/// <returns></returns>
public double getVal_TSVC(string VCName, bool doReset)
{
double answ = -999999;
// cerco VC...
if (TSVC_Data.ContainsKey(VCName))
{
try
{
switch (TSVC_Data[VCName].Funzione)
{
case VC_func.POINT:
// prendo PRIMO
answ = TSVC_Data[VCName].dataArray.FirstOrDefault();
break;
case VC_func.AVG:
answ = TSVC_Data[VCName].dataArray.Average();
break;
case VC_func.MEDIAN:
answ = TSVC_Data[VCName].dataArray.Median();
break;
case VC_func.MIN:
answ = TSVC_Data[VCName].dataArray.Min();
break;
case VC_func.MAX:
default:
answ = TSVC_Data[VCName].dataArray.Max();
break;
}
}
catch
{ }
// ora resetto... SE richiesto...
if (doReset)
{
TSVC_Data[VCName].dataArray = new List<double>();
TSVC_Data[VCName].DTStart = DateTime.Now;
}
}
return answ;
}
/// <summary>
/// Recupera la VC x TS, svuotando lista e resettando periodo partenza
/// </summary>
/// <param name="VCName">Nome della VC</param>
/// <param name="doReset">Reimposta e resetta array VC</param>
/// <returns></returns>
public int getVal_TSVC_int(string VCName, bool doReset)
{
int answ = 0;
// cerco VC...
if (TSVC_Data.ContainsKey(VCName))
{
// !!!FARE!!! vero calcolo... x ora FIX a MAX...
foreach (var item in TSVC_Data[VCName].dataArray)
{
answ = (int)item > answ ? (int)item : answ;
}
// ora resetto... SE richiesto..
if (doReset)
{
TSVC_Data[VCName].dataArray = new List<double>();
TSVC_Data[VCName].DTStart = DateTime.Now;
}
}
return answ;
}
/// <summary>
/// Area init asincrono (fare override async!o)
/// </summary>
/// <returns></returns>
public virtual Task InitializeAsync()
{
// da usare per implementare logiche di init specifiche
return Task.CompletedTask;
}
/// <summary>
/// Restituisce un payload in formato json della lista di valori ricevuta
/// </summary>
/// <param name="tipoUrl">Tipo di URL (eventi / FLog)</param>
/// <param name="elencoValori">elenco di valori da coda string salvata</param>
/// <returns></returns>
public string jsonPayload(urlType tipoUrl, List<string> elencoValori)
{
string answ = "";
if (elencoValori != null)
{
string[] valori;
int counter = 0;
DateTime dtEve = DateTime.Now;
switch (tipoUrl)
{
case urlType.FLog:
flogData currFlData = new flogData();
flogJsonPayload fullFlObj = new flogJsonPayload();
fullFlObj.fluxData = new List<flogData>();
// inizio processando ogni valore
foreach (var item in elencoValori)
{
if (item != null)
{
valori = qDecodeIN(item);
CultureInfo provider = CultureInfo.InvariantCulture;
DateTime.TryParseExact(valori[0], "yyyyMMddHHmmssfff", provider, DateTimeStyles.AssumeLocal, out dtEve);
int.TryParse(valori[3], out counter);
currFlData = new flogData()
{
flux = valori[1],
valore = valori[2],
dtEve = dtEve,
dtCurr = DateTime.Now,
cnt = counter
};
fullFlObj.fluxData.Add(currFlData);
}
}
// conversione finale
try
{
answ = JsonConvert.SerializeObject(fullFlObj);
}
catch (Exception exc)
{
lgError($"FLog Errore in costruzione jsonPayload:{Environment.NewLine}{exc}");
}
break;
case urlType.SignIN:
evData currSigData = new evData();
evJsonPayload fullEvObj = new evJsonPayload();
fullEvObj.eventList = new List<evData>();
// inizio processando ogni valore
foreach (var item in elencoValori)
{
valori = qDecodeIN(item);
CultureInfo provider = CultureInfo.InvariantCulture;
DateTime.TryParseExact(valori[0], "yyyyMMddHHmmssfff", provider, DateTimeStyles.AssumeLocal, out dtEve);
int.TryParse(valori[2], out counter);
currSigData = new evData()
{
valore = valori[1],
dtEve = dtEve,
dtCurr = DateTime.Now,
cnt = counter
};
fullEvObj.eventList.Add(currSigData);
}
// conversione finale
try
{
answ = JsonConvert.SerializeObject(fullEvObj);
}
catch (Exception exc)
{
lgError($"SignIN Errore in costruzione jsonPayload:{Environment.NewLine}{exc}");
}
break;
case urlType.RawTransf:
BaseRawTransf currRTData = new BaseRawTransf();
#if false
rawTransfJsonPayload fullRTObj = new rawTransfJsonPayload();
fullRTObj.rawTransfData = new List<BaseRawTransf>();
// inizio processando ogni valore
foreach (var item in elencoValori)
{
try
{
currRTData = JsonConvert.DeserializeObject<BaseRawTransf>(item);
}
catch (Exception exc)
{
lgError($"Eccezione in deserializzazione BaseRawTransf:{Environment.NewLine}{exc}");
}
fullRTObj.rawTransfData.Add(currRTData);
}
// conversione finale
try
{
answ = JsonConvert.SerializeObject(fullRTObj);
}
catch (Exception exc)
{
lgError($"RawTransf Errore in costruzione jsonPayload:{Environment.NewLine}{exc}");
}
#endif
// provo una serializzazione "brutale", ovvero aggiungo alla stringa il
// valore di testa...
string rawResult = "";
foreach (var item in elencoValori)
{
rawResult += $"{item},";
}
if (rawResult.Length > 0)
{
rawResult = rawResult.Substring(0, rawResult.Length - 1);
}
answ = $"[{rawResult}]";
//hasVeto = "{" + $"\"rawTransfData\":[{rawResult}]" + "}";
break;
case urlType.ULog:
int numVal = 0;
int matrOp = 0;
ulogData currUlData = new ulogData();
ulogJsonPayload fullUlObj = new ulogJsonPayload();
fullUlObj.fluxData = new List<ulogData>();
// inizio processando ogni valore
foreach (var item in elencoValori)
{
valori = qDecodeIN(item);
CultureInfo provider = CultureInfo.InvariantCulture;
DateTime.TryParseExact(valori[0], "yyyyMMddHHmmssfff", provider, DateTimeStyles.AssumeLocal, out dtEve);
int.TryParse(valori[3], out matrOp);
int.TryParse(valori[5], out numVal);
int.TryParse(valori[6], out counter);
currUlData = new ulogData()
{
flux = valori[1],
valore = valori[2],
dtEve = dtEve,
dtCurr = DateTime.Now,
cnt = counter,
matrOpr = matrOp,
label = valori[4],
valNum = numVal
};
fullUlObj.fluxData.Add(currUlData);
}
// conversione finale
try
{
answ = JsonConvert.SerializeObject(fullUlObj);
}
catch (Exception exc)
{
lgError($"ULog Errore in costruzione jsonPayload:{Environment.NewLine}{exc}");
}
break;
default:
break;
}
}
return answ;
}
/// <summary>
/// Restitusice valore ultimo invio di una chiave, se non esistesse fornisce valore di 1 gg prima e lo salva...
/// </summary>
/// <param name="keyReq"></param>
/// <returns></returns>
public DateTime LastSendGet(string keyReq)
{
DateTime lastSend = DateTime.Now.AddDays(-1);
string lastSendKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:LastSend");
string rawVal = redisMan.redGetHashField(lastSendKey, keyReq);
if (!string.IsNullOrEmpty(rawVal))
{
lastSend = DataSerializer.Deserialize<DateTime>(rawVal);
}
else
{
rawVal = DataSerializer.Serialize(lastSend);
KeyValuePair<string, string>[] hashFields = new KeyValuePair<string, string>[1];
hashFields[0] = new KeyValuePair<string, string>(keyReq, rawVal);
redisMan.redSaveHash(lastSendKey, hashFields);
}
return lastSend;
}
/// <summary>
/// Imposta valore datetime x ultimo invio di una chiamata in REDIS HashSet
/// </summary>
/// <param name="keyReq"></param>
/// <param name="dtRif"></param>
public bool LastSendSet(string keyReq, DateTime dtRif)
{
string lastSendKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:LastSend");
string rawVal = DataSerializer.Serialize(dtRif);
KeyValuePair<string, string>[] hashFields = new KeyValuePair<string, string>[1];
hashFields[0] = new KeyValuePair<string, string>(keyReq, rawVal);
bool fatto = redisMan.redSaveHash(lastSendKey, hashFields);
return fatto;
}
/// <summary>
/// Effettua un trim della stringa al numero max di linee da mostrare a video
/// </summary>
/// <param name="newString"></param>
/// <returns></returns>
public string limitLine2show(string newString)
{
// se num righe superiore a limite trimmo...
if (newString.Split('\n').Length > parentForm.nLine2show)
{
//int idx = newString.LastIndexOf('\r');
int idx = newString.LastIndexOf(Environment.NewLine);
newString = newString.Substring(0, idx);
}
return newString;
}
/// <summary>
/// riporta il log di tutti i dati di results temporali registrati
/// </summary>
public void logTimeResults()
{
if (TimingData.results.Count > 0)
{
lgInfo("{0}--------------- START TIMING DATA ---------------", Environment.NewLine);
int globNumCall = 0;
TimeSpan globAvgMsec = new TimeSpan(0);
foreach (TimeRec item in TimingData.results)
{
// loggo SOLO se del mio IOB corrente...
if (item.classCall == IOBConfFull.General.FilenameIOB)
{
lgInfo("{4}|Chiamate {0}: effettuate {1}, tempo medio {2:N2} msec | impegno canale {3:P3}", item.codCall, item.numCall, item.avgMsec, item.totMsec.TotalSeconds / DateTime.Now.Subtract(dtAvvioAdp).TotalSeconds, IOBConfFull.General.CodIOB);
globNumCall += item.numCall;
globAvgMsec += item.totMsec;
}
}
// riporto conteggio medio al secondo...
lgInfo("{4}|Chiamate GLOBALI: {0}, periodo: {1:N2} minuti.cent, tempo medio {2:N2} msec | impegno canale {3:P3}", globNumCall, DateTime.Now.Subtract(dtAvvioAdp).TotalMinutes, globAvgMsec.TotalMilliseconds / globNumCall, globAvgMsec.TotalSeconds / DateTime.Now.Subtract(dtAvvioAdp).TotalSeconds, IOBConfFull.General.CodIOB);
lgInfo("{0}--------------- STOP TIMING DATA ---------------{0}", Environment.NewLine);
// mostro in form statistiche globali!
parentForm.updateComStats(string.Format("Periodo: {0:N2}min | {1} x {2:N2}ms | canale {3:P3}", DateTime.Now.Subtract(dtAvvioAdp).TotalMinutes, globNumCall, globAvgMsec.TotalMilliseconds / globNumCall, globAvgMsec.TotalSeconds / DateTime.Now.Subtract(dtAvvioAdp).TotalSeconds));
}
}
/// <summary>
/// Processa un monitoredItem, e ritorna boolean SE richiede invio (cambio o scadenza)
/// </summary>
/// <param name="newVal"></param>
/// <param name="item"></param>
/// <returns></returns>
public bool monItem2Send(string newVal, DynDataItem item)
{
bool answ = false;
if (item != null)
{
// controllo in base al tipo di function...
switch (item.func)
{
case "SAMPLE":
// controllo se scaduto sample period...
if (item.DTScad < DateTime.Now)
{
answ = true;
}
break;
case "CHANGE":
default:
// controllo se scaduto o se variato...
if (newVal != item.actVal || item.DTScad < DateTime.Now)
{
// aggiorno scadenza e che vada inviato
answ = true;
}
break;
}
}
return answ;
}
/// <summary>
/// Verifica e processing x gestione Dossier quotidiani automatica
/// </summary>
public void ProcessAutoDossier()
{
bool fatto = false;
string callResp = "";
if (IOBConfFull.FluxLog.AutoSnapshotDossier)
{
DateTime adesso = DateTime.Now;
if (adesso > dtVetoAutoDossier)
{
dtVetoAutoDossier = adesso.AddMinutes(30);
lgTrace("Richiesta ProcessAutoDossier");
lgTrace("AutoSnapshotDossier abilitato");
// chiamo stored x creare Snapshot Dossier giornalieri fino alla data...
callResp = utils.callUrl(urlFixDailyDossier);
fatto = callResp == "OK";
lgDebug($"Esecuzione ProcessAutoDossier completata --> esito: {callResp}");
}
else
{
lgTrace("AutoSnapshotDossier DISABILITATO");
}
}
// loggo se enabled
if (fatto)
{
lgTrace($"Effettuato ProcessAutoDossier, esito positivo | {DateTime.Now:HH.mm.ss}");
}
}
/// <summary>
/// Wrapper AutoOdl in modalità Sync
/// </summary>
public void ProcessAutoOdl()
{
try
{
Task.Run(async () => await ProcessAutoOdlAsync())
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError("ProcessAutoOdl | Crash nel ponte Sync/Async: " + ex.Message);
}
}
/// <summary>
/// Verifica e processing x gestione ODL automatica
/// </summary>
public async Task ProcessAutoOdlAsync()
{
bool fatto = false;
if (IOBConfFull.Odl.AutoChangeOdl)
{
lgTrace("ProcessAutoOdlAsync | AutoChangeOdl abilitato");
// imposto il veto lettura contapezzi a 1 minuto x iniziare...
dtVetoReadPzCount = DateTime.Now.AddMinutes(delayMinReadPzCount);
string fullUrl = "";
DateTime adesso = DateTime.Now;
DateTime inizioOdl = adesso;
string rawDataInizio = "";
if (VetoProcessAutoOdl > adesso)
{
lgTrace($"Veto per check autoOdl attivo fino a {VetoProcessAutoOdl} | salto verifica");
}
else
{
bool callChangeODL = false;
DateTime dtStart = await currOdlStart();
switch (IOBConfFull.Odl.ChangeOdlMode)
{
case "PZCOUNT_RESET":
/* verifico se sia "armato" il reset cambio ODL, DEVO essere :
* - NON in produzione
* - contapezzi ACT < contapezzi LAST
* */
lgTrace("ProcessAutoOdlAsync: caso PZCOUNT_RESET");
if ((!isRunning || forceResetInRun) && pzCountResetted)
{
callChangeODL = true;
lgInfo("Attivato cambio ODL da PZCOUNT_RESET");
}
else
{
lgInfo($"isRunning: {isRunning} | pzCountResetted: {pzCountResetted} | forceResetInRun: {forceResetInRun} | contapezziIOB: {contapezziIOB} | contapezziPLC: {contapezziPLC}");
}
break;
case "DAILY":
// verifico inizio ODL, se è data di oggi NON eseguo...
if (dtStart.Date < adesso.Date)
{
// chiamo stored x creare ODL giornalieri alla data...
string autoOdlRes = utils.callUrl(urlFixDailyOdl);
fatto = autoOdlRes == "OK";
// imposto x prox controllo veto a 10 min
VetoProcessAutoOdl = adesso.AddMinutes(10);
}
else
{
// imposto x prox controllo veto a 30 min
VetoProcessAutoOdl = adesso.AddMinutes(30);
}
break;
case "DAILY_CONF_PZ":
// verifico inizio ODL, se è data di oggi NON eseguo...
if (dtStart.Date < adesso.Date)
{
// imposto x prox controllo veto a 10 min
VetoProcessAutoOdl = adesso.AddMinutes(10);
string autoOdlRes = "";
if (!isMulti)
{
// chiamo stored x creare ODL giornalieri alla data + conferma pezzi...
autoOdlRes = utils.callUrl(urlFixDailyOdlConfPzCount);
}
else
{
// prendo il + vecchio...
foreach (var item in IOBConfFull.Device.MultiIobList)
{
fullUrl = $@"{urlCommand("fixDailyOdlConfPzCount")}{item}";
autoOdlRes = await callUrl(fullUrl, false);
}
}
fatto = autoOdlRes == "OK";
}
else
{
// imposto x prox controllo veto a 30 min
VetoProcessAutoOdl = adesso.AddMinutes(30);
}
break;
case "SIMUL":
case "TIME":
// imposto x prox controllo veto a 1 min
VetoProcessAutoOdl = adesso.AddMinutes(1);
// controllo parametri validi
if (IOBConfFull.Odl.OdlDurationHours > 0 && IOBConfFull.Odl.IdleStateMin >= 0)
{
// leggo da server inizio ODL... se non multi 1 solo...
inizioOdl = DateTime.Now;
rawDataInizio = "";
if (!isMulti)
{
rawDataInizio = await callUrl(urlInizioOdlIob, false);
DateTime.TryParse(rawDataInizio, out inizioOdl);
}
else
{
DateTime tmpData = DateTime.Now;
// prendo il + vecchio...
foreach (var item in IOBConfFull.Device.MultiIobList)
{
fullUrl = $"{urlInizioOdlIob}|{item}";
rawDataInizio = await callUrl(fullUrl, false);
DateTime.TryParse(rawDataInizio, out tmpData);
inizioOdl = (tmpData < inizioOdl) ? tmpData : inizioOdl;
}
}
// verifico se sia scaduto...
if (inizioOdl.AddHours(IOBConfFull.Odl.OdlDurationHours) < adesso)
{
string rawIdle = "";
int idlePeriod = 0;
if (!isMulti)
{
// controllo SE sono fermo (spento o in manuale) per il
// periodo minimo richiesto...
rawIdle = await callUrl(urlIdleTime, false);
int.TryParse(rawIdle, out idlePeriod);
}
else
{
int tmpIdle = 0;
// prendo il + grande...
foreach (var item in IOBConfFull.Device.MultiIobList)
{
fullUrl = $"{urlIdleTime}|{item}";
rawIdle = await callUrl(fullUrl, false);
int.TryParse(rawIdle, out tmpIdle);
idlePeriod = tmpIdle > idlePeriod ? tmpIdle : idlePeriod;
}
}
if (idlePeriod >= IOBConfFull.Odl.IdleStateMin)
{
callChangeODL = true;
}
}
}
// se NON fosse scaduto MA è simulato esegue controllo sul num pezzi da
// fare, se sfora (RANDOM) > +(50...110)% --> cambia!
if (!callChangeODL && IOBConfFull.Odl.ChangeOdlMode == "SIMUL")
{
var rawCount = await callUrl(urlGetNumPzCurrODL, false);
if (int.TryParse(rawCount, out var numPzReqOdl))
{
int limitQty = (numPzReqOdl * rndGen.Next(150, 210)) / 100;
callChangeODL = contapezziPLC > limitQty;
}
}
break;
default:
break;
}
// vero processing...
if (callChangeODL)
{
lgTrace("Chiamata: ProcessAutoOdlAsync --> forceSplitODL");
fatto = await forceSplitOdl();
// metto contapezzi a zero..
contapezziPLC = 0;
// aspetto 2 sec per proseguire dopo force split...
await Task.Delay(2000);
pzCountResetted = false;
lgInfo("Esecuzione ProcessAutoOdlAsync completata --> pzCountResetted = false");
// imposto x prox controllo veto a 15 min
VetoProcessAutoOdl = adesso.AddMinutes(15);
}
}
}
else
{
lgTrace("ProcessAutoOdlAsync | AutoChangeOdl DISABILITATO");
}
// loggo se ok + processing post opzionali
if (fatto)
{
lgInfo($"Effettuato ProcessAutoOdlAsync | mode: {IOBConfFull.Odl.ChangeOdlMode}");
processAutoOdlExtraStep();
}
}
/// <summary>
/// Effettua processing degli allarmi CNC SE disponibili
/// </summary>
public void processCncAlarms()
{
if (utils.CRB("enableAlarms"))
{
Dictionary<string, string> currAlarms = new Dictionary<string, string>();
if (connectionOk)
{
currAlarms = getCncAlarms();
}
else
{
lgError("Errore connessione mancante x getCncAlarms");
}
// verifico SE sia cambiato il programma...
if (currAlarms.Count > 0)
{
try
{
string sVal = "";
if (lastAlarm != currAlarms["CNC_ALARM"])
{
// salvo!
lastAlarm = currAlarms["CNC_ALARM"];
// per ogni valore del dizionario mostro ed accodo!
foreach (var item in currAlarms)
{
// verifico NON sia un ND...
if (item.Key == "ND" || string.IsNullOrEmpty(item.Key))
{
// log anomalia...
lgTrace($"Errore in predisposizione FL Allarmi CNC: rawJob.key risulta ND! | rawJob.key: {item.Key} | rawJob.Value: {item.Value}");
}
else
{
sVal = string.Format("[CNC_ALARM]{0}|{1}", item.Key, item.Value);
// chiamo accodamento...
bool sent = accodaFLog(item.Key, sVal, qEncodeFLog(item.Key, item.Value));
if (sent)
{
// traccio valore DynData x analisi
trackDynData(item.Key, item.Value);
}
}
}
}
// accodo ALTRI allarmi NON CNC...
foreach (var item in currAlarms.Where(X => X.Key != "CNC_ALARM"))
{
// verifico NON sia un ND...
if (item.Key == "ND" || string.IsNullOrEmpty(item.Key))
{
// log anomalia...
lgTrace($"Errore in predisposizione FL Allarmi NON CNC: rawJob.key risulta ND! | rawJob.key: {item.Key} | rawJob.Value: {item.Value}");
}
else
{
sVal = $"{item.Key} | {item.Value}";
bool sent = accodaFLog(item.Key, sVal, qEncodeFLog(item.Key, item.Value));
if (sent)
{ // traccio valore DynData x analisi
trackDynData(item.Key, item.Value);
}
}
}
}
catch (Exception exc)
{
lgError($"Eccezione in processCncAlarms{Environment.NewLine}{exc}");
}
}
}
}
/// <summary>
/// Effettua processing contapezzi (ed eventualmente alza il bit di contapezzo...)
/// </summary>
public virtual void processContapezzi()
{ }
/// <summary>
/// Effettua processing CUSTOM x l'IOB corrente (ed invia ad IO) (task svolto tipicamente
/// ogni 5 sec se base timer 10ms, vedere app.config)
/// </summary>
public virtual void processCustomTaskLF()
{ }
/// <summary>
/// Effettua processing CUSTOM x l'IOB corrente (ed invia ad IO) (task svolto tipicamente
/// ogni 3 sec se base timer 10ms, vedere app.config)
/// </summary>
public virtual void processCustomTaskMF()
{ }
/// <summary>
/// Task periodici SE disconnesso
/// </summary>
public virtual void processDisconnectedTask()
{
}
/// <summary>
/// Effettua processing del recupero dei valori dinamici:
/// es: speed (RPM, feedrate) degli assi, valori pressioni, temeprature
/// </summary>
public void processDynData()
{
// FixMe Todo: generalizzare parametri nell'obj?
bool enableByApp = utils.CRB("enableDynData");
Dictionary<string, string> currDynData = new Dictionary<string, string>();
if (enableByApp || IOBConfFull.FluxLog.EnableDynData)
{
// verifico se ho fattore demoltiplica...
if (demFactDynData > 1)
{
lgTrace($"Non eseguo processDynData: fatt veto {demFactDynData}");
// riduco...
demFactDynData--;
}
else
{
lgTrace("Inizio processDynData");
// sistemo il valore di demoltiplica a default
fixDemFactDynData();
// proseguo
if (connectionOk)
{
currDynData = getDynData();
bool hasDynDataVal = currDynData.Count > 0;
if (!hasDynDataVal)
{
lgWarn($"processDynData.getDynData: nessun valori DynData ricevuto");
}
else
{
// verifico DynData siano validi: se tutti uguali NON li considero validi...
bool isValidSet = checkValidDynData(currDynData);
// se non valido loggo e NON proseguo..
if (!isValidSet)
{
lgWarn($"processDynData.getDynData: Valori ricevuti NON validi | # dynData{currDynData.Count}");
}
else
{
var currAlarmData = getAlarmData();
lgTrace($"currDynData: {currDynData.Count} | currAlarmData: {currAlarmData.Count}");
// se ho allarmi
if (currAlarmData != null && currAlarmData.Count > 0)
{
foreach (var item in currAlarmData)
{
if (!currDynData.ContainsKey(item.Key))
{
// aggiungo!
currDynData.Add(item.Key, item.Value);
}
}
}
// verifico parametro sintesi... che ricalcolo ogni volta
if (!currDynData.ContainsKey("DYNDATA") && hasDynDataVal)
{
currDynData.Add("DYNDATA", $"{currDynData.Count}xVal");
}
// verifico se DynData sia abilitato IN GENERALE
if (!IOBConfFull.FluxLog.DisDynData)
{
try
{
string sVal = "";
// se richiesto send diretto...
if (IOBConfFull.FluxLog.ForceDynData)
{
// per ogni valore del dizionario mostro ed accodo!
foreach (var item in currDynData)
{
// controllo se vietato dynData
if (IOBConfFull.FluxLog.SendDynDataRec || item.Key != "DYNDATA")
{
// verifico NON sia un ND...
if (item.Key == "ND" || string.IsNullOrEmpty(item.Key))
{
// log anomalia...
lgTrace($"Errore in processDynData.01: rawJob.key risulta ND! | rawJob.key: {item.Key} | rawJob.Value: {item.Value}");
}
else
{
sVal = string.Format("[DYNDATA]{0}|{1}", item.Key, item.Value);
// chiamo accodamento...
bool sent = accodaFLog(item.Key, sVal, qEncodeFLog(item.Key, item.Value));
if (sent)
{
// traccio valore DynData x analisi
trackDynData(item.Key, item.Value);
}
}
}
}
}
// altrimenti verifico SE sia cambiato il valore dei DynData...
else if (hasDynDataVal && (lastDynDataCtrlVal == null || lastDynDataCtrlVal != currDynData["DYNDATA"]))
{
// salvo!
lastDynDataCtrlVal = currDynData["DYNDATA"];
// per ogni valore del dizionario mostro ed accodo!
foreach (var item in currDynData)
{
// controllo se vietato dynData
if (IOBConfFull.FluxLog.SendDynDataRec || item.Key != "DYNDATA")
{
// verifico NON sia un ND...
if (item.Key == "ND" || string.IsNullOrEmpty(item.Key))
{
// log anomalia...
lgTrace($"Errore in processDynData.02: rawJob.key risulta ND! | rawJob.key: {item.Key} | rawJob.Value: {item.Value}");
}
else
{
sVal = string.Format("[DYNDATA]{0}|{1}", item.Key, item.Value);
// chiamo accodamento...
bool sent = accodaFLog(item.Key, sVal, qEncodeFLog(item.Key, item.Value));
if (sent)
{
// traccio valore DynData x analisi
trackDynData(item.Key, item.Value);
}
}
}
}
}
// salvo array...
lastDynData = currDynData;
}
catch (Exception exc)
{
lgError(exc, "Eccezione in processDynData");
}
}
// ora popolo prod data dai dynData...
foreach (var item in currDynData)
{
// se configurato x scrivere valori in WRITE PROD
if (IOBConfFull.FluxLog.CopyDyn2MemWrite)
{
// se presente nelle memorie write/read --> metto in curr data...
if (memMap != null && memMap.mMapWrite.ContainsKey(item.Key) && !string.IsNullOrEmpty(item.Value))
{
upsertKey(item.Key, item.Value);
}
}
// verifico area read x i lastProd...
if (memMap != null && memMap.mMapRead.ContainsKey(item.Key) && !string.IsNullOrEmpty(item.Value))
{
upsertKeyLP(item.Key, item.Value);
}
}
}
}
}
else
{
lgError("Errore connessione mancante x getDynData");
}
}
}
}
/// <summary>
/// Processa gestione memoria x invio dati QUANDO IOB è disconnesso da CNC...
/// </summary>
public void processMemoryDiscon()
{
// init obj display
newDisplayData currDispData = new newDisplayData();
// controllo contatore invio "keepalive"... invio solo a scadenza
int disconnMaxSec = utils.CRI("disconMaxSec");
if (DateTime.Now.Subtract(lastDisconnCheck).TotalSeconds > disconnMaxSec)
{
// resetto tutti i valori BYTE IN/PREV/OUT... così invio macchina spenta...
B_input = 0;
B_output = 0;
B_previous = -1;
accodaSigIN(ref currDispData);
// update controllo
lastDisconnCheck = DateTime.Now;
lgInfo($"Send 00 | disconMaxSec: {disconnMaxSec}");
}
raiseRefresh(currDispData);
}
/// <summary>
/// Effettua processing mode/status (EDIT/MDI/...)
/// </summary>
public virtual void processMode()
{ }
/// <summary>
/// Effettua processing ALTRI contatori/parametri (ed invia ad IO)
/// </summary>
public virtual async Task processOtherCounters()
{
await Task.Delay(1);
}
/// <summary>
/// Effettua processing del recupero delle OVERRIDE (spindle, feedrate, rapid)
/// </summary>
public virtual void processOverride()
{
bool enableByApp = utils.CRB("enableOverrides");
Dictionary<string, string> currOverride = new Dictionary<string, string>();
if (enableByApp || IOBConfFull.FluxLog.EnableOverrides)
{
lgInfo("Inizio processOverride");
if (connectionOk)
{
currOverride = getOverrides();
}
else
{
lgError("Errore connessione mancante x getOverrides");
}
// SE sono connesso...
if (connectionOk)
{
// se HO dei valori override...
if (currOverride.Count > 0)
{
// verifico SE sia cambiato il programma...
if (lastOverrideFS != currOverride["FEED_OVER"] || lastOverrideRapid != currOverride["RAPID_OVER"])
{
// salvo!
lastOverrideFS = currOverride["FEED_OVER"];
lastOverrideRapid = currOverride["RAPID_OVER"];
// per ogni valore del dizionario mostro ed accodo!
string sVal = "";
foreach (var item in currOverride)
{
// verifico NON sia un ND...
if (item.Key == "ND" || string.IsNullOrEmpty(item.Key))
{
// log anomalia...
lgTrace($"Errore in processOverride: rawJob.key risulta ND! | rawJob.key: {item.Key} | rawJob.Value: {item.Value}");
}
else
{
sVal = string.Format("[OVERRIDES]{0}|{1}", item.Key, item.Value);
// chiamo accodamento...
bool sent = accodaFLog(item.Key, sVal, qEncodeFLog(item.Key, item.Value));
if (sent)
{
// traccio valore DynData x analisi
trackDynData(item.Key, item.Value);
}
}
}
}
}
}
}
}
/// <summary>
/// Processa esecuzione task ricevuti
/// </summary>
/// <param name="task2exe"></param>
/// <param name="codTav"></param>
/// <returns>Restituisce elenco task svolti --> per accodamento risposta</returns>
public Dictionary<string, string> ProcessTask(Dictionary<string, string> task2exe, string codTav)
{
Dictionary<string, string> taskDone = new Dictionary<string, string>();
Dictionary<string, string> task2Add = new Dictionary<string, string>();
// eseguo realmente solo se NON disabilitata questa gestione (caso doppio PLC/HMI)...
if (!IOBConfFull.Device.DisabExeTask)
{
if (task2exe != null && task2exe.Count > 0)
{
string logMsg = $"Task2Exe S01: {task2exe.Count} task ricevuti";
if (!string.IsNullOrEmpty(codTav))
{
logMsg += $" | codTav: {codTav}";
}
lgInfo(logMsg);
int idTask = 0;
foreach (var item in task2exe)
{
idTask++;
lgInfo($"[{idTask:00}] - {item.Key} --> {item.Value}");
// verifico SE il task abbia un duplo writeLink e nel caso lo aggiungo...
var linkVal = getOptWriteLink(item.Key);
if (!string.IsNullOrEmpty(linkVal))
{
// aggiungo a task2add SE manca...
if (!task2Add.ContainsKey(linkVal))
{
task2Add.Add(linkVal, item.Value);
}
lgInfo($"Aggiunta task linked: {linkVal} -> {item.Value}");
}
}
// se ho task Link da aggiungere li aggiungo!
if (task2Add.Count > 0)
{
foreach (var item in task2Add)
{
task2exe.Add(item.Key, item.Value);
}
}
// chiamo procedura esecutiva (diversa x ogni IOB)
taskDone = executeTasks(task2exe, codTav);
lgInfo($"Task2Exe S02: eseguiti {taskDone.Count} task");
// loggo tutti i task done...
foreach (var item in taskDone)
{
sendToTaskWatch(item.Key, item.Value, codTav);
}
}
}
return taskDone;
}
#if false
/// <summary>
/// Processa esecuzione task ricevuti
/// </summary>
/// <param name="task2exe"></param>
/// <param name="codTav"></param>
/// <returns></returns>
public async Task<Dictionary<string, string>> ProcessTask(Dictionary<string, string> task2exe, string codTav)
{
Dictionary<string, string> taskDone = new Dictionary<string, string>();
Dictionary<string, string> task2Add = new Dictionary<string, string>();
// eseguo realmente solo se NON disabilitata questa gestione (caso doppio PLC/HMI)...
if (!IOBConfFull.Device.DisabExeTask)
{
if (task2exe != null)
{
string logMsg = $"Task2Exe S01: {task2exe.Count} task ricevuti";
if (!string.IsNullOrEmpty(codTav))
{
logMsg += $" | codTav: {codTav}";
}
lgInfo(logMsg);
int idTask = 0;
foreach (var item in task2exe)
{
idTask++;
lgInfo($"[{idTask:00}] - {item.Key} --> {item.Value}");
// verifico SE il task abbia un duplo writeLink e nel caso lo aggiungo...
var linkVal = getOptWriteLink(item.Key);
if (!string.IsNullOrEmpty(linkVal))
{
// aggiungo a task2add SE manca...
if (!task2Add.ContainsKey(linkVal))
{
task2Add.Add(linkVal, item.Value);
}
lgInfo($"Aggiunta task linked: {linkVal} -> {item.Value}");
}
}
// se ho task Link da aggiungere li aggiungo!
if (task2Add.Count > 0)
{
foreach (var item in task2Add)
{
task2exe.Add(item.Key, item.Value);
}
}
// chiamo procedura esecutiva (diversa x ogni IOB)
taskDone = executeTasks(task2exe, codTav);
lgInfo($"Task2Exe S02: eseguiti {taskDone.Count} task");
// loggo tutti i task done...
foreach (var item in taskDone)
{
sendToTaskWatch(item.Key, item.Value, codTav);
}
// ora chiamo la cancellazione dei task eseguiti...
foreach (var item in taskDone)
{
await remTask2exe(item.Key, item.Value, codTav);
}
}
}
return taskDone;
}
#endif
/// <summary>
/// Classe fittizia in caso di processing task in MsVHF
/// </summary>
public virtual void processVHF()
{
}
/// <summary>
/// Classe fittizia in caso di processing watchdog data
/// </summary>
public virtual void processWhatchDog()
{
}
/// <summary>
/// Effettua rilettura del contapezzi dal server MP/IO
/// </summary>
/// <param name="forceCountRec">Forza rilettura da DB tempi ciclo rilevati</param>
public void pzCntReload(bool forceCountRec, string forceMach = "")
{
// se NON disabilitato contapezzi
if (!IOBConfFull.Device.EnabPzCount)
{
lgDebug("pzCntReload disabilitato da EnabPzCount");
}
else
{
// legge da IO server ULTIMO valore CONTPEZZI al riavvio...
string currServerCount = "";
string lastIdxODL = "";
string calcUrl = "";
if (!disableOdl)
{
var srvAlive = CheckServerAlive();
if (srvAlive)
{
if (isMulti)
{
// disabilitato per macchina MULTI, da riportare logica da OpcUa ...
}
else
{
// leggo PRIMA ODL ....
calcUrl = string.IsNullOrEmpty(forceMach) ? urlGetCurrODL : urlGetCurrODL.Replace(IOBConfFull.General.CodIOB, forceMach);
lastIdxODL = utils.callUrl(calcUrl);
lgTrace($"Lettura ODL dall'url {calcUrl} --> {lastIdxODL}");
// se ho valori in coda da trasmettere uso dati REDIS
if (forceCountRec)
{
// uso dati da TCiclo registrati...
calcUrl = string.IsNullOrEmpty(forceMach) ? urlGetPzCountRec : urlGetPzCountRec.Replace(IOBConfFull.General.CodIOB, forceMach);
currServerCount = utils.callUrl(calcUrl);
lgInfo($"Lettura contapezzi da TCiclo registrati dall'url {calcUrl} --> num pz: {currServerCount}");
}
else
{
// uso il contapezzi dichiarato dall'IOB stesso
calcUrl = string.IsNullOrEmpty(forceMach) ? urlGetPzCount : urlGetPzCount.Replace(IOBConfFull.General.CodIOB, forceMach);
currServerCount = utils.callUrl(calcUrl);
lgInfo($"Lettura contapezzi dall'url {calcUrl} --> {currServerCount}");
}
// controllo: SE NON HO ODL...
if (string.IsNullOrEmpty(lastIdxODL) || lastIdxODL == "0")
{
// NON AGGIORNO
contapezziIOB = contapezziPLC;
lgError($"Errore lettura ODL (vuoto) resta tutto invariato contapezzi: {contapezziIOB} | contapezziPLC {contapezziPLC}");
}
else
{
if (!string.IsNullOrEmpty(currServerCount))
{
// se "-1" resto a ultimo...
if (currServerCount != "-1")
{
int newVal = -1;
Int32.TryParse(currServerCount, out newVal);
contapezziIOB = newVal > -1 ? newVal : contapezziIOB;
lgInfo("Ricevuta conferma da server di {0} pezzi registrati per ODL", currServerCount);
}
else
{
// NON AGGIORNO
contapezziIOB = contapezziPLC;
lgError($"Errore lettura contapezzi (-1) - uso contapezziPLC --> {contapezziPLC}");
}
}
else
{
// registro che ho UN NUOVO ODL
lgInfo($"Lettura ODL in pzCntReload, currIdxODL {currIdxODL} --> lastIdxODL {lastIdxODL}");
// se ODL differente e NUOVO è zero --> resetto!
if (currIdxODL.ToString() != lastIdxODL && lastIdxODL == "0")
{
// cambiato ODL quindi reset...
contapezziIOB = 0;
lgInfo("Nuovo ODL==0, RESET contapezzi (-->ZERO)");
}
}
// provo a salvare nuovo ODL
int.TryParse(lastIdxODL, out currIdxODL);
lgInfo($"ODL | currIdxODL: {currIdxODL}");
}
}
}
else
{
// se server NON pronto...
contapezziIOB = contapezziPLC;
lgWarn("Errore server NON pronto in pzCntReload");
}
}
}
}
/// <summary>
/// Fornisce il valore di flusso e valore in formato valido x messa in coda nel formato
/// dtEve#flusso#valore#cont <paramref name="flusso">Flusso dati</paramref><paramref
/// name="valore">Valore da salvare</paramref>
/// </summary>
public string qEncodeFLog(string flusso, string valore)
{
string answ = "";
// solo se valore !="", su DynData...
if (flusso != "DYNDATA" || !string.IsNullOrEmpty(valore))
{
try
{
answ = $"{DateTime.Now:yyyyMMddHHmmssfff}#{flusso}#{valore}#{counterULog}";
}
catch
{ }
}
return answ;
}
/// <summary>
/// Fornisce il valore di flusso e valore in formato valido x messa in coda nel formato
/// dtEve#flux#valReq#cont <paramref name="eventDT">DataOra evento
/// registrato</paramref><paramref name="flusso">Flusso dati</paramref><paramref
/// name="valore">Valore da salvare</paramref>
/// </summary>
public string qEncodeFLog(DateTime eventDT, string flusso, string valore)
{
string answ = "";
try
{
answ = $"{eventDT:yyyyMMddHHmmssfff}#{flusso}#{valore}#{counterFLog}";
}
catch
{ }
return answ;
}
/// <summary>
/// Fornisce il valore di UserLog e valore in formato valido x messa in coda nel formato:
/// dtEve#flusso#valReq#cont#matrOpr#label#valNum <paramref name="flusso">Flusso dati
/// (RC/RS/DI)</paramref><paramref name="valore">Valore da inviare
/// (valString</paramref><paramref name="matrOpr">Matricola operatore</paramref><paramref
/// name="label">Valore etichetta: causale scarto / tagCode</paramref><paramref
/// name="valNum">Valore numerico: esitoOk (0/1) / nuo scarti</paramref>
/// </summary>
public string qEncodeULog(string flusso, string valore, int matrOpr, string label, int valNum)
{
string answ = "";
try
{
answ = $"{DateTime.Now:yyyyMMddHHmmssfff}#{flusso}#{valore}#{matrOpr}#{label}#{valNum}#{counterULog}";
}
catch
{ }
return answ;
}
/// <summary>
/// Effettua lettura dati
/// <paramref name="currDispData">Parametri da aggiornare x display in form</paramref>
/// </summary>
public virtual void readAllData(ref newDisplayData currDispData)
{
if (currDispData == null)
{
currDispData = new newDisplayData();
}
try
{
if (DemoIn)
{
// segnalo che sono in Demo
currDispData.semIn = Semaforo.SV;
}
if (connectionOk)
{
if (!IOBConfFull.Device.DisabStateCh)
{
readSemafori(ref currDispData);
}
else
{
// forzo lettura OK
currDispData.semIn = Semaforo.SV;
}
}
else
{
lgError("Errore connessione mancante x readSemafori");
}
nReadIN++;
// aggiorno valore mostrato...
displayRawData(ref currDispData);
}
catch
{
currDispData.semIn = Semaforo.SR;
}
raiseRefresh(currDispData);
}
/// <summary>
/// Effettua lettura semafori principale <paramref name="currDispData">Parametri da
/// aggiornare x display in form</paramref>
/// </summary>
public virtual void readSemafori(ref newDisplayData currDispData)
{
lastReadPLC = DateTime.Now;
}
/// <summary>
/// Aggiunge ai dati da inviare alla parentform i valori di RawInput rilevati (32bit)
/// 2021.08.27: filtrati secondo i valori setup start/size RawDataInput
/// </summary>
public virtual void reportRawInput(ref newDisplayData currDispData)
{
// processo eventualmente aggiungendo ad elementi esistenti...
if (currDispData == null)
{
currDispData = new newDisplayData();
}
try
{
StringBuilder sb = new StringBuilder();
sb.Append($"B_input --> {(short)B_input}{Environment.NewLine}");
sb.Append($"{baseUtils.binaryForm(B_input)}{Environment.NewLine}");
sb.Append($"{Environment.NewLine}");
sb.Append($"----------- RAW Data BankConf -----------{Environment.NewLine}");
// Do x scontato siano VALIDI i valori di RawDataInputStart / size --> filtro...
var filtRawData = new byte[RawDataInputSize];
Array.Copy(RawInput, RawDataInputStart, filtRawData, 0, RawDataInputSize);
int i = RawDataInputStart;
foreach (var item in filtRawData)
{
sb.Append($"B{i:00} --> {baseUtils.binaryForm(item)} = {(short)item}{Environment.NewLine}");
i++;
}
sb.Append("-------------------------------");
currDispData.currBitmap = sb.ToString();
}
catch
{ }
}
/// <summary>
/// Metodo generico di reset contapezzi...
/// </summary>
/// <returns></returns>
public virtual bool resetContapezziPLC(string codTav)
{
lgInfo("Generic.resetContapezziPLC");
return false;
}
/// <summary>
/// Effettua salvataggio in LUT del valore ricevuto (valori string)
/// - non serve calcolo medie o altro
/// - accoda in out val e basta
/// </summary>
/// <param name="outVal">Array eventi da popolare</param>
/// <param name="valore">valore da salvare</param>
/// <param name="chiave">ID/chiave di riferimento</param>
/// <returns></returns>
public virtual void saveAlarmString(ref Dictionary<string, string> outVal, string valore, string chiave)
{
DateTime adesso = DateTime.Now;
//check obj preliminare
if (outVal == null)
{
outVal = new Dictionary<string, string>();
}
// default invio: blindato a 120 sec SE non trova conf x fluxLogResendPeriod
int vetoSendAlarms = 120;
if (fluxLogReduce)
{
vetoSendAlarms = 60 * fluxLogResendPeriod;
}
// verifico se sia scaduto OVVERO variato rispetto a prima oppure oltre tempo
bool scaduto = !LastTSSSend.ContainsKey(chiave) || (LastTSSSend[chiave].AddSeconds(vetoSendAlarms) < adesso);
bool cambiato = !LastTSS.ContainsKey(chiave) || !LastTSS[chiave].Equals(valore);
if (scaduto || cambiato)
{
outVal.Add(chiave, valore);
LastTSS[chiave] = valore;
LastTSSSend[chiave] = adesso;
}
}
/// <summary>
/// metodo dummy x salvataggio aree memoria conf x CN
/// </summary>
/// <param name="tipo">tipo di DUMP</param>
public virtual void saveMemDump(dumpType tipo)
{
}
/// <summary>
/// Effettua salvataggio in LUT del valore ricevuto (valori numerici)
/// </summary>
/// <param name="outVal"></param>
/// <param name="valore"></param>
/// <param name="chiave"></param>
/// <returns></returns>
public virtual void saveValue(ref Dictionary<string, string> outVal, string chiave, double valore)
{
//check obj preliminare
if (outVal == null)
{
outVal = new Dictionary<string, string>();
}
bool scaduto = stackVal_TSVC(chiave, valore, DateTime.Now);
// recupero VC
valore = getVal_TSVC(chiave, scaduto);
// 2025.08.05: verifico se il valore SIA configurato come onlyIncr...
if (IOBConfFull.Memory.mMapRead.ContainsKey(chiave))
{
// in quel caso recupero ultimo valore
var dataRec = IOBConfFull.Memory.mMapRead[chiave];
// ....e se inferiore uso quello...
if (dataRec.onlyIncr && valore < LastTSVC[chiave])
{
valore = LastTSVC[chiave];
}
}
// 2023.11.15: gestione deduplica (opzionale) fluxlog... in caso invalida scaduto...
if (fluxLogReduce && scaduto)
{
/*-------------------------------
* logica processing:
* - confronto con valore precedente (se non c'è allora registro questo)
* - se variato OLTRE deadband --> nulla cambia
* - se NON variato x deadband --> accodo SOLO SE scaduto tempo resend
* ------------------------------ */
DateTime adesso = DateTime.Now;
// cerco tra i veto...
if (fluxLogReduceVeto.ContainsKey(chiave))
{
// per prima cosa verifico che sia ancora valido il veto...
if (adesso < fluxLogReduceVeto[chiave])
{
// verifico valore precedente + deadband x decidere se sia davvero scaduto
if (fluxLogReduceLast.ContainsKey(chiave))
{
scaduto = Math.Abs(fluxLogReduceLast[chiave] - valore) > fluxLogRedDeadBand;
if (scaduto)
{
lgTrace($"saveValue: deadBand superata | {chiave} | old: {fluxLogReduceLast[chiave]:F3} | {valore:F3} | DBand: {fluxLogRedDeadBand}");
}
}
}
// altrimenti creo un nuovo veto lasciando inalterata la scadenza...
else
{
fluxLogReduceVeto[chiave] = adesso.AddMinutes(fluxLogResendPeriod);
}
}
// se non ci fosse metto veto con periodo std...
else
{
fluxLogReduceVeto.Add(chiave, adesso.AddMinutes(fluxLogResendPeriod));
if (fluxLogReduceLast.ContainsKey(chiave))
{
fluxLogReduceLast[chiave] = valore;
}
else
{
fluxLogReduceLast.Add(chiave, valore);
}
}
}
if (scaduto)
{
/*--------------------------------------------------
* FIX gestione decimali e formati IT/EN
* - limite a 3 digit x valore float
* - culture invariant (
* - richiede CHECK i vari double/float parser...
*
* per decodificare usare ad esempio:
*
* bool success = double.TryParse(rawVal, NumberStyles.Any, CultureInfo.InvariantCulture, out cntDouble);
* */
outVal.Add(chiave, valore.ToString("F3", CultureInfo.InvariantCulture));
//outVal.Add(chiave, $"{valore:N3}");
LastTSVC[chiave] = valore;
fluxLogReduceLast[chiave] = valore;
lgDebug($"saveValue: valore accodato | {chiave}: {valore:F3}");
}
else
{
lgTrace($"saveValue: filtro attivo | {chiave}: {valore:F3}");
}
}
/// <summary>
/// Effettua salvataggio in LUT del valore ricevuto (valori string)
/// - non serve calcolo medie o altro
/// - accoda in out val e basta
/// </summary>
/// <param name="outVal">Array eventi da popolare</param>
/// <param name="chiave">ID/chiave di riferimento</param>
/// <param name="valore">valore da salvare</param>
/// <returns></returns>
public virtual void saveValueString(ref Dictionary<string, string> outVal, string chiave, string valore)
{
DateTime adesso = DateTime.Now;
//check obj preliminare
if (outVal == null)
{
outVal = new Dictionary<string, string>();
}
int maxVetoSeconds = 60;
// cerco VC...
if (TSVC_Data.ContainsKey(chiave))
{
maxVetoSeconds = TSVC_Data[chiave].Period;
}
// se ho attivo il veto invio fluxLogReduce metto periodo a minuti indicati...
if (fluxLogReduce)
{
maxVetoSeconds = fluxLogResendPeriod * 60;
}
// verifico se sia scaduto OVVERO variato rispetto a prima oppure oltre tempo
bool scaduto = !LastTSSSend.ContainsKey(chiave) || (LastTSSSend[chiave].AddSeconds(maxVetoSeconds) < adesso);
bool cambiato = !LastTSS.ContainsKey(chiave) || !LastTSS[chiave].Equals(valore);
if (scaduto || cambiato)
{
outVal.Add(chiave, valore);
LastTSS[chiave] = valore;
LastTSSSend[chiave] = adesso;
}
}
/// <summary>
/// Invio la variazione dello stato allarmi (se avvenuta)
/// </summary>
/// <param name="memAddr">COD memoria allarmi</param>
/// <param name="index">Indice memoria allarmi</param>
/// <param name="lastStatus">ultimo stato rilevato</param>
/// <param name="currStatus">stato corrente</param>
/// <param name="AlarmList">Lista allarmi validi per l'area</param>
/// <returns></returns>
public bool sendAlarmVariations(string memAddr, int index, uint lastStatus, uint currStatus, List<string> AlarmList)
{
bool fatto = false;
if (lastStatus != currStatus)
{
List<string> ActiveAlarmList = new List<string>();
// invio GET del MemoryAddress, del banco e del valore currStatus
lastUrl = $"{urlSendAlarm}?memAddr={memAddr}&index={index}&currStatus={currStatus}";
if (currStatus == 0)
{
ActiveAlarmList.Add("ALL OK");
}
else
{
// calcolo quali allarmi mostrare dato currStatus ed elenco allarmi
string bitMap = Convert.ToString(currStatus, 2);
int bitLen = bitMap.Length;
// ciclo x associare tutti gli allarmi
for (int i = 0; i < bitLen; i++)
{
// se è 1 --> aggiungo!
if (bitMap[bitLen - i - 1] == '1')
{
// se sono entro limiti
if (AlarmList.Count >= i)
{
ActiveAlarmList.Add($"{i:000}-{AlarmList.ElementAt(i)}");
}
//else
//{
// ActiveAlarmList.Add($"Unknown Num.{i}");
//}
}
}
}
string rawData = JsonConvert.SerializeObject(ActiveAlarmList);
string resp = utils.CallUrlPost(lastUrl, rawData);
//string resp = utils.callUrlAsync(lastUrl, rawData);
if (resp != null)
{
if (resp.ToUpper() == "OK")
{
// segnalo okReport
fatto = true;
}
}
else
{
lgError($"Errore in invio richiesta registrazione allarme | resp: {resp} | URL: {lastUrl}{Environment.NewLine}Payload:{Environment.NewLine}{rawData}");
}
}
return fatto;
}
/// <summary>
/// Invia una LISTA di valori
/// </summary>
/// <param name="tipoUrl"></param>
/// <param name="queueVal"></param>
public async Task<bool> sendDataBlock(urlType tipoUrl, List<string> listQueueVal, bool force = false)
{
bool fatto = false;
// init obj display
newDisplayData currDispData = new newDisplayData();
if (listQueueVal != null)
{
try
{
// recupero e formatto URL dati da coda...
lastUrl = urlDataBlock(tipoUrl);
// in base al tipo di dato compongo il payload Json da inviare
string payload = jsonPayload(tipoUrl, listQueueVal);
// async a true SE FLog
bool doAsync = tipoUrl == urlType.FLog ? true : false;
// se NON sono in demo effettuo invio!
if (!DemoOut)
{
// SE server alive...
if (await CheckServerAliveAsync())
{
// chiamo URL!
string answ = await callUrlWithPayloadAsync(lastUrl, payload, doAsync);
// valutare invio REST alternativo...
#if false
// invio alternativo nuovo
if (string.IsNullOrEmpty(answ))
{
answ = utils.ExecCallPostPlain(lastUrl, payload);
}
#endif
// loggo!
lgInfo($"[SEND payload] TipoURL: {tipoUrl} | {listQueueVal.Count} records --> {answ}");
// se "OK" verde, altrimenti errore --> ROSSO
if (answ.Contains("OK"))
{
fatto = true;
currDispData.semOut = Semaforo.SV;
// se oltre 1 min NON era online --> check pezzi!
if (DateTime.Now.Subtract(lastIobOnline).TotalMinutes > 1 && !isMulti)
{
lgInfo($"sendDataBlock --> offline timeout ({lastIobOnline}) --> pzCntReload(true)");
pzCntReload(true);
}
lastIobOnline = DateTime.Now;
}
else
{
currDispData.semOut = Semaforo.SR;
}
}
else
{
lgInfo($"[SERVER KO] {listQueueVal.Count}");
}
}
else
{
currDispData.semOut = Semaforo.SV;
// loggo!
lgInfo($"{listQueueVal.Count} records --> [SIM]");
}
nSendOut += listQueueVal.Count;
// riporto cosa inviato
currDispData.newUrlCallData = lastUrl;
// aggiorno data ultimo watchdog...
lastWatchDog = DateTime.Now;
}
catch
{
currDispData.semOut = Semaforo.SR;
}
}
raiseRefresh(currDispData);
return fatto;
}
/// <summary>
/// Effettua invio a MoonPro del valore richiesto
/// </summary>
/// <param name="tipoUrl"></param>
/// <param name="queueVal">
/// Valore da trasmettere: es
/// INPUT: lo status rilevato in HEX
/// FLog: il valore da trasmettere per il flusso indicato
/// </param>
public async Task sendToMoonPro(urlType tipoUrl, string queueVal)
{
// controllo NON nullo..
if (queueVal != null)
{
// init obj display
newDisplayData currDispData = new newDisplayData();
try
{
// recupero e formatto URL dati da coda...
switch (tipoUrl)
{
case urlType.FLog:
lastUrl = urlFLog(queueVal);
break;
case urlType.SignIN:
lastUrl = urlInput(queueVal);
break;
default:
lastUrl = "";
break;
}
// se NON sono in demo effettuo invio!
if (!DemoOut)
{
// SE server alive...
if (await CheckServerAliveAsync())
{
// chiamo URL!
string answ = await callUrl(lastUrl, false);
// loggo!
lgDebug(string.Format("[SEND] {0} -> {1}", queueVal, answ));
// se oltre 1 min NON era online --> check pezzi!
if (DateTime.Now.Subtract(lastIobOnline).TotalMinutes > 1 && !isMulti)
{
lgInfo($"sendToMoonPro --> offline timeout ({lastIobOnline}) --> pzCntReload(true)");
pzCntReload(true);
}
// se richiesto effettuo refresh contapezzi
if (needRefreshPzCount && !isMulti)
{
pzCntReload(true);
needRefreshPzCount = false;
}
lastIobOnline = DateTime.Now;
// se "OK" verde, altrimenti errore --> ROSSO
if (answ == "OK")
{
currDispData.semOut = Semaforo.SV;
}
else
{
currDispData.semOut = Semaforo.SR;
}
}
else
{
lgError(string.Format("[SERVER KO] {0}", queueVal));
}
}
else
{
currDispData.semOut = Semaforo.SV;
// loggo!
lgDebug(string.Format("{0} -> [SIM]", queueVal));
}
nSendOut++;
currDispData.newUrlCallData = lastUrl;
// aggiorno data ultimo watchdog...
lastWatchDog = DateTime.Now;
}
catch
{
currDispData.semOut = Semaforo.SR;
}
raiseRefresh(currDispData);
}
else
{
lgTrace($"Richiesto invio valore nullo, salto");
}
}
/// <summary>
/// Metodo generico di IMPOSTAZIONE FORZATA del contapezzi...
/// </summary>
/// <param name="newPzCount">Pezzi richiesti</param>
/// <returns></returns>
public virtual bool setcontapezziPLC(int newPzCount, string codTav)
{
return false;
}
/// <summary>
/// Effettua impostazione del conteggio pezzi richiesti
/// </summary>
/// <returns></returns>
public virtual bool setPzComm(int pzReq)
{
return false;
}
/// <summary>
/// Processing di Variabili Campionarie x TimeSeries, accoda valori x la VC (se esiste) e
/// restituisce bool val se SCADUTO periodo controllo
/// </summary>
/// <param name="VCName">Nome della VC</param>
/// <param name="VCVal">Valore (nuovo) delal VC</param>
/// <returns></returns>
public bool stackVal_TSVC(string VCName, double VCVal, DateTime dtLimit)
{
bool answ = false;
// cerco VC...
if (TSVC_Data.ContainsKey(VCName))
{
TSVC_Data[VCName].dataArray.Add(VCVal);
// ora verifico scadenza...
if (TSVC_Data[VCName].DTStart.AddSeconds(TSVC_Data[VCName].Period) < dtLimit)
{
// 2026.01.20: solo se ho almeno 3 valori altrimenti rischio zeri...
answ = TSVC_Data[VCName].dataArray.Count > 2;
//answ = true;
}
lgTrace($"stackVal_TSVC | {VCName} | {VCVal} | scaduta: {answ}");
}
return answ;
}
/// <summary>
/// Avvia l'adapter sulla porta richiesta
/// </summary>
/// <param name="resetQueue">indica se sia richiesto di SVUOTARE le code delle info</param>
public virtual void startAdapter(bool resetQueue)
{
DateTime adesso = DateTime.Now;
DateTime scaduto = adesso.AddMinutes(-10);
lgInfo("Starting adapter...");
maxJsonData = utils.CRI("maxJsonData");
maxJsonDataEv = utils.CRI("maxJsonDataEv");
parentForm.commPlcActive = false;
adpRunning = true;
dtAvvioAdp = adesso;
lastWatchDog = scaduto;
lastPING = scaduto;
lastReadPLC = scaduto;
lastDisconnCheck = scaduto;
TimingData.resetData();
// aggiungo altri defaults
setDefaults(resetQueue);
adpTryRestart = true;
// sistemo altri check avvio
dtVetoQueueIN = DateTime.Now.AddSeconds(vetoQueueIn);
queueInEnabCurr = false;
string msgVeto = $"Impostato veto QUEUE-IN a {vetoQueueIn}s | fino alle {dtVetoQueueIN:yyyy.MM.dd HH:mm:ss}";
lgInfoStartup(msgVeto);
lgDebug($"startAdapter completed | veto queueIn impostato per {vetoQueueIn}s fino alle {dtVetoQueueIN:yyyy.MM.dd HH:mm:ss}");
}
/// <summary>
/// Ferma l'adapter...
/// </summary>
/// <param name="tryRestart">
/// indica se si debba tentare di riavviare l'adapter (con caduta connessione viene fermato
/// in automatico)
/// </param>
/// <param name="forceDequeue">indica se sia richiesto di SVUOTARE le code delle info</param>
public virtual async Task stopAdapter(bool tryRestart, bool forceDequeue)
{
// controllo che non ci sia redis queue altrimenti NON forza svuotamento...
if (forceDequeue && !IOBConfFull.General.EnabRedisQue)
{
// svuoto le code dei valori letti e non ancora trasmessi...
parentForm.displayTaskAndLog("[CLOSING] Svuotamento FORZATO coda segnali...", true);
while (QueueIN.Count > 0)
{
// INVIO COMUNQUE...!!!
string valore = "";
QueueIN.TryDequeue(out valore);
await sendToMoonPro(urlType.SignIN, valore);
}
parentForm.displayTaskAndLog("[CLOSING] Svuotamento FORZATO coda FluxLOG...", true);
// se ho + di 2 elementi in coda --> uso invio JSON in blocco...
if (QueueFLog.Count > minJsonData)
{
while (QueueFLog.Count > 0)
{
List<string> listaValori = new List<string>();
// se ho + di maxJsonData elementi --> invio un set di dati alla volta
if (QueueFLog.Count > maxJsonData)
{
string currVal = "";
// prendoi primi maxJsonDataValori
for (int i = 0; i < maxJsonData; i++)
{
QueueFLog.TryDequeue(out currVal);
listaValori.Add(currVal);
}
await sendDataBlock(urlType.FLog, listaValori);
}
else
{
// invio in blocco
listaValori = QueueFLog.ToList();
// invio
await sendDataBlock(urlType.FLog, listaValori);
// svuoto! NO REDIS
QueueFLog = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueFLog", false, redisMan);
//QueueFLog = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueFLog", IOBConfFull.General.EnabRedisQue, redisMan);
}
}
// HO FINITO invio di FLog...
}
else
{
string currVal = "";
while (QueueFLog.Count > 0)
{
// INVIO COMUNQUE...!!!
QueueFLog.TryDequeue(out currVal);
await sendToMoonPro(urlType.FLog, currVal);
}
}
// svuoto coda ULog
while (QueueULog.Count > 0)
{
List<string> listaValori = new List<string>();
// se ho + di maxJsonData elementi --> invio un set di dati alla volta
if (QueueULog.Count > maxJsonData)
{
string currVal = "";
// prendoi primi maxJsonDataValori
for (int i = 0; i < maxJsonData; i++)
{
QueueULog.TryDequeue(out currVal);
listaValori.Add(currVal);
}
await sendDataBlock(urlType.ULog, listaValori);
}
else
{
// invio in blocco
listaValori = QueueULog.ToList();
// invio
await sendDataBlock(urlType.ULog, listaValori);
// svuoto!
QueueULog = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueULog", false, redisMan);
}
}
}
parentForm.displayTaskAndLog("[STOP] Stopping adapter...", true);
adpTryRestart = false;
parentForm.displayTaskAndLog("[STOP] Stopping adapter - last periodic data read...", true);
// salvo statistiche
string callKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:CallStats");
await CallMetricsCollector.SaveToRedisAsync(redisMan.currDb, callKey, false);
// chiudo la connessione all'adapter...
tryDisconnect();
dtStopAdp = DateTime.Now;
adpTryRestart = tryRestart;
adpRunning = false;
// chiudo!
parentForm.displayTaskAndLog("Adapter Stopped.", true);
DateTime adesso = DateTime.Now;
lastReadPLC = adesso;
lastPING = adesso;
parentForm.commPlcActive = false;
}
/// <summary>
/// Processo la coda SignalIN...
/// </summary>
public async Task svuotaCodaSignInAsync()
{
// verifico SE la coda abbia dei valori...
if (QueueIN.Count > 0)
{
// invio pacchetto di dati (max da conf)
for (int i = 0; i < nMaxSend; i++)
{
if (QueueIN.Count > 0)
{
string currVal = "";
// se online provo
if (MPOnline)
{
if (IobOnline)
{
// se ho + di 2 elementi in coda --> uso invio JSON in blocco...
if (QueueIN.Count > 1)
{
List<string> listaValori = new List<string>();
// se ho + di maxJsonData elementi --> invio un set di dati alla volta
if (QueueIN.Count > maxJsonDataEv)
{
// prendoi primi maxJsonDataValori
for (int j = 0; j < maxJsonDataEv; j++)
{
QueueIN.TryDequeue(out currVal);
listaValori.Add(currVal);
}
await sendDataBlock(urlType.SignIN, listaValori);
}
else
{
// invio in blocco
listaValori = QueueIN.ToList();
// invio
await sendDataBlock(urlType.SignIN, listaValori);
// svuoto!
QueueIN = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueIN", IOBConfFull.General.EnabRedisQue, redisMan);
}
}
else
{
// INVIO SINGOLO...!!!
QueueIN.TryDequeue(out currVal);
await sendToMoonPro(urlType.SignIN, currVal);
}
// salvo come last signal in il currVal ultimo... SE !=""
if (!string.IsNullOrEmpty(currVal))
{
lastSignInVal = currVal;
}
}
else
{
break;
}
}
else
{
break;
}
}
else
{
break;
}
}
}
}
/// <summary>
/// Metodo base connessione...
/// </summary>
public virtual void tryConnect()
{
dtAvvioAdp = DateTime.Now;
queueInEnabCurr = true;
}
/// <summary>
/// Metodo base disconnessione...
/// </summary>
public virtual void tryDisconnect()
{
queueInEnabCurr = false;
}
/// <summary>
/// Inserimento/aggiornamento chiavi/valore in currProdData + cache REDIS
/// </summary>
/// <param name="chiave"></param>
/// <param name="valore"></param>
/// <returns>True se modificato/inserito, false se INVARIATO</returns>
public bool upsertKey(string chiave, string valore)
{
bool done = false;
if (currProdData.ContainsKey(chiave))
{
// se variato inserisco...
if (currProdData[chiave] != valore)
{
currProdData[chiave] = valore;
done = true;
}
}
else
{
currProdData.Add(chiave, valore);
done = true;
}
if (done)
{
// salvo in redis...
redisMan.redSaveHashDict(rKeyCurrProdData, currProdData);
}
return done;
}
/// <summary>
/// Inserimento/aggiornamento chiavi/valore in lastProdData
/// </summary>
/// <param name="chiave"></param>
/// <param name="valore"></param>
/// <returns>True se modificato/inserito, false se INVARIATO</returns>
public bool upsertKeyLP(string chiave, string valore)
{
bool done = false;
if (lastProdData.ContainsKey(chiave))
{
// se variato inserisco...
if (lastProdData[chiave] != valore)
{
lastProdData[chiave] = valore;
done = true;
}
}
else
{
lastProdData.Add(chiave, valore);
done = true;
}
return done;
}
/// <summary>
/// Formatta URL x invio in DataBlock Json dei dati FLog / eventi
/// </summary>
/// <param name="tipoUrl"></param>
/// <returns></returns>
public virtual string urlDataBlock(urlType tipoUrl)
{
// verifico la parte di link "tipoComando"
string tipoComando = "";
switch (tipoUrl)
{
case urlType.FLog:
tipoComando = "flogJson";
break;
case urlType.SignIN:
tipoComando = "evListJson";
break;
case urlType.RawTransf:
tipoComando = "rawTransfJson";
break;
case urlType.ULog:
tipoComando = "ulogJson";
break;
default:
break;
}
// URL base x input
string answ = $@"{urlCommandIob(tipoComando)}";
// se è disabilitato keepalive aggiungo opzione
if (IOBConfFull.MapoMes.DisabKeepAlive)
{
// se c'è già "?" aggiungo con "&" altrimenti
string sPar = answ.Contains("?") ? "&&" : "?";
answ += $@"{sPar}disabKA=true";
}
return answ;
}
/// <summary>
/// Fornisce URL di tipo FluxLog
/// </summary>
/// <param name="queueVal">valore salvato in coda nel formato dtEve#flux#valore#counter</param>
/// <returns></returns>
public string urlFLog(string queueVal)
{
// URL base x input
string answ = $@"{urlCommandIob("flog")}";
// decodifica valore!
string[] valori = qDecodeIN(queueVal);
// aggiungo flux e valore...
answ += $@"?flux={valori[1]}&&valore={valori[2]}";
// aggiondo dataOra evento e corrente + contatore...
answ += $@"&&dtEve={valori[0]}&&dtCurr={DateTime.Now:yyyyMMddHHmmssfff}&&cnt={valori[3]}";
// se è disabilitato keepalive aggiungo opzione
if (IOBConfFull.MapoMes.DisabKeepAlive)
{
;
answ += $"&&disabKA=true";
}
return answ;
}
/// <summary>
/// URL per recupero dati ODL alla data...
/// </summary>
public string urlGetOdlAtDate(DateTime dtRif)
{
string answ = $@"{urlCommandIob("getOdlAtDate")}?dateRif={dtRif:yyyyMMdd}";
return answ;
}
/// <summary>
/// Fornisce URL INPUT per i parametri richiesti
/// </summary>
/// <param name="queueVal">valore salvato in coda formato dtEve#valore#counter</param>
/// <returns></returns>
public string urlInput(string queueVal)
{
// URL base x input
string answ = $@"{urlCommandIob("input")}";
// decodifica valore!
string[] valori = qDecodeIN(queueVal);
// aggiungo macchina e valore...
answ += $@"?valore={valori[1]}";
// aggiondo dataOra evento e corrente + contatore...
answ += $@"&&dtEve={valori[0]}&&dtCurr={DateTime.Now:yyyyMMddHHmmssfff}&&cnt={valori[2]}";
return answ;
}
/// <summary>
/// Fornisce URL di tipo UserLog
/// </summary>
/// <param name="queueVal">valore salvato in coda nel formato dtEve#flux#valore#counter</param>
/// <returns></returns>
public string urlULog(string queueVal)
{
// URL base x input
string answ = $@"{urlCommandIob("ulog")}";
// decodifica valore!
string[] valori = qDecodeIN(queueVal);
// aggiungo macchina e valore...
answ += $@"?flux={valori[1]}&&valore={valori[2]}";
// aggiondo dataOra evento e corrente + contatore...
answ += $@"&&dtEve={valori[0]}&&dtCurr={DateTime.Now:yyyyMMddHHmmssfff}&&cnt={valori[3]}";
return answ;
}
#endregion Public Methods
#region Private Methods
/// <summary>
/// Test ping + api al server in modalità Async
/// </summary>
/// <param name="maxRetries"></param>
/// <returns></returns>
private async Task<bool> ExecuteApiCheckWithRetryAsync(int maxRetries)
{
var rand = new Random();
for (int i = 0; i <= maxRetries; i++)
{
try
{
// Se non è il primo tentativo, resetta i client e aspetta
if (i > 0)
{
if (i == 4) resetWebClients(); // Reset specifico a metà tentativi
await Task.Delay(rand.Next(150, 500));
}
string resp = await callUrl(urlAlive, false);
if (resp == "OK") return true;
}
catch (Exception ex)
{
if (i == 0) lgError($"Errore API Check: {ex.Message}");
}
}
return false;
}
#if false
/// <summary>
/// Processing di una risposta raw di task2exe
/// </summary>
/// <param name="resp">Risposta come string RAW</param>
/// <param name="codTav">Cod Tav (opzionale)</param>
private async Task ProcessResp(string resp, string codTav)
{
Dictionary<string, string> task2exe = new Dictionary<string, string>();
Dictionary<string, string> taskDone = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(resp) && resp.Length > 2)
{
try
{
task2exe = JsonConvert.DeserializeObject<Dictionary<string, string>>(resp);
// se ho da fare chiamo esecuzione..
if (task2exe.Count > 0)
{
taskDone = await ProcessTask(task2exe, codTav);
}
}
catch (Exception exc)
{
lgError($"Eccezione in ServerGetRequestsAsync.ProcessResp:{Environment.NewLine}{exc}");
}
}
}
#endif
/// <summary>
/// Update stato server
/// </summary>
/// <param name="currentAlive"></param>
private void UpdateServerState(bool currentAlive)
{
if (MPOnline != currentAlive)
{
MPOnline = currentAlive;
parentForm.commSrvActive = currentAlive ? 1 : 0;
if (currentAlive)
{
lgInfo("SERVER ONLINE");
dtVetoPing = DateTime.Now.AddMilliseconds(baseUtils.nextPauseSendMSec * 10);
}
else
{
lgError("SERVER OFFLINE");
// Veto standard per server offline
dtVetoPing = DateTime.Now.AddMilliseconds(baseUtils.nextPauseSendMSec * 20);
utils.dtVetoSend = dtVetoPing;
}
}
else
{
// Se lo stato è invariato (es. era online e resta online), allunghiamo il prossimo controllo
dtVetoPing = DateTime.Now.AddMilliseconds(baseUtils.nextPauseSendMSec * 30);
}
}
#endregion Private Methods
#region Protected Fields
/// <summary>
/// Variabile numero errori vari (in lettura) --&gt; se supera soglia maxErroriCheck --&gt; disconnette
/// </summary>
protected static int numErroriCheck = 0;
protected bool _connOk = false;
/// <summary>
/// valore booleano di check se sia in fase di COMUNICAZIONE ATTIVA con il PLC/NC
/// </summary>
protected bool adpCommAct;
/// <summary>
/// porta x adapter (x restart)
/// </summary>
protected int adpPortNum;
/// <summary>
/// DataOra ultimo avvio adapter x watchdog
/// </summary>
protected DateTime adpStartRun;
/// <summary>
/// Vettore 32 BIT valori in ingresso al filtro
/// </summary>
protected int B_input;
/// <summary>
/// Vettore 32 BIT valori in uscita dal filtro
/// </summary>
protected int B_output;
/// <summary>
/// Vettore 32 BIT valori precedenti
/// </summary>
protected int B_previous = -1;
/// <summary>
/// Cod grupo IOB x creazione PODL al volo
/// </summary>
protected string CodGruppoIob = "ND-00";
/// <summary>
/// Num errori check alive
/// </summary>
protected int currAliveErrors = 0;
/// <summary>
/// Dizionario valori impostati x produzione
/// </summary>
protected Dictionary<string, string> currProdData = new Dictionary<string, string>();
/// <summary>
/// num corrente errori read PLC
/// </summary>
protected int currReadErrors = 0;
/// <summary>
/// num errori send
/// </summary>
protected int currSendErrors = 0;
/// <summary>
/// Tempo di attesa in minuti x lettura contapezzi standard (da .ini / OptPar)
/// </summary>
protected double delayMinReadPzCount = 0;
/// <summary>
/// Fattore di demoltiplicazione dei DynData x ridurre campionamento
/// </summary>
protected int demFactDynData = 1;
/// <summary>
/// Boolean x indicare contapezzi disabilitato forzatamente da IOB
/// </summary>
protected bool disablePzCountByIob = false;
/// <summary>
/// Veto x esecuzione task AutoDossier
/// </summary>
protected DateTime dtVetoAutoDossier = DateTime.Now;
/// <summary>
/// Veto x esecuzione Task2Exe troppo frequenti
/// </summary>
protected DateTime dtVetoTask2Exe = DateTime.Now;
/// <summary>
/// Veto x lettura contapezzi (in caso di autoODL con attesa reset ad esempio...)
/// </summary>
protected DateTime dtVetoReadPzCount = DateTime.Now;
/// <summary>
/// Determina se sia prevista gestione PODL (creazione/avvio/chiusura) come Soitaab
/// </summary>
protected bool EnabelPodlManFull = false;
/// <summary>
/// Abilita riscrittura memoria se trova differenze lettura/richiesti
/// </summary>
protected bool ENABLE_MEM_REWRITE = true;
/// <summary>
/// Abilitazione restart (da opt par...)
/// </summary>
protected bool enableCliRestart = false;
/// <summary>
/// Abilitazione invio dataitem
/// </summary>
protected bool enableSendDataItem = true;
protected int minVetoSendDataItem = 60;
/// <summary>
/// DataOra x veto all'invio dataItem
/// </summary>
protected DateTime dtVetoSenDataItem = DateTime.Now;
/// <summary>
/// Boolean x indicare contapezzi abilitato a livello di conf applicazione
/// </summary>
protected bool enablePzCountByApp = true;
/// <summary>
/// Abilitazione gestione slow data
/// </summary>
protected bool enableSlowData = false;
/// <summary>
/// dizionario (opzionale) xz decodifica file da importare
/// </summary>
protected Dictionary<string, int> FileDecod = new Dictionary<string, int>();
/// <summary>
/// DeadBand x riduzione dati FluxLog (se 0 non gestita)
/// </summary>
protected double fluxLogRedDeadBand = 0;
/// <summary>
/// Determina se sia gestita riduzione dati FluxLog
/// </summary>
protected bool fluxLogReduce = false;
/// <summary>
/// Dizionario dei valori FluxLog ultimi verificati x veto
/// </summary>
protected Dictionary<string, double> fluxLogReduceLast = new Dictionary<string, double>();
/// <summary>
/// Dizionario dei valori FluxLog ultimi tipo STRING verificati x veto
/// </summary>
protected Dictionary<string, string> fluxLogReduceLastString = new Dictionary<string, string>();
/// <summary>
/// Dizionario dei veto send x ogni variabile quando non variata
/// </summary>
protected Dictionary<string, DateTime> fluxLogReduceVeto = new Dictionary<string, DateTime>();
/// <summary>
/// Finestra in minuti x invio dati FluxLog quando invariati
/// </summary>
protected int fluxLogResendPeriod = 60;
/// <summary>
/// indica che è richiesto forzatamente reset contapezzi
/// </summary>
protected bool forcePzReset = false;
/// <summary>
/// indica che è richiesto forzatamente reset contapezzi
/// </summary>
protected DateTime forcePzResetUntil = DateTime.Now.AddMinutes(-1);
/// <summary>
/// DEADBAND globale se impostata (es x gestire variazioni minime valori FluxLog da inviare...)
/// </summary>
protected float GLOBAL_DBAND = 0;
/// <summary>
/// Determina se siano gestite le ricette
/// </summary>
protected bool hasRecipe = false;
/// <summary>
/// Array dei contatori x segnali blinking
/// </summary>
protected int[] i_counters;
/// <summary>
/// Indica impianto IN SETUP (fino a quando SMETTE di esserlo...)
/// </summary>
protected bool inSetup = false;
/// <summary>
/// ultimo tentativo connessione...
/// </summary>
protected DateTime lastConnectTry;
/// <summary>
/// Dizionario ULTIMI valori impostati x produzione
/// </summary>
protected Dictionary<string, string> lastProdData = new Dictionary<string, string>();
/// <summary>
/// Ultimo invio contapezzi (x invio delayed)
/// </summary>
protected DateTime lastPzCountSend;
/// <summary>
/// Dizionario ultimi valori (string) delle TSS
/// </summary>
protected Dictionary<string, string> LastTSS = new Dictionary<string, string>();
/// <summary>
/// Dizionario ultimi valori (string) delle TSS
/// </summary>
protected Dictionary<string, DateTime> LastTSSSend = new Dictionary<string, DateTime>();
/// <summary>
/// Dizionario ultimi valori (double) delle TSVC
/// </summary>
protected Dictionary<string, double> LastTSVC = new Dictionary<string, double>();
/// <summary>
/// Ultima registrazione warning x ODL mancante (x scrivere solo ogni 15 secondi)
/// </summary>
protected DateTime lastWarnODL;
/// <summary>
/// ultima data-ora invio parametri write x ribadire status
/// </summary>
protected DateTime lastWriteParamsUpsert = DateTime.Now;
/// <summary>
/// Separatore linea (tipicamente x commenti)
/// </summary>
protected string lineSep = "--------------------------";
/// <summary>
/// Elenco parametri calcolati da inviare alla macchina (es ricetta con traduzione, RAMA/TFT)
/// </summary>
protected List<objItem> list2Write = new List<objItem>();
/// <summary>
/// Elenco delle eventuali condizioni di veto conditions attive
/// </summary>
protected List<string> ListVetoCond = new List<string>();
/// <summary>
/// Num massimo di errori in funzionalità check alive
/// </summary>
protected int maxAliveErrors = utils.CRI("maxAliveErrors");
/// <summary>
/// Soglia massima errori prima della disconnessione automatica in check
/// </summary>
protected int maxErroriCheck = utils.CRI("maxErroriCheck");
/// <summary>
/// Quantità massima per singola ricetta (per determinare num ricette da inviare)
/// </summary>
protected int maxQtyPerFile = 0;
/// <summary>
/// Num massimo di errori di rete read (dal PLC)
/// </summary>
protected int maxReadErrors = utils.CRI("maxReadErrors");
/// <summary>
/// Periodo massimo (in sec) per letture dati in mancanza di eventi di aggiornamento
/// </summary>
protected int MaxSecReload = 120;
/// <summary>
/// Num massimodi errori di rete send al server
/// </summary>
protected int maxSendErrors = utils.CRI("maxSendErrors");
/// <summary>
/// Numero massimo di tentativi x test ping preliminare
/// </summary>
protected int maxTryPing = 1;
protected string mem2trace = "";
/// <summary>
/// indica se serva refresh parametri e quindi PLC...
/// </summary>
protected bool needRefresh = true;
/// <summary>
/// Indica se usare la parte numerica di un articolo come codice INT
/// </summary>
protected bool numArtCharTrim = false;
/// <summary>
/// Timeout x ping al server
/// </summary>
protected int pingServerMsTimeout = utils.CRI("PingMsTimeout");
/// <summary>
/// DataOra avvio Programma x check avvio
/// </summary>
protected DateTime prgStarted = DateTime.Now;
/// <summary>
/// Ritardo minimo x invio contapezzi
/// </summary>
protected int pzCountDelay = 2500;
/// <summary>
/// Periodo di campionamento x refresh info
/// </summary>
protected int samplePeriod = 1000;
/// <summary>
/// MsSample Period base x campionamenti tpo OCP-UA
/// </summary>
protected int samplePeriodBase = 600;
/// <summary>
/// Dizionario di VC da trattare come TimeSeries (con conf decodificata + processing successivo...)
/// </summary>
protected Dictionary<string, VCData> TSVC_Data = new Dictionary<string, VCData>();
/// <summary>
/// Indica se usare archivio locale ricette vs scarico http/REST
/// </summary>
protected bool useLocalRecipe = true;
/// <summary>
/// Dizionario di VARIABILI da trattare come eventi (da inviare quando cambiano oppure a
/// scadenza periodo...)
/// </summary>
protected Dictionary<string, EVData> VarArray = new Dictionary<string, EVData>();
/// <summary>
/// Dizionario dei divieti di invio x ogni counter gestito
/// </summary>
protected Dictionary<string, DateTime> VetoCounterSend = new Dictionary<string, DateTime>();
/// <summary>
/// Durata in secondi del divieto accodamento segnali IN alla fase di startup
/// </summary>
protected int vetoQueueIn = 10;
/// <summary>
/// Periodo di veto prima di invio nuova conferma dei valori write correnti, default ogni 5 min
/// </summary>
protected double vetoSendWriteUpsert = 1;
/// <summary>
/// Periodo wathdog di default (2 sec se non specificato)
/// </summary>
protected int watchDogPeriod = 2;
#endregion Protected Fields
#region Protected Properties
/// <summary>
/// Valore del num max invii consecutivi da coda...
/// </summary>
protected static int nMaxSend
{
get
{
int answ = 5;
try
{
answ = utils.CRI("nMaxSend");
}
catch
{ }
return answ;
}
}
/// <summary>
/// Indica il counter della keyReq richiesta attiva
/// - gestito tramite Redis
/// - a scadenza 25h
/// - incrementato ogni invio di auto ODL
/// </summary>
protected int countKeyRichiesta
{
get
{
// calcolo keyReq odierna
int answ = redisMan.getKReqCount(dailyKey);
return answ;
}
set
{
// calcolo keyReq odierna
redisMan.setKReqCount(dailyKey, value);
}
}
protected string dailyKey
{
get
{
return DateTime.Today.ToString("yyMMdd");
}
}
/// <summary>
/// Dizionario delle deadband attive
/// </summary>
protected Dictionary<string, float> dictActDBand { get; set; } = new Dictionary<string, float>();
protected Dictionary<string, string> DictNumArt { get; set; } = new Dictionary<string, string>();
protected bool disDynDataRangeCheck { get; set; } = false;
/// <summary>
/// Folder in cui è presente il tool import excel + file json di conf
/// </summary>
protected string exclToolDirPath { get; set; } = "";
/// <summary>
/// Folder in cui salvare i file importati (es excel)
/// </summary>
protected string fileArchiveFolder { get; set; } = "";
/// <summary>
/// Folder da cui importare i file (es excel)
/// </summary>
protected string fileImportFolder { get; set; } = "";
/// <summary>
/// Tipologia di files da importare (default excel)
/// </summary>
protected string fileImportType { get; set; } = "*.xslx";
/// <summary>
/// Dati fluxLog serializzati in redis
/// </summary>
protected List<GenLogRow> fluxLogData
{
get
{
List<GenLogRow> answ = new List<GenLogRow>();
string redKeyFLog = redisMan.redHash($"IOB:CurrData:{IOBConfFull.General.FilenameIOB}:LogFile:FluxLog");
var rawData = redisMan.getRSV(redKeyFLog);
if (!string.IsNullOrEmpty(rawData))
{
answ = JsonConvert.DeserializeObject<List<GenLogRow>>(rawData);
}
return answ;
}
set
{
string redKeyFLog = redisMan.redHash($"IOB:CurrData:{IOBConfFull.General.FilenameIOB}:LogFile:FluxLog");
string fluxLogRaw = JsonConvert.SerializeObject(value);
redisMan.setRSV(redKeyFLog, fluxLogRaw);
}
}
/// <summary>
/// Variabile di appoggio GLOBALE x indicare che si può forzare reset contapezzi con
/// macchina in RUN
/// </summary>
protected bool forceResetInRun { get; set; } = false;
/// <summary>
/// Variabile di appoggio GLOBALE x indicare macchina RUN (es x gestione su reset pezzi)
/// </summary>
protected bool isRunning { get; set; } = false;
/// <summary>
/// Variabile isRunning (NON realtime)
/// </summary>
protected bool isRunningState
{
get
{
bool answ = false;
string cStatus = redisMan.getRSV(redKeyProdRunState);
bool.TryParse(cStatus, out answ);
return answ;
}
set
{
redisMan.setRSV(redKeyProdRunState, $"{value}");
}
}
protected string lastArtDescr
{
get => redisMan.getRSV(redKeyProdLastArt);
set => redisMan.setRSV(redKeyProdLastArt, value);
}
/// <summary>
/// Wrapper di log
/// </summary>
protected override Logger lg
{
get => _logger;
}
/// <summary>
/// Elenco Articoli (x invio verso macchina)
/// </summary>
protected List<ArtRow> ListaArticoli { get; set; } = new List<ArtRow>();
/// <summary>
/// Elenco Job di produzione (x invio verso macchina)
/// </summary>
protected List<JobRow> ListaJobs { get; set; } = new List<JobRow>();
/// <summary>
/// Valore limite MASSIMO di invio di dati come array Json
/// </summary>
protected int maxJsonData { get; set; } = utils.CRI("maxJsonData");
/// <summary>
/// Valore limite MASSIMO di invio di dati come array Json x EVENTI
/// </summary>
protected int maxJsonDataEv { get; set; } = utils.CRI("maxJsonDataEv");
/// <summary>
/// Max tentativi ping permessi (default: 5)
/// </summary>
protected int maxPingRetry { get; set; } = 5;
/// <summary>
/// Coda massima ammessa per FLog (se = 0 disattivata...)
/// </summary>
protected int maxQueueFLog { get; set; } = utils.CRI("maxQueueFLog");
/// <summary>
/// Coda massima ammessa per FLog (se = 0 disattivata...)
/// </summary>
protected int maxQueueRawTransf { get; set; } = utils.CRI("maxQueueRawTransf");
/// <summary>
/// Valore MINIMO limite x decidere invio di dati come array Json
/// </summary>
protected int minJsonData { get; set; } = utils.CRI("minJsonData");
protected string nextKeyRich
{
get
{
return $"{dailyKey}{countKeyRichiesta:00}";
}
}
/// <summary>
/// Numero letture IN da avvio
/// </summary>
protected int nReadFilt { get; set; }
/// <summary>
/// Numero letture IN da avvio
/// </summary>
protected int nReadIN { get; set; }
/// <summary>
/// Numero invii OUT (svuotamento coda)
/// </summary>
protected int nSendOut { get; set; }
/// <summary>
/// Numero simulazioni ammesse...
/// </summary>
protected int numSim { get; set; }
/// <summary>
/// Dizionario condizioni di check BIT da usare x valori controllo (es ModBus TCP Imax)
/// </summary>
protected Dictionary<string, BitConditionCheck> OptCheckCondBit { get; set; } = new Dictionary<string, BitConditionCheck>();
/// <summary>
/// Dizionario condizioni di check INT da usare x valori controllo bitmap (es ModBus TCP Zetapack)
/// </summary>
protected Dictionary<string, IntConditionCheck> OptCheckCondInt { get; set; } = new Dictionary<string, IntConditionCheck>();
/// <summary>
/// Elenco di valori da tradurre da valori BIT (es ModBus TCP FIMAT)
/// NB: potrebbero essere contemporaneamente attivi + valori e + traduzioni
/// </summary>
protected Dictionary<string, string> OptVar2TranslBit { get; set; } = new Dictionary<string, string>();
/// <summary>
/// Elenco di valori da tradurre da valori INT esclusivi (es ModBus TCP FIMAT)
/// </summary>
protected Dictionary<string, string> OptVar2TranslInt { get; set; } = new Dictionary<string, string>();
/// <summary>
/// Elenco dei fullPath utili x processing
/// </summary>
protected Dictionary<string, string> pathList { get; set; } = new Dictionary<string, string>();
/// <summary>
/// Gestione archivio serializzato PODL inviati (tramite file temp locale/REDIS)
/// </summary>
protected Dictionary<int, PODLModel> POdlSentFileArch
{
get
{
Dictionary<int, PODLModel> answ = new Dictionary<int, PODLModel>();
string redKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:POdlSent");
string rawData = redisMan.getRSV(redKey);
if (!string.IsNullOrEmpty(rawData))
{
try
{
answ = JsonConvert.DeserializeObject<Dictionary<int, PODLModel>>(rawData);
lgInfo($"Rilettura status POdlSentFileArch: trovati {answ.Count} record");
}
catch (Exception exc)
{
lgError($"Errore in deserializzazione POdlSentFileArch{Environment.NewLine}{exc}");
}
}
return answ;
}
set
{
string rawVal = JsonConvert.SerializeObject(value);
string redKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:POdlSent");
redisMan.setRSV(redKey, rawVal);
lgDebug($"Salvataggio status POdlSentFileArch | {value.Count} record");
}
}
/// <summary>
/// Elenco corrente (in memory) PODL inviati all'impianto
/// chiave: idxPODL
/// valore: record PODL
/// </summary>
protected Dictionary<int, PODLModel> POdlSentList { get; set; } = new Dictionary<int, PODLModel>();
/// <summary>
/// Indica se sia stato resettato un contapezzi
/// </summary>
protected bool pzCountResetted { get; set; } = false;
/// <summary>
/// Fornisce il valore letto da BITMAP in formato valido x messa in coda nel formato dtEve#valReq#cont
/// </summary>
protected string qEncodeIN
{
get
{
string answ = "";
try
{
answ = string.Format("{0:yyyyMMddHHmmssfff}#{1:X2}#{2}", DateTime.Now, B_output, counterSigIN);
}
catch
{ }
return answ;
}
}
/// <summary>
/// Definizioni x replace in file ricette
/// </summary>
protected Dictionary<string, string> RecipeReplRules { get; set; } = new Dictionary<string, string>();
/// <summary>
/// Chiave ultima condition registrata redis (da aggiungere eventuale ID specifico condition)
/// </summary>
protected string redKeyLastCondition
{
get => redisMan.redHash($"IOB:CurrData:{IOBConfFull.General.FilenameIOB}:Condition");
}
protected string redKeyLogfileAct
{
get => redisMan.redHash($"IOB:CurrData:{IOBConfFull.General.FilenameIOB}:LogFile:Act");
}
protected string redKeyLogfileLast
{
get => redisMan.redHash($"IOB:CurrData:{IOBConfFull.General.FilenameIOB}:LogFile:Last");
}
protected string redKeyProdLastArt
{
get => redisMan.redHash($"IOB:CurrData:{IOBConfFull.General.FilenameIOB}:prodDecod:lastArtDesc");
}
protected string redKeyProdRunState
{
get => redisMan.redHash($"IOB:CurrData:{IOBConfFull.General.FilenameIOB}:prodDecod:isRunningState");
}
/// <summary>
/// Redis key del dizionari valori DataItemMem persistiti
/// </summary>
protected string rKeyFluxMem
{
get => redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:FluxMem");
}
protected Random rndGen { get; set; } = new Random();
/// <summary>
/// Indica se vada inviata la keyReq richiesta con lo split/autoODL
/// </summary>
protected bool sendKeyRichiesta { get; set; } = false;
/// <summary>
/// test ping all'indirizzo PLC/CNC impostato nei parametri
/// </summary>
/// <returns></returns>
protected IPStatus testPingMachine
{
get
{
IPStatus answ = IPStatus.Unknown;
// se disabilitato salto...
if (IOBConfFull.Device.DisabPing)
{
answ = IPStatus.Success;
}
else
{
IPAddress address;
PingReply reply;
using (Ping pingSender = new Ping())
{
address = IPAddress.Loopback;
int pingMsTimeout = IOBConfFull.Device.Connect.PingMsTimeout;
IPAddress.TryParse(IOBConfFull.Device.Connect.PingIpAddr, out address);
try
{
// se != null --> uso address...
if (address != null)
{
reply = pingSender.Send(address, pingMsTimeout);
}
else
{
reply = pingSender.Send(IOBConfFull.Device.Connect.IpAddr, pingMsTimeout);
}
}
catch
{
reply = pingSender.Send(IPAddress.Loopback, pingMsTimeout);
}
answ = reply.Status;
}
}
return answ;
}
}
/// <summary>
/// URL per INVIO IN BLOCCO di un INCREMENTO x contapezzi (quelli della macchina: PLC/CNC)...
/// </summary>
protected string urlAddPzCount
{
get => $@"{urlCommandIob("savePzCountInc")}?qty=";
}
/// <summary>
/// URL per INVIO IN BLOCCO di un INCREMENTO x contapezzi (quelli della macchina: PLC/CNC)
/// in una data SPECIFICA
/// </summary>
protected string urlAddPzCountAtDate
{
get => $@"{urlCommandIob("savePzCountIncAtDate")}?qty=";
}
/// <summary>
/// URL per check alive...
/// </summary>
protected string urlAlive
{
get
{
return $@"{urlCommand("alive")}";
}
}
/// <summary>
/// URL per creazione di un PODL da cod art (metodo GET)...
/// </summary>
protected string urlCreatePOdl
{
get => $@"{urlCommandIob("forceCreatePOdl")}?";
}
/// <summary>
/// URL per chiamata generazione Snapshot Dossier giornalieri alla data
/// </summary>
protected string urlFixDailyDossier
{
get => $@"{urlCommandIob("fixDailyDossier")}";
}
/// <summary>
/// URL per chiamata generazione ODL giornalieri alla data
/// </summary>
protected string urlFixDailyOdl
{
get => $@"{urlCommandIob("fixDailyOdl")}";
}
/// <summary>
/// URL per chiamata generazione ODL giornalieri alla data + conferma PzCount ad ogni step
/// </summary>
protected string urlFixDailyOdlConfPzCount
{
get => $@"{urlCommandIob("fixDailyOdlConfPzCount")}";
}
/// <summary>
/// URL per forzare split ODL...
/// </summary>
protected string urlForceSplit
{
get => $"{urlCommandIob("forceSplitOdlFull")}?doConfirm=true&qtyFromLast=true&roundStep=500";
}
/// <summary>
/// URL per recupero PODL NEXT = ATTIVABILI x macchina...
/// </summary>
protected string urlGetActPODL
{
get => $@"{urlCommandIob("getPOdlAct")}";
}
/// <summary>
/// URL per recupero ARTICOLI correnti x macchina...
/// </summary>
protected string urlGetCurrArt
{
get => $@"{urlCommandIob("getLastArtByMacc")}";
}
/// <summary>
/// URL per recupero DOSS correnti x macchina...
/// </summary>
protected string urlGetCurrDOSS
{
get => $@"{urlCommandIob("getLastDossByMacc")}";
}
/// <summary>
/// URL per recupero IDX ODL corrente...
/// </summary>
protected string urlGetCurrODL
{
get => $@"{urlCommandIob("getCurrODL")}";
}
/// <summary>
/// URL per recupero dati ODL corrente...
/// </summary>
protected string urlGetCurrOdlRow
{
get => $@"{urlCommandIob("getCurrOdlRow")}";
}
/// <summary>
/// URL per recupero PODL ATTIVABILI (quindi correnti in senso di pronti) x macchina...
/// </summary>
[Obsolete("Metodo obsoleto (dubbia interpretazione): sostituire con urlGetNextPODL se si vogliono PROSSIMI POdl attivabili (significato originale) o con urlGetActPODL x il POdl attualmente in produzione (per casi come Rama OPC-UA Siemens)")]
protected string urlGetCurrPODL
{
get => $@"{urlCommandIob("getCurrPODL")}";
}
/// <summary>
/// URL per recupero ListValue tipo fasi...
/// </summary>
protected string urlGetListValFasiPodl
{
get => $@"{urlCommand("getListValByTable")}PODL";
}
/// <summary>
/// URL per recupero PODL NEXT = ATTIVABILI x macchina...
/// </summary>
protected string urlGetNextPODL
{
get => $@"{urlCommandIob("getPOdlNext")}";
}
/// <summary>
/// URL per recupero traduzione CodArt in numerico...
/// </summary>
protected string urlGetNumArt
{
get => $@"{urlCommandIob("getArtNum")}";
}
/// <summary>
/// URL per recupero traduzione CodComm in numerico...
/// </summary>
protected string urlGetNumComm
{
get => $@"{urlCommandIob("getXdlNum")}";
}
/// <summary>
/// URL per recupero num pezzi ODL corrente...
/// </summary>
protected string urlGetNumPzCurrODL
{
get => $@"{urlCommandIob("getCurrOdlQtaReq")}";
}
/// <summary>
/// URL per richiamo parametri da scrivere...
/// </summary>
protected string urlGetParams2Write
{
get => $@"{urlCommandIob("getObjItems2Write")}";
}
/// <summary>
/// URL per recupero contapezzi...
/// </summary>
protected string urlGetPzCount
{
get => $@"{urlCommandIob("getCounter")}";
}
/// <summary>
/// URL per recupero contapezzi REGISTRATI da TC...
/// </summary>
protected string urlGetPzCountRec
{
get => $@"{urlCommandIob("getCounterTCRec")}";
}
/// <summary>
/// URL per richiamo task da eseguire...
/// </summary>
protected string urlGetTask2Exe
{
get => $@"{urlCommandIob("getTask2Exe")}";
}
/// <summary>
/// URL per recupero idle time IOB...
/// </summary>
protected string urlIdleTime
{
get => $@"{urlCommandIob("getIdlePeriod")}";
}
/// <summary>
/// URL per recupero inizio ODL...
/// </summary>
protected string urlInizioOdlIob
{
get => $@"{urlCommandIob("getCurrOdlStart")}";
}
/// <summary>
/// URL per check se abilitato...
/// </summary>
protected string urlIobEnabled
{
get => $@"{urlCommandIob("enabled")}";
}
/// <summary>
/// URL per richiesta chiusura manuale ad utente x ODL corrente (metodo GET)...
/// </summary>
protected string urlODLAskClose
{
get => $@"{urlCommandIob("askCloseODL")}?idxOdl=";
}
/// <summary>
/// URL per chiusura ODL corrente (metodo GET)...
/// </summary>
protected string urlODLClose
{
get => $@"{urlCommandIob("closeODL")}/?idxOdl=";
}
/// <summary>
/// URL per AVVIO di un ODL da PODL indicato (metodo GET)...
/// </summary>
protected string urlOdlStartFromPOdl
{
get => $@"{urlCommandIob("forceStartPOdl")}?idxPODL=";
}
/// <summary>
/// URL per chiusura PODL --&gt; ODL corrente (metodo GET)...
/// </summary>
protected string urlPODLClose
{
get => $@"{urlCommandIob("closePODL")}?idxPOdl=";
}
/// <summary>
/// URL per richiamo task da eseguire...
/// </summary>
protected string urlRemTask2Exe
{
get => $@"{urlCommandIob("remTask2Exe")}?taskName=";
}
/// <summary>
/// URL per salvataggio dati PARAMETRI IOB...
/// </summary>
protected string urlSaveAllParams
{
get => $@"{urlCommandIob("setObjItems")}";
}
/// <summary>
/// URL x salvataggio elenco dataItems OpcUa/MTC
/// </summary>
protected string urlSaveDataItems
{
get => $@"{urlCommandIob("saveDataItems")}";
}
/// <summary>
/// URL per invio MachineIobConf info
/// </summary>
protected string urlSaveMachIobConf
{
get => $@"{urlCommandIobFile("saveMachineIobConf")}";
}
/// <summary>
/// URL per salvataggio dati conf memoria IOB...
/// </summary>
protected string urlSaveMemMap
{
get => $@"{urlCommandIobFile("saveConf")}";
}
/// <summary>
/// URL per INVIO di un update dello status di un certo allarme
/// </summary>
protected string urlSendAlarm
{
get => $@"{urlCommandIob("sendAlarmBankUpdate")}";
}
/// <summary>
/// URL per invio info HASH gestione recipes
/// </summary>
protected string urlSetHashDict
{
get => $@"{urlCommandIob("setRedisHashDict")}";
}
/// <summary>
/// URL per salvataggio dati associazione Machine 2 IOB...
/// </summary>
protected string urlSetM2IOB
{
get => $@"{urlCommandIobFile("setM2IOB")}?IOB_name={Environment.MachineName}";
}
/// <summary>
/// URL per salvataggio VALORI opzionali...
/// </summary>
protected string urlSetOptVal
{
get => $@"{urlCommandIob("addOptPar")}?";
}
/// <summary>
/// URL per salvataggio contapezzi...
/// </summary>
protected string urlSetPzCount
{
get => $@"{urlCommandIob("setCounter")}?counter=";
}
/// <summary>
/// URL per effettuare salvataggio parametri (snapshot) macchina...
/// </summary>
protected string urlTakeSnapshot
{
get => $@"{urlCommandIob("takeFlogSnapshot")}";
}
/// <summary>
/// URL per salvataggio in UPSERT dei PARAMETRI IOB scritti...
/// </summary>
protected string urlUpdateWriteParams
{
get => $@"{urlCommandIob("upsertObjItems")}";
}
/// <summary>
/// Secondi standard x veto check status e log
/// </summary>
protected int vetoSeconds { get; set; } = utils.CRI("vetoSeconds");
#endregion Protected Properties
#region Protected Methods
/// <summary>
/// Decodifica file MAP (caso <paramref name="ByteNum" />.bit)
/// </summary>
/// <param name="linea"></param>
/// <param name="separator"></param>
/// <param name="ByteNum">indirizzo Byte: indirizzo di partenza memoria</param>
/// <param name="memSize">dimensione singolo slot in byte</param>
/// <param name="BitNum">indirizzo bit: numero riga x calcolo indice bit</param>
/// <returns></returns>
protected static otherData decodeBitData(string linea, char separator, int ByteNum, int memSize, int BitNum)
{
if (linea != null)
{
string[] valori = linea.Split(separator);
int shift = 0;
try
{
shift = Convert.ToInt32(valori[0]) - 1;
}
catch
{ }
int resto = 0;
Math.DivRem(BitNum, 8, out resto);
string memAddr = string.Format("{0}.{1}", ByteNum + shift * memSize, resto);
return new otherData(valori[0], memAddr, valori[1].Trim(), valori[2].Trim());
}
else
{
return null;
}
}
/// <summary>
/// Decodifica file MAP generico
/// </summary>
/// <param name="linea"></param>
/// <param name="separator"></param>
/// <param name="memPre"></param>
/// <param name="baseAddr"></param>
/// <param name="memSize"></param>
/// <returns></returns>
protected static otherData decodeOtherData(string linea, char separator, string memPre, int baseAddr, int memSize)
{
if (linea != null)
{
string[] valori = linea.Split(separator);
int shift = 0;
try
{
shift = Convert.ToInt32(valori[0]) - 1;
}
catch
{ }
string memAddr = string.Format("{0}{1}", memPre, baseAddr + shift * memSize);
return new otherData(valori[0], memAddr, valori[1].Trim(), valori[2].Trim());
}
else
{
return null;
}
}
protected static long GetObjectSize(object genObj, bool isSerializable)
{
long result = 0;
if (isSerializable)
{
using (var stream = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(stream, genObj);
result = stream.Length;
}
}
else
{
string rawVal = System.Text.Json.JsonSerializer.Serialize(genObj, options);
result = rawVal.Length;
}
return result;
}
/// <summary>
/// Decodifica valore della coda IN nel formato sendEnab[0]=dtEve sendEnab[1]=valore sendEnab[2]=counter
/// </summary>
/// <param name="queueVal">dtEve + '#' + valReq + '#' + cont</param>
/// <returns></returns>
protected static string[] qDecodeIN(string queueVal)
{
string[] answ = null;
if (!string.IsNullOrEmpty(queueVal))
{
try
{
answ = queueVal.Split('#');
}
catch
{ }
}
return answ;
}
/// <summary>
/// Accodamento richieste server
/// </summary>
/// <param name="codTav"></param>
/// <param name="newReq"></param>
protected void accodaServReq(string codTav, string newReq)
{
JobTaskData jobTask = new JobTaskData(codTav, newReq);
// accodo richiesta serializzata
string serVal = JsonConvert.SerializeObject(jobTask);
QueueSrvReq.Enqueue(serVal);
// loggo!
lgDebug($"[QUEUE] | QueueSrvReq len: {QueueSrvReq.Count}");
}
/// <summary>
/// Accodamento risposte per il server
/// </summary>
/// <param name="codTav"></param>
/// <param name="rawData"></param>
protected void accodaServResp(string codTav, string rawData)
{
JobTaskData jobTask = new JobTaskData(codTav, rawData);
// accodo richiesta serializzata
string serVal = JsonConvert.SerializeObject(jobTask);
QueueSrvResp.Enqueue(serVal);
// loggo!
lgDebug($"[QUEUE] | QueueSrvResp len: {QueueSrvResp.Count}");
}
/// <summary>
/// Classe di base implementazione traduzione di una LUT da memoria come valore BIT a valore
/// esplicito x FLog
/// </summary>
protected virtual void checkTranslateBit()
{
// verifico se devo processare decodifica di qualche valore...
if (OptVar2TranslBit != null && OptVar2TranslBit.Count > 0)
{ }
}
/// <summary>
/// Classe di base implementazione traduzione di una LUT da memoria come valore INT a valore
/// esplicito x FLog
/// </summary>
protected virtual void checkTranslateInt()
{
if (OptVar2TranslInt != null && OptVar2TranslInt.Count > 0)
{ }
}
/// <summary>
/// Verifico i dynData x validità:
/// - siano almeno 1
/// - NON siano tutti identici
/// </summary>
/// <param name="currDynData"></param>
/// <returns></returns>
protected bool checkValidDynData(Dictionary<string, string> currDynData)
{
bool answ = false;
// conto num valori
int numVal = currDynData.Count;
bool dataPresent = numVal > 0;
if (dataPresent)
{
// prendo il primo valore..
string firstVal = currDynData.FirstOrDefault().Value;
// conto val uguali al primo
int numEq = currDynData.Where(x => x.Value == firstVal).Count();
// se solo 1 o almeno 1 è diverso è ok
answ = numVal == 1 || (numVal > numEq);
}
return answ;
}
/// <summary>
/// Conversione string row in log generico
/// </summary>
/// <param name="dailyLog"></param>
/// <returns></returns>
protected virtual GenLogRow convertToMachineLog(string dailyLog)
{
GenLogRow answ = new GenLogRow();
if (!string.IsNullOrEmpty(dailyLog))
{
// preventivamente: doppio ";" --> ";"
dailyLog = dailyLog.Replace(";", ";");
var sSplit = dailyLog.Split(';');
answ.dtRif = DateTime.ParseExact($"{sSplit[0]} {sSplit[1]}", "yyyy-M-d HH:mm:ss", null);
answ.valString = $"{sSplit[2]};{sSplit[3]}";
}
return answ;
}
/// <summary>
/// Restituisce stato allarmi in formato byte[]
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
protected byte[] currAlarmsState(BaseAlarmConf item)
{
byte[] answ = new byte[item.size];
int currStatus = 0;
int[] listInt = new int[2];
// banchi in array Int16 --> scompongo
for (int i = 0; i < item.size / 2; i++)
{
currStatus = getAlarmStatus(item);
// aggiornamento blink counters dato nuovo valore
item.checkBlinkCounter(i, (uint)currStatus);
// calcolo indice errori
if ((uint)(item.alarmsMask[i] & currStatus) > 0)
{
var currByte = BitConverter.GetBytes(currStatus);
// salvo nell'array di byte
Buffer.BlockCopy(currByte, 0, answ, i * 2, 2);
}
}
return answ;
}
/// <summary>
/// Recupera ODL attivo su impianto
/// </summary>
protected int CurrOdl()
{
int cODL = 0;
// vieto controllo prima di 5 sec... da configurare?
if (DateTime.Now.Subtract(vetoCheckOdl).TotalSeconds > IOBConfFull.Odl.VetoCheckOdlSec)
{
string sCurrODL = utils.CallUrlGet(urlGetCurrODL);
int.TryParse(sCurrODL, out cODL);
currIdxODL = cODL;
vetoCheckOdl = DateTime.Now;
}
else
{
cODL = currIdxODL;
}
return cODL;
}
/// <summary>
/// Aggiunge o aggiorna nDict ad un Dict
/// </summary>
/// <param name="currDict"></param>
/// <param name="newKey"></param>
/// <param name="newVal"></param>
protected void DictUpsert(ref Dictionary<string, string> currDict, string newKey, string newVal)
{
if (currDict.ContainsKey(newKey))
{
currDict[newKey] = newVal;
}
else
{
currDict.Add(newKey, newVal);
}
}
/// <summary>
/// Verifica se salvare (nel log) info dei FluxLog filtrati
/// - per scadenza valore VetoFlushFiltFL
/// - per un numero complessivo di eventi superiori a soglia
/// </summary>
/// <param name="minCount">numero minimo di eventi necessari al salvataggio (come somma complessiva)</param>
protected void FiltFluxLogCheckSave(long minCount)
{
DateTime adesso = DateTime.Now;
bool doWrite = VetoFlushFiltFL < adesso;
// conteggio valori
if (!doWrite)
{
long numFL = DictFiltFLog.Sum(x => x.Value);
doWrite = numFL > minCount;
}
if (doWrite)
{
// scrivo log x ogni valore
string sep = "------------------------------------------------------------";
_logger.Info(sep);
_logger.Info("- Paret0 Eventi FluxLog filtrati");
_logger.Info(sep);
foreach (var item in DictFiltFLog.OrderByDescending(x => x.Value))
{
// loggo!
_logger.Info($"{item.Key}: {item.Value}");
}
_logger.Info(sep);
// reset dizionario
DictFiltFLog.Clear();
// nuovo veto a 1h
VetoFlushFiltFL = adesso.AddHours(1);
}
}
/// <summary>
/// Imposta eventuali altri valori default
/// </summary>
protected void fixDefaultPar()
{
// parametro max tentativi PING...
maxPingRetry = IOBConfFull.General.MaxPingRetry;
maxErroriCheck = IOBConfFull.General.MaxErroriCheck;
watchDogPeriod = IOBConfFull.General.WatchDogPeriod;
// sistemo conf folder acquisizione files
if (IOBConfFull.Special.FileConf != null)
{
fileImportFolder = IOBConfFull.Special.FileConf.ImportFolder;
fileImportType = IOBConfFull.Special.FileConf.ImportType;
fileArchiveFolder = IOBConfFull.Special.FileConf.ArchiveFolder;
exclToolDirPath = IOBConfFull.Special.FileConf.ExcelToolDirPath;
}
delayMinReadPzCount = IOBConfFull.General.DelayReadPzCount;
}
/// <summary>
/// Effettua un force kill dato nome processo
/// </summary>
/// <param name="nomeProc"></param>
protected void ForceKillByName(string nomeProc)
{
Process[] stillRunningProc = Process.GetProcessesByName(nomeProc);
if (stillRunningProc != null)
{
if (stillRunningProc.Length > 0)
{
foreach (var item in stillRunningProc)
{
try
{
Process p = Process.GetProcessById(item.Id);
{
if (!p.HasExited)
{
int closeWaitTime = waitForExitMsec * 8;
// se è MAN --> aspetto + a lungo...
if (nomeProc.Contains("IOB-MAN"))
{
closeWaitTime = closeWaitTime * 4;
}
p.CloseMainWindow();
p.WaitForExit(closeWaitTime);
}
if (!p.HasExited)
{
lg.Error($"Process not Exited, 2nd try p.kill()");
p.Kill();
p.WaitForExit(waitForExitMsec);
}
if (!p.HasExited)
{
lg.Error($"Process not Killed, 3nd try p.kill()");
p.Kill();
p.WaitForExit(waitForExitMsec * 2);
}
if (!p.HasExited)
{
lg.Error($"Process not Killed, 4th try p.kill()");
p.Kill();
}
}
}
catch (Exception exc)
{
lg.Error($"Errore in fase di kill processo da nome {exc}");
}
}
}
}
}
/// <summary>
/// Imposta la memoria PLC in modo forzato (es x oggetti gestiti da FTP come setComm)
/// </summary>
protected virtual void forceMemMap()
{ }
/// <summary>
/// Implementazione di riferimento della verifica stato allarmi
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
protected virtual int getAlarmStatus(BaseAlarmConf item)
{
int answ = 0;
return answ;
}
/// <summary>
/// Implementazione di riferimento della verifica stato allarmi formato UInt
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
protected virtual uint getAlarmStatusUInt(BaseAlarmConf item)
{
uint answ = 0;
return answ;
}
/// <summary>
/// Recupera valore da dizionario CurrProdData o restituisce val default
/// </summary>
/// <param name="key">Chiave richiesta</param>
/// <param name="defVal">Valore di default</param>
/// <returns></returns>
protected string getCurrProdData(string key, string defVal)
{
string answ = "";
if (currProdData.ContainsKey(key))
{
answ = string.IsNullOrEmpty(currProdData[key]) ? defVal : currProdData[key];
}
return answ;
}
/// <summary>
/// Fornisce formato valido x messa in coda nel formato dtEve#valReq#cont
/// </summary>
protected string getEncodSigLog(DateTime DtEvent, int val2log, int counter)
{
string answ = "";
try
{
answ = $"{DtEvent:yyyyMMddHHmmssfff}#{val2log:X2}#{counter}";
}
catch
{ }
return answ;
}
/// <summary>
/// Recupero dati da FluxLog
/// </summary>
/// <param name="resultSet"></param>
/// <param name="codFlux"></param>
/// <returns></returns>
protected string getFluxVal(DossierFluxLogDTO resultSet, string codFlux)
{
string answ = "";
var searchRec = resultSet.ODL.Where(x => x.CodFlux == codFlux).FirstOrDefault();
if (searchRec != null)
{
answ = !string.IsNullOrEmpty(searchRec.ValoreEdit) ? searchRec.ValoreEdit : searchRec.Valore;
}
return answ;
}
/// <summary>
/// Recupero dati da FluxLog formato double
/// </summary>
/// <param name="resultSet"></param>
/// <param name="codFlux"></param>
/// <returns></returns>
protected double getFluxValDouble(DossierFluxLogDTO resultSet, string codFlux)
{
double answ = 0;
var style = NumberStyles.AllowDecimalPoint;
var culture = CultureInfo.InvariantCulture;
string rawVal = getFluxVal(resultSet, codFlux);
if (rawVal.Contains(","))
{
rawVal = rawVal.Replace(",", ".");
}
if (!string.IsNullOrEmpty(rawVal))
{
double.TryParse(rawVal, style, culture, out answ);
}
return answ;
}
/// <summary>
/// Recupero dati da FluxLog formato INT
/// </summary>
/// <param name="resultSet"></param>
/// <param name="codFlux"></param>
/// <returns></returns>
protected int getFluxValInt(DossierFluxLogDTO resultSet, string codFlux)
{
int answ = 0;
double dVal = 0;
var style = NumberStyles.AllowDecimalPoint;
var culture = CultureInfo.InvariantCulture;
string rawVal = getFluxVal(resultSet, codFlux);
if (rawVal.Contains(","))
{
rawVal = rawVal.Replace(",", ".");
}
if (!string.IsNullOrEmpty(rawVal))
{
double.TryParse(rawVal, style, culture, out dVal);
answ = (int)dVal;
}
return answ;
}
/// <summary>
/// Converte un file di log della macchina in un dizionario
/// </summary>
/// <param name="dailyLog"></param>
/// <returns></returns>
protected List<GenLogRow> getGenLogFromMachineLog(string dailyLog)
{
List<GenLogRow> result = new List<GenLogRow>();
//se ho valori.. faccio split!
if (!string.IsNullOrEmpty(dailyLog))
{
var rowList = dailyLog.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
if (rowList != null && rowList.Length > 0)
{
result = rowList
.Where(x => !string.IsNullOrEmpty(x))
.Select(x => convertToMachineLog(x))
.ToList();
}
}
return result;
}
/// <summary>
/// Effettua traduzione da tabella listValue per chiave
/// </summary>
/// <param name="factTable"></param>
/// <param name="key"></param>
/// <returns></returns>
protected string getLV(Dictionary<string, string> currDict, string key)
{
string answ = currDict.ContainsKey(key) ? currDict[key] : key;
return answ;
}
/// <summary>
/// Restituisce valore salvato in memMapWrite
/// </summary>
/// <param name="keyName"></param>
/// <returns></returns>
protected string getMemMapWriteVal(string keyName)
{
string answ = "";
if (memMap != null && memMap.mMapWrite.ContainsKey(keyName))
{
answ = memMap.mMapWrite[keyName].value;
}
return answ;
}
/// <summary>
/// Recupera valore numerico ARTICOLO x salvataggio valori INT
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
protected string getNumArt(string value)
{
string answ = "";
// cerco in dizionario corrente...
if (DictNumArt.Count > 0 && DictNumArt.ContainsKey(value))
{
answ = DictNumArt[value];
}
// altrimenti chiamo servizio
else
{
// chiamo MP-IO server
bool srvAlive = CheckServerAlive();
if (srvAlive)
{
string url2call = $"{urlGetNumArt}?CodArt={value}";
if (verboseLog)
{
lgInfo("chiamata URL " + url2call);
}
var rawData = utils.CallUrlGet(url2call);
// deserializzo e recupero KVP...
var dictArtSrv = JsonConvert.DeserializeObject<Dictionary<string, string>>(rawData);
// se fosse una chiamata con valore vuoto --> salvo intera tabella...
if (string.IsNullOrEmpty(value))
{
DictNumArt = dictArtSrv;
}
// altrimenti aggiungo a dizionario
else
{
foreach (var item in dictArtSrv)
{
if (!DictNumArt.ContainsKey(item.Key))
{
DictNumArt.Add(item.Key, item.Value);
}
}
}
// ora cerco valore e restituisco
if (DictNumArt.ContainsKey(value))
{
answ = DictNumArt[value];
}
}
}
return answ;
}
/// <summary>
/// Recupera valore numerico COMMESSA x salvataggio valori INT
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
protected string getNumComm(string value)
{
string answ = "";
// chiamo MP-IO server
bool srvAlive = CheckServerAlive();
if (srvAlive)
{
string url2call = $"{urlGetNumComm}?CodXdl={value}";
if (verboseLog)
{
lgInfo("chiamata URL " + url2call);
}
// è direttamente il risultato!
answ = utils.CallUrlGet(url2call);
}
// restituisco
return answ;
}
/// <summary>
/// Stringa raw dei parametri da scrivere...
/// </summary>
/// <returns></returns>
protected string getParams2write()
{
string answ = "";
string url2call = $"{urlGetParams2Write}";
if (verboseLog)
{
lgInfo("chiamata URL " + url2call);
}
answ = utils.CallUrlGet(url2call);
// se vuoto faccio seconda prova...
if (string.IsNullOrEmpty(answ) || answ == "[]")
{
answ = utils.CallUrlGet(url2call);
}
return answ;
}
/// <summary>
/// Recupera file log da analizzare
/// </summary>
/// <returns></returns>
protected virtual bool getRemoteLog()
{
bool fatto = false;
return fatto;
}
/// <summary>
/// Chiede elenco dei task da eseguire
/// - formato Json
/// - array di KVP / Dictionary
/// - formato definito da API x MP/IO/:
/// - KEY: task
/// - VALUE: array JSon KVP
/// </summary>
/// <param name="codTav">Cod DP/Tavola (opzionale)</param>
/// <returns></returns>
protected async Task<string> getTask2exe(string codTav)
{
string answ = "";
bool srvAlive = await CheckServerAliveAsync();
if (srvAlive)
{
string url2call = $"{urlGetTask2Exe}";
// se ho tavola aggiungo richiesta...
if (!string.IsNullOrEmpty(codTav))
{
url2call += $"|{codTav}";
}
if (verboseLog)
{
lgInfo("chiamata URL " + url2call);
}
answ = await utils.callUrlAsync(url2call);
}
return answ;
}
/// <summary>
/// Verifica presenza eventuali allarmi
/// </summary>
/// <returns></returns>
protected virtual bool hasAlarms()
{
bool answ = false;
// 2023.12.20: aggiunta gestione secondo tipo allarmi come elenco OPC-UA (BLM/Adige)
if (alarmType == AlarmBlockType.Bitmap)
{
int numRaised = 0;
int currStatus = 0;
ushort full16 = 65535;
int[] listInt = new int[2];
if (alarmMaps != null)
{
// leggo a ciclo le aree degli allarmi CONFIGURATI, se ne trovo --> segnalo allarme...
foreach (var item in alarmMaps)
{
// banchi in array Int16 --> scompongo
for (int i = 0; i < item.size / 2; i++)
{
currStatus = getAlarmStatus(item);
// ora filtro x l'i-esimo banco a 16 bit...
if (i == 0)
{
currStatus = (ushort)(currStatus & full16);
}
else
{
var temp = currStatus >> 16;
currStatus = (ushort)temp;
}
// aggiornamento blink counters dato nuovo valore
item.checkBlinkCounter(i, (uint)currStatus);
// calcolo indice errori
if ((uint)(item.alarmsMask[i] & currStatus) > 0)
{
numRaised++;
}
// verifico SE sia variato... confronto allarmi filtrato stile blink per
// bit status:
// - allarmi che iniziano per # IGNORATI
// - altri allarmi con un countdown da MAX_COUNTER_BLINK a 0 per il fronte
// di discesa
if (item.isChanged(i, (uint)currStatus))
{
lgInfo($"Alarm state change | {item.memAddr} | i: {i} | prev: {item.alarmsState[i]} | curr: {currStatus}");
// registro gli allarmi attivi e trasmetto...
if (sendAlarmVariations(item.memAddr, i, item.alarmsState[i], (uint)(item.alarmsMask[i] & currStatus), item.messages))
{
// se inviato --> salvo stato da current...
item.updStatusVal(i, (uint)(item.alarmsMask[i] & currStatus));
// salvo in redis...
string alarmHash = redisMan.redHash($"IOB:ALARM_STATUS:{IOBConfFull.General.FilenameIOB}:{item.memAddr}");
string rawAlarms = JsonConvert.SerializeObject(item.alarmsState);
redisMan.setRSV(alarmHash, rawAlarms);
}
else
{
lgError($"Errore in sendAlarmVariations | {item.memAddr} | i: {i} | prev: {item.alarmsState[i]} | curr: {currStatus}");
}
}
else
{
if (currStatus > 0)
{
lgTrace($"Alarm UNCHANGED but Active | {item.memAddr} | i: {i} | prev: {item.alarmsState[i]} | curr: {currStatus}");
}
}
}
}
}
answ = numRaised > 0;
}
// gestione allarmi come elenco
else if (alarmType == AlarmBlockType.ActiveList)
{
// verifico SE ho allarmi
if (alarmMaps != null && alarmMaps.Count > 0)
{
// ciclo x ogni blocco
foreach (var item in alarmMaps)
{
// cerco se sia superiore al livello minimo da conf
if (item.blockLevel >= alarmLevelMin)
{
// controllo variabile counter SE > 0...
int numCount = getAlarmStatus(item);
answ = numCount > 0;
}
}
}
}
return answ;
}
/// <summary>
/// Recupera da server set di dati specifici x IOB
/// </summary>
/// <returns></returns>
protected virtual bool IobGetDataFromServer()
{
return false;
}
/// <summary>
/// Effettua upload verso server FTP della macchina dei files nella folder indicata
/// </summary>
/// <param name="folderDir"></param>
/// <returns></returns>
protected virtual bool iobSendFTP(string folderDir)
{
bool answ = false;
if (IOBConfFull.Special.FtpConf != null)
{
// recupero CONF
var ftpConf = IOBConfFull.Special.FtpConf;
// procedo SE TROVO conf...
if (string.IsNullOrEmpty($"{ftpConf.Server}{ftpConf.User}{ftpConf.Passwd}"))
{
lgError($"Impossibile eseguire iobSendFTP x mancanza parametri | ftpServ: {ftpConf.Server} | ftpUser: {ftpConf.User} | ftpPass: {ftpConf.Passwd}");
}
else
{
var ftpClient = new Manager(ftpConf.Server, ftpConf.User, ftpConf.Passwd, ftpConf.CertValue, ftpConf.SkipCert);
var testServer = ftpClient.ServerOk();
if (testServer)
{
lgInfo($"FTP: server found at {ftpConf.Server}");
var srvType = ftpClient.ServerType();
lgInfo($"FTP Server type: {srvType}");
var preTest = ftpClient.DirExists(ftpConf.DirRemote);
if (!preTest)
{
var dirCreate = ftpClient.CreateDir(ftpConf.DirRemote);
lgInfo($"FTP: created remote dir {ftpConf.DirRemote}");
}
// test directory...
string basePath = Application.StartupPath;
string localPath = Path.Combine(basePath, ftpConf.DirLocal);
lgInfo($"basePath: {basePath} | localPath: {localPath}");
var fileList = Directory.GetFiles(localPath, "*.csv");
int numfile = 0;
foreach (var file in fileList)
{
string fName = file.Split('\\').Last();
string remName = $"{ftpConf.DirRemote}\\{fName}";
ftpClient.SendFile(file, remName);
numfile++;
}
var dirUploaded = (numfile == fileList.Count());
if (dirUploaded)
{
lgInfo($"FTP: uploaded dir content {ftpConf.DirLocal} --> {ftpConf.DirRemote}");
}
// se ok --> sposto invio
DateTime adesso = DateTime.Now;
string archBasePath = Path.Combine(basePath, "DATA", "HIST", $"{adesso:yyyy}");
baseUtils.checkDir(archBasePath);
string archPath = Path.Combine(archBasePath, $"{adesso:MMddHHmmss}");
baseUtils.checkDir(archPath);
lgInfo($"Richiesta Dir Move | {localPath} | {archPath}");
foreach (var item in fileList)
{
string fName = item.Split('\\').Last();
string destName = $"{archPath}\\{fName}";
File.Move(item, destName);
}
//Directory.Move(localPath, archBasePath);
lgInfo($"FTP: Archived dir content {localPath} --> {archPath}");
}
}
}
return answ;
}
/// <summary>
/// Prepara files CSV da inviare alla macchina
/// </summary>
protected virtual bool iobWriteLocalCSV()
{
bool answ = false;
// conf ftp
if (IOBConfFull.Special.FtpConf != null)
{
var ftpConf = IOBConfFull.Special.FtpConf;
// salvo articoli
string locDir = ftpConf.DirLocal;
bool addHeader = ftpConf.CsvAddHeader;
string basePath = Application.StartupPath;
string tempDir = Path.Combine(basePath, locDir);
lgInfo($"iobWriteLocalCSV | locDir: {locDir} | addHeader: {addHeader} | tempDir: {tempDir}");
baseUtils.checkDir(tempDir);
string filePath = Path.Combine(tempDir, "articoli.csv");
answ = DataExport.SaveToCsv(ListaArticoli, filePath, addHeader);
if (answ)
{
lgInfo($"CSV: saved ART file as articoli.csv at {filePath}");
// salvo PODL
string csvName = $"{DateTime.Now:dd-MM-yyyy}.csv";
filePath = Path.Combine(tempDir, $"{csvName}");
answ = DataExport.SaveToCsv(ListaJobs, filePath, addHeader);
if (answ)
{
lgInfo($"CSV: saved PODL file {csvName} at {filePath}");
}
}
}
return answ;
}
/// <summary>
/// Prepara files USTD da inviare alle macchine in formato USTD (Emmegi - taglierine)
/// </summary>
protected virtual bool iobWriteLocalUSTD()
{
bool answ = false;
#if false
// conf ftp
var ftpConf = IOBConfFull.Special.FtpConf;
// salvo articoli
string locDir = ftpConf.DirLocal;
bool addHeader = ftpConf.CsvAddHeader;
string basePath = Application.StartupPath;
string tempDir = Path.Combine(basePath, locDir);
lgInfo($"iobWriteLocalCSV | locDir: {locDir} | addHeader: {addHeader} | tempDir: {tempDir}");
baseUtils.checkDir(tempDir);
string filePath = Path.Combine(tempDir, "articoli.csv");
answ = DataExport.SaveToCsv(ListaArticoli, filePath, addHeader);
if (answ)
{
lgInfo($"CSV: saved ART file as articoli.csv at {filePath}");
// salvo PODL
string csvName = $"{DateTime.Now:dd-MM-yyyy}.csv";
filePath = Path.Combine(tempDir, $"{csvName}");
answ = DataExport.SaveToCsv(ListaJobs, filePath, addHeader);
if (answ)
{
lgInfo($"CSV: saved PODL file {csvName} at {filePath}");
}
}
#endif
return answ;
}
/// <summary>
/// Effettua traduzione ITEM da LUT parametrica (keyReq: tipo+id) del file di conf, se non
/// trovo uso keyReq
/// </summary>
/// <param name="tipo"></param>
/// <param name="id"></param>
/// <returns></returns>
protected virtual string itemTranslation(string tipo, string id)
{
string answ = "";
if (IOBConfFull.ItemTranslation != null)
{
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 (IOBConfFull.ItemTranslation.ContainsKey(lemma))
{
answ = IOBConfFull.ItemTranslation[lemma];
}
else
{
answ = lemma;
}
}
return answ;
}
/// <summary>
/// Effettua traduzione ITEM da LUT parametrica (keyReq: lemma) del file di conf, se non
/// trovo uso keyReq
/// </summary>
/// <param name="lemma"></param>
/// <returns></returns>
protected virtual string itemTranslation(string lemma)
{
string answ = "";
if (IOBConfFull.ItemTranslation != null)
{
if (IOBConfFull.ItemTranslation.ContainsKey(lemma))
{
answ = IOBConfFull.ItemTranslation[lemma];
}
else
{
answ = lemma;
}
}
return answ;
}
/// <summary>
/// Legge il file di conf di una MAP di informazioni da gestire con lettura set memoria
/// </summary>
/// <param name="vettoreConf">nome vettore memoria</param>
/// <param name="nomeFile">file origine</param>
/// <param name="memSize">dimensione (in byte) della memoria</param>
/// <param name="numVett">dimensione (in byte) della memoria</param>
protected void loadConfFile(ref otherData[] vettoreConf, string nomeFile, int memSize, ref int numVett)
{
otherData lastData = new otherData();
int totRighe = 0;
string linea;
totRighe = File.ReadLines(nomeFile).Count();
// creo un vettore della dimensione corretta... conta anche commenti tanto poi riduco...
vettoreConf = new otherData[File.ReadLines(nomeFile).Count()];
// carica da file...
StreamReader file = new StreamReader(nomeFile);
// leggo 1 linea alla volta...
int numRiga = 0;
int bitNum = 0;
int byteNum = 0;
while ((linea = file.ReadLine()) != null)
{
// SE non è un commento...
if (linea.Substring(0, 1) != "#")
{
// se finisce per BIT allora processo bit-a-bit...
if (linea.EndsWith("BOOL"))
{
try
{
string[] memIdx = linea.Split(utils.CRC("testCharSep"))[0].Split('.');
// calcolo bit e byte number...
int.TryParse(memIdx[0], out byteNum);
if (memIdx.Length > 1)
{
int.TryParse(memIdx[1], out bitNum);
}
else
{
bitNum = 0;
}
}
catch
{
byteNum = 0;
bitNum = 0;
}
lastData = decodeBitData(linea, utils.CRC("testCharSep"), byteNum, 1, bitNum);
vettoreConf[numRiga] = lastData;
}
else
{
lastData = decodeOtherData(linea, utils.CRC("testCharSep"), "", 1, memSize);
vettoreConf[numRiga] = lastData;
}
numRiga++;
}
}
// salvo lunghezza file...
try
{
numVett = Convert.ToInt32(lastData.memAddr) + 1;
}
catch
{
numVett = numRiga + 1;
}
// chiudo file
file.Close();
// ora trimmo vettore al solo numero VERO dei valori caricati...
Array.Resize<otherData>(ref vettoreConf, numRiga);
lgInfo(string.Format("Fine caricamento vettore di {0} variabili per file {1}", numRiga, nomeFile));
}
/// <summary>
/// Lettura memorie conf speciali (json) ...SE inserito in [OPTPAR] come PARAM_CONF=nome.json
/// </summary>
protected virtual void loadMemConf()
{
if (DateTime.Now <= vetoReloadConf)
{
lgTrace($"Veto on loadMemConf active until {vetoReloadConf}");
}
else
{
lgInfo("loadMemConf.01");
lgInfoStartup("BEGIN loadMemConf");
// variabili x gestione send contapezzi in blocco
enableSendPzCountBlock = IOBConfFull.Counters.EnabSendPzcBlock;
minSendPzCountBlock = IOBConfFull.Counters.SendPzcBlockMin;
maxSendPzCountBlock = IOBConfFull.Counters.SendPzcBlockMax;
lgInfo("loadMemConf.02");
minRespTimeMs = IOBConfFull.Device.MinRespTimeMs;
// gruppo macchina
CodGruppoIob = IOBConfFull.General.CodGruppoIob;
// gestione completa PODL
EnabelPodlManFull = IOBConfFull.General.EnabelPodlManFull;
// abilitazione invio WDST disabilitazione controllo range valori DynData
disableWdst = IOBConfFull.General.DisabWDST;
// disabilitazione controllo range valori DynData
disDynDataRangeCheck = IOBConfFull.FluxLog.DisDynDataRangeCheck;
lgInfo("loadMemConf.03");
// se ho info in IOBConfFull uso quello...
if (IOBConfFull.Memory != null)
{
lgInfo("loadMemConf.05a");
memMap = IOBConfFull.Memory;
}
else
{
lgInfo("loadMemConf.05b");
// inizializzo LUT decodifica PARAMETRI
string jsonParams = getOptPar("PARAM_CONF");
if (!string.IsNullOrEmpty(jsonParams))
{
string jsonFileName = $"{Application.StartupPath}\\DATA\\CONF\\{jsonParams}";
lgInfoStartup($"Apertura file {jsonFileName}");
StreamReader reader = new StreamReader(jsonFileName);
string jsonData = reader.ReadToEnd();
if (!string.IsNullOrEmpty(jsonData))
{
lgInfoStartup($"File json PARAMETRI composto da {jsonData.Length} caratteri");
lgInfo("loadMemConf.04");
try
{
memMap = JsonConvert.DeserializeObject<plcMemMapExt>(jsonData);
}
catch (Exception exc)
{
lgError($"Eccezione in decodifica conf json{Environment.NewLine}{exc}");
}
}
else
{
lgError("Errore in loadMemConf: file json vuoto!");
}
reader.Dispose();
}
else
{
lgInfoStartup("loadMemConf: non trovata opzione PARAM_CONF in file INI");
}
}
// continuo setup...
setupMemMap();
setupOptMemPar();
setupFileDecod();
setupSpecialParams();
lgInfo("loadMemConf.06");
// inizializzo LUT decodifica ALLARMI
if (IOBConfFull.Alarms != null)
{
alarmMaps = IOBConfFull.Alarms.AlarmMaps;
setupAlarmMap();
// leggo il tipo gestione allarmi..
string sAlarmType = getOptJsonKVP("alarmType");
if (!string.IsNullOrEmpty(sAlarmType))
{
alarmType = (AlarmBlockType)Enum.Parse(typeof(AlarmBlockType), sAlarmType);
}
string sAlarmLMin = getOptJsonKVP("alarmLevelMin");
if (!string.IsNullOrEmpty(sAlarmLMin))
{
alarmLevelMin = (AlarmLevel)Enum.Parse(typeof(AlarmLevel), sAlarmLMin);
}
lgInfo("loadMemConf | Fine setup allarmi");
}
vetoReloadConf = DateTime.Now.AddMinutes(15);
// loggo
lgInfo($"loadMemConf.07, veto reload until {vetoReloadConf}");
lgInfoStartup($"DONE loadMemConf, veto reload until {vetoReloadConf}");
}
}
/// <summary>
/// Recupera elenco PODL assegnabili
/// </summary>
/// <returns></returns>
protected List<PODLModel> MachineNextPodl()
{
List<PODLModel> reqPOdlList = new List<PODLModel>();
// per prima cosa recupero elenco PODL da gestire....
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
var rawListPODL = await callUrl(urlGetNextPODL, false);
if (!string.IsNullOrEmpty(rawListPODL))
{
reqPOdlList = JsonConvert.DeserializeObject<List<PODLModel>>(rawListPODL) ?? new List<PODLModel>();
}
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lg.Error($"Errore: chiamata MachineNextPodl: {ex.Message}");
}
return reqPOdlList;
}
/// <summary>
/// Indica il periodo di sampling della memoria (se presente, altrimenti 0)
/// </summary>
/// <param name="memName"></param>
/// <returns></returns>
protected int memPeriod(string memName)
{
int period = 0;
if (memMap != null)
{
if (memMap.mMapRead != null)
{
//cerco in primis modalità NUM altrimenti standard...
if (memMap.mMapRead.ContainsKey(memName))
{
period = memMap.mMapRead[memName].period;
}
}
}
return period;
}
/// <summary>
/// Metodo da overridare x scrivere DAVVERO i parametri sul PLC
/// NB: deve resettare reqValue
/// </summary>
/// <param name="updatedPar"></param>
protected virtual void plcWriteParams(ref List<objItem> updatedPar)
{
// non faccio nulla di base...
}
/// <summary>
/// Esegue eventuali step a valle dell'auto ODL (in caso di esito positivo autoODL)
/// Effettuare override x casi specifici (es SiemensRama x reset contapezzi)
/// </summary>
protected virtual void processAutoOdlExtraStep()
{
// non fa nulla di default...
}
/// <summary>
/// Effettua sync dati da e verso impianto
/// </summary>
protected virtual void ProcessDataSync()
{
}
/// <summary>
/// Ponte esecuzione processi asunc
/// </summary>
/// <returns></returns>
protected virtual bool ProcessFileImport()
{
bool fatto = false;
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
fatto = await ProcessFileImportAsync();
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in ProcessFileImportAsync{Environment.NewLine}{ex.Message}");
}
return fatto;
}
/// <summary>
/// Effettua eventuale file import, archiviando file importati
/// - es gestione file excel di Giacovelli
/// - es gestione ritorno ricette FIMAT
/// </summary>
/// <returns></returns>
protected virtual async Task<bool> ProcessFileImportAsync()
{
bool answ = false;
/* ------------------------------------------
* esecuzione cablata, si potrebbe costruire una cosa più flessibile tramite conf:
* Oggetto MultiStepFileImport
* {
* StepName: Step01
* ProcType: Excel2Json
* FolderIn: xxx
* FolderOut: xxx
* FolderErr: xxx
* },{
* StepName: Step02
* ProcType: Json2RegGiac
* FolderIn: xxx
* FolderOut: xxx
* FolderErr: xxx
* }
* ------------------------------------------*/
if (!string.IsNullOrEmpty(fileImportFolder))
{
// verifico esistenza folders...
if (Directory.Exists(fileImportFolder))
{
string errore = "";
// verifico archivio
string dirArchive = Path.Combine(fileImportFolder, "archive");
baseUtils.checkDir(dirArchive);
string dirConvert = Path.Combine(fileImportFolder, "converted");
baseUtils.checkDir(dirConvert);
Dictionary<string, TimeSpan> statsColl = new Dictionary<string, TimeSpan>();
// cerco files da convertire
var listFiles2Conv = Directory.GetFiles(fileImportFolder, fileImportType);
// se li trovo --> chiamo import!
if (listFiles2Conv != null && listFiles2Conv.Length > 0)
{
// leggo file conf di riferimento
FileProcMan fpm = new FileProcMan(dirArchive, dirConvert, exclToolDirPath, "ExcImport.exe", "RefExcConv.json", "DB Loco");
int idxODL = 0;
foreach (var fileItem in listFiles2Conv)
{
// calcolo ODL x il file...
string fileName = Path.GetFileNameWithoutExtension(fileItem);
DateTime dataDoc = DateTime.Today;
DateTime.TryParse(fileName, out dataDoc);
// cerco lotto x giornata...
string sIdxODL = utils.callUrl(urlGetOdlAtDate(dataDoc));
int.TryParse(sIdxODL, out idxODL);
// chiamo conversione
TimeSpan timeElaps = fpm.doProcess(fileItem, idxODL);
statsColl.Add($"ExcImport conversion executed for {fileItem}", timeElaps);
lgTrace(($"ExcImport conversion executed for {fileItem} | {timeElaps.Milliseconds}ms"));
}
}
// cerco i file json x invio
var listFiles2Upload = Directory.GetFiles(dirConvert, "*.json");
// se li trovo --> chiamo import!
if (listFiles2Upload != null && listFiles2Upload.Length > 0)
{
foreach (var fileItem in listFiles2Upload)
{
// leggo il file json
Dictionary<int, BatchRec> list2Send = new Dictionary<int, BatchRec>();
string rawData = File.ReadAllText(fileItem);
if (!string.IsNullOrEmpty(rawData))
{
var convData = JsonConvert.DeserializeObject<Dictionary<int, BatchRec>>(rawData);
if (convData != null)
{
list2Send = convData;
// ora accodo contenuto file json...
accodaRawData(rawTransfType.RegGiacenze, list2Send);
}
}
// processo invio: provo forzare send...
answ = await SvuotaCodaRawTransfAsync(true);
if (!answ)
{
errore = $"Errore in fase di invio dati SvuotaCodaRawTransfAsync x {fileItem}";
}
// archivio il file
else
{
// sposto file...
File.Move(fileItem, $"{dirArchive}/{Path.GetFileName(fileItem)}");
// fixme todo !!! eliminare json?!?!?
}
// log eventuali errori
if (!answ)
{
string dirError = $"{fileImportFolder}/errors";
baseUtils.checkDir(dirError);
// sposto file...
File.Move(fileItem, $"{dirError}/{Path.GetFileName(fileItem)}");
// segno errore!
string fileError = $"{fileImportFolder}/errors.log";
File.AppendAllText(fileError, errore);
}
}
}
}
}
return answ;
}
/// <summary>
/// Processa le richieste di scrittura memoria
/// </summary>
/// <returns></returns>
protected string processMemWriteRequests()
{
string answ = "";
// li salvo nei parametri in memoria locale (ogni adapter DOVREBBE salvare POI sul VERO PLC)
List<objItem> writeList = new List<objItem>();
List<objItem> updatedPar = new List<objItem>();
string currOut = "";
// recupero elenco delle cose da fare
string resp = getParams2write();
if (!string.IsNullOrEmpty(resp))
{
try
{
writeList = JsonConvert.DeserializeObject<List<objItem>>(resp);
// se ho da fare chiamo esecuzione..
if (writeList.Count > 0)
{
foreach (var item in writeList)
{
// scrivo in memoria
if (memMap.mMapWrite.ContainsKey(item.uid))
{
memMap.mMapWrite[item.uid].value = item.reqValue;
currOut = $" | Parameter {item.uid} | {item.value} --> {item.reqValue}";
// sistemo valori
item.value = item.reqValue;
// forzo variabile writeable
item.writable = true;
lgInfo($"Richiesta update parametro {currOut}");
// salvo in lista da ritrasmettere
updatedPar.Add(item);
}
else
{
currOut = $" | Error: parameter {item.uid} not found";
lgError($"Errore {currOut}");
}
// accodo in stringa taskVal...
answ += currOut;
}
// richiamo scrittura parametri su PLC
plcWriteParams(ref updatedPar);
// invio su cloud parametri!
string rawData = JsonConvert.SerializeObject(updatedPar);
utils.callUrl($"{urlUpdateWriteParams}", rawData);
lgInfo($"Notifica a server scrittura {updatedPar.Count} parametri");
}
}
catch (Exception exc)
{
lgError($"Eccezione in processMemWriteRequests:{Environment.NewLine}{exc}");
}
}
else
{
lgError("Non è stata ricevuta risposta x task da eseguire");
}
return answ;
}
/// <summary>
/// Wrapper sync process other info
/// </summary>
/// <param name="keyReq"></param>
/// <param name="valReq"></param>
/// <returns></returns>
protected virtual bool ProcessOtherInfo(string keyReq, string valReq)
{
bool fatto = false;
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
fatto = await ProcessOtherInfoAsync(keyReq, valReq);
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in ProcessOtherInfo | keyReq: {keyReq} | valReq: {valReq}{Environment.NewLine}{ex.Message}");
}
return fatto;
}
/// <summary>
/// Cerca di recuperare i file generati dall'impianto in merito al processing degli ordini
/// </summary>
/// <returns></returns>
/// <summary>
/// Processing di dati "OtherInfo" da implementare caso x caso (qui riportato caso FIMAT ricette...)
/// </summary>
/// <param name="keyReq"></param>
/// <param name="valReq"></param>
protected virtual async Task<bool> ProcessOtherInfoAsync(string keyReq, string valReq)
{
bool answ = false;
// test file import da conf... se hasRecipe=true --> modalità Tenditalia/FIMAT...
if (hasRecipe)
{
string archBasePath = Path.Combine(pathList["fullPath-locBase"], pathList["fullPath-03-Recv"]);
string reportBasePath = pathList["fullPath-outReport"];
baseUtils.checkDir(reportBasePath);
// verifico x cosa è stato richiesto attività (che esista la folder...)
string folderPath = Path.Combine(archBasePath, keyReq);
if (Directory.Exists(folderPath))
{
// calcolo il nome zip di destinazione finale
int anno = DateTime.Today.Year;
int.TryParse(keyReq.Substring(0, 4), out anno);
string zipDir = Path.Combine(archBasePath, $"{anno}");
baseUtils.checkDir(zipDir);
string zipName = Path.Combine(zipDir, $"{keyReq}.zip");
// gestione report OUT
string reportPath = Path.Combine(reportBasePath, $"{keyReq}");
// verifico il tipo di attività e procedo
switch (valReq)
{
case "Arch":
// solo archiviazione ZIP della folder...
answ = doZipArchiveFolder(folderPath, zipName);
// se sent --> elimino record da REDIS (locale e remoto)
if (answ)
{
await RecipeRemoveWeekStatus(keyReq);
}
break;
case "SendArch":
// preparazione file di resoconto contumi
answ = RecipeDoConsumeReport(folderPath, reportPath);
if (answ)
{
// infine archiviazione ZIP della folder...
bool okArchive = doZipArchiveFolder(folderPath, zipName);
// se sent --> elimino record da REDIS (locale e remoto)
if (okArchive)
{
await RecipeRemoveWeekStatus(keyReq);
}
}
break;
default:
break;
}
}
}
return answ;
}
protected virtual async Task<bool> ProcessRecipeFileRetAsync()
{
bool answ = false;
// test file import da conf... se hasRecipe=true --> modalità Tenditalia/FIMAT...
if (hasRecipe)
{
if (pathList.Count == 0)
{
lg.Debug("Attenzione: pathList vuoto!!!");
}
else
{
// recupera i NUOVI file e li sposta in folder locale temp
string remoPath = Path.Combine(pathList["fullPath-locBase"], pathList["fullPath-05-remExe"]);
string archBasePath = Path.Combine(pathList["fullPath-locBase"], pathList["fullPath-03-Recv"]);
string tempPath = Path.Combine(archBasePath, "TEMP");
baseUtils.checkDir(tempPath);
bool okRetrieve = RecipeTaskDoneRetrieve(remoPath, tempPath);
// ora cerca nella folder locale e processa i files...
bool okCheck = await RecipeDoCheckFileProc(tempPath, archBasePath);
// verifica se sia da creare/inviare il record settimanale di consumo (dopo la
// mezzanotte del lunedì inizio settimana)
bool okSent = RecipeDoProcCons();
}
}
return answ;
}
/// <summary>
/// Sync archivio ricette (Macchina -- MES)
/// </summary>
/// <returns></returns>
protected virtual bool processRecipeSyncArch()
{
bool answ = false;
// test file import da conf... se hasRecipe=true --> modalità Tenditalia/FIMAT...
if (hasRecipe)
{
DateTime adesso = DateTime.Now;
// verifico veto sync ricette (x non ripetere troppo spesso)
if (adesso > dtVetoCheckSyncRecipe)
{
// effettua sync eventuali NUOVI file ricette
bool okSync = RecipeDoSyncRecipe();
// imposto veto a 1h...
dtVetoCheckSyncRecipe = adesso.AddHours(1);
}
}
return answ;
}
/// <summary>
/// Effettua task òettura a bassa velocità
/// </summary>
/// <returns></returns>
protected virtual bool processSlowDataRead()
{
bool answ = false;
return answ;
}
protected void raiseRefresh(newDisplayData currDispData)
{
if (currDispData != null)
{
if (currDispData.hasData)
{
// segnalo refresh!
if (eh_refreshed != null)
{
eh_refreshed(this, new iobRefreshedEventArgs(currDispData));
}
}
}
}
/// <summary>
/// Registra i file in directory suddivise x anno/settimana e restitusice elenco
/// </summary>
/// <param name="recipeFile">Percorso file XML</param>
/// <param name="archBasePath">Percorso base archivi</param>
/// <returns></returns>
protected List<string> RecipeDoArchiveWeek(string recipeFile, string archBasePath)
{
List<string> weeksProc = new List<string>();
// init var
int week = 0;
string archPath = "";
Calendar cal = new CultureInfo("it-IT").Calendar;
// leggo contenuto XML
string rawData = File.ReadAllText(recipeFile);
// deserializza file XML x recuperare righe consumo
var currRecipe = XmlDataSerializer.Deserialize<ARecipe>(rawData);
if (currRecipe != null)
{
// recupero data-ora ricetta da campo string
DateTime recExeDt = DateTime.Today;
DateTime.TryParse(currRecipe.DesRecipe.DesData.Date, out recExeDt);
// calcolo week da data-ora file...
week = cal.GetWeekOfYear(recExeDt, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
// verifico se ci sia la settimana indicata in elenco...
string currWeek = $"{recExeDt:yyyy}-{week:00}";
if (!weeksProc.Contains(currWeek))
{
weeksProc.Add(currWeek);
}
// sposto file x in area week
string rName = Path.GetFileName(recipeFile);
archPath = Path.Combine(archBasePath, $"{recExeDt:yyyy}-{week:00}");
baseUtils.checkDir(archPath);
string destPath = Path.Combine(archPath, rName);
// sposto files
File.Move(recipeFile, destPath);
}
return weeksProc;
}
/// <summary>
/// Verifica i file recuperati
/// - PODL da inviare a MAPO
/// - accumulo consumo materiali
/// </summary>
/// <param name="localPath"></param>
/// <param name="archBasePath"></param>
/// <returns></returns>
protected virtual async Task<bool> RecipeDoCheckFileProc(string localPath, string archBasePath)
{
bool answ = false;
List<string> weekProcNew = new List<string>();
// recupero elenco files...
var fileList = Directory.GetFiles(localPath);
if (fileList != null && fileList.Count() > 0)
{
foreach (var recipeFile in fileList)
{
// processo eventuale apertura/chiusura PODL
bool okPOdl = RecipeDoProcessPODL(recipeFile);
// processa i file archiviando x settimana
var procWeeks = RecipeDoArchiveWeek(recipeFile, archBasePath);
// aggiungo all'elenco settimane SE mancassero le sett inserite
foreach (var item in procWeeks)
{
if (!weekProcNew.Contains(item))
{
weekProcNew.Add(item);
}
}
}
if (weekProcNew.Count > 0)
{
// predispongo azioni di base ammesse
Dictionary<string, string> redHashWeek = new Dictionary<string, string>();
Dictionary<string, string> stdActList = new Dictionary<string, string>();
stdActList.Add("Arch", "Archivia");
stdActList.Add("SendArch", "Invia + Archivia");
List<PeriodInfo> weekHashUpdate = new List<PeriodInfo>();
// aggiorna in REDIS (hashList) status delle settimane coinvolte
foreach (var cWeek in weekProcNew)
{
string weekPath = Path.Combine(archBasePath, $"{cWeek}");
var weekFileList = Directory.GetFiles(weekPath, "*.xml");
PeriodInfo cPerInfo = new PeriodInfo()
{
NumFilesReceived = weekFileList.Count(),
CurrStatus = "Ricevute",
ActionList = stdActList
};
string rawWeek = JsonConvert.SerializeObject(cPerInfo);
redHashWeek.Add(cWeek, rawWeek);
}
// salvo in redis...
string fullKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:WeekStats");
var okHashDict = redisMan.redSaveHashDict(fullKey, redHashWeek);
// invio ANCHE in MP-IO l'update delle info...
string remUrl = urlSetHashDict;
string dictPayload = JsonConvert.SerializeObject(redHashWeek);
//await callUrlWithPayloadAsync(remUrl, dictPayload, false);
await callUrlWithPayloadAsync(remUrl, dictPayload, true);
}
}
return answ;
}
/// <summary>
/// Verifica se si debba registrare/inviare il report periodico di consumo indicato
/// </summary>
/// <returns></returns>
protected bool RecipeDoProcCons()
{
bool answ = false;
// calcolo quale sia la settimana corrente...
int week = 0;
DateTime adesso = DateTime.Now;
Calendar cal = new CultureInfo("it-IT").Calendar;
// calcolo week da data-ora file...
week = cal.GetWeekOfYear(adesso, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
// verifico se ci sia la settimana indicata in elenco...
string currWeek = $"{adesso:yyyy}-{week:00}";
// recupero elenco delle settimane da processare da redis/WeekStats
string fullKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:WeekStats");
Dictionary<string, string> currStats = redisMan.redGetHashDict(fullKey);
if (currStats != null && currStats.Count > 0)
{
// definisco il DICT delle settimane da processare (= settimane passate, NON la corrente...)
foreach (var weekData in currStats)
{
if (weekData.Key != currWeek)
{
// ...x ogni settimana PASSATA effettua SendArch... --> lancia un task!
ProcessOtherInfo(weekData.Key, "SendArch");
}
}
answ = true;
}
return answ;
}
/// <summary>
/// Acquisisce eventuali nuove ricette
/// </summary>
/// <returns></returns>
protected bool RecipeDoSyncRecipe()
{
bool answ = false;
DateTime asdesso = DateTime.Now;
try
{
string recLocalArchPath = Path.Combine(pathList["fullPath-locBase"], pathList["fullPath-00-Arch"]);
string recRemArchPath = pathList["fullPath-06-remRec"];
// leggo da redis (se disponibile) elenco hast file/MD5....
string fullKey = redisMan.redHash($"IOB:Remote:{IOBConfFull.General.FilenameIOB}:FileCheck");
Dictionary<string, string> dictRecMd5 = redisMan.redGetHashDict(fullKey);
// leggo la directory remota...
var remoteList = Directory.GetFiles(recRemArchPath);
string fileName = "";
string newFilePath = "";
bool doAcquire = false;
int recNum = 0;
int newNum = 0;
string currMd5;
foreach (var remoteFile in remoteList)
{
recNum = 0;
doAcquire = false;
// verifico MD5...
currMd5 = baseUtils.GetFileMd5(remoteFile);
fileName = Path.GetFileName(remoteFile);
// verifo se c'è il file...
if (!dictRecMd5.ContainsKey(fileName))
{
dictRecMd5.Add(fileName, currMd5);
doAcquire = true;
}
else
{
//// verifico SE sia modificato da meno di 30 gg...
//DateTime lastMod = File.GetLastWriteTime(remoteFile);
if (dictRecMd5[fileName] != currMd5)
{
dictRecMd5[fileName] = currMd5;
doAcquire = true;
}
}
// se devo aggiungere processo --> fix numRicetta + 10'000
if (doAcquire)
{
answ = true;
// leggo contenuto...
string recContent = File.ReadAllText(remoteFile);
// calcolo nuovo nome
var namePart = fileName.Split('.');
int.TryParse(namePart[0], out recNum);
newNum = recNum + 10000;
// modifico ricetta
recContent = recContent.Replace($"<RecName>{namePart[0]}</RecName>", $"<RecName>{newNum}</RecName>");
// salvo ricetta modificata in archivio
newFilePath = Path.Combine(recLocalArchPath, $"{newNum}.xml");
// elimino eventuale file precedente...
if (File.Exists(newFilePath))
{
File.Delete(newFilePath);
}
File.WriteAllText(newFilePath, recContent);
}
}
// se ho importato qualcosa...
if (answ)
{
// salvo hash dei files...
redisMan.redSaveHashDict(fullKey, dictRecMd5);
}
}
catch (Exception exc)
{
lgError($"Eccezione durante RecipeDoSyncRecipe{Environment.NewLine}{exc}");
}
return answ;
}
/// <summary>
/// restitusice i record di consumo dei materiali associati al file:
/// </summary>
/// <param name="recipeFile">Percorso file XML</param>
/// <returns></returns>
protected List<ConsRec> RecipeGetCons(string recipeFile)
{
List<ConsRec> listConsumi = new List<ConsRec>();
// leggo contenuto XML
string rawData = File.ReadAllText(recipeFile);
// deserializza file XML x recuperare righe consumo
var currRecipe = XmlDataSerializer.Deserialize<ARecipe>(rawData);
if (currRecipe != null)
{
if (currRecipe.ColRecipe != null && currRecipe.ColRecipe.Count > 0)
{
List<ColData> RigheConsumi = new List<ColData>();
foreach (var rigaComp in currRecipe.ColRecipe)
{
ColData datiComp = rigaComp.ColData;
RigheConsumi.Add(datiComp);
}
// recupero data-ora ricetta da campo string
DateTime recExeDt = DateTime.Today;
DateTime.TryParse(currRecipe.DesRecipe.DesData.Date, out recExeDt);
// converto le righe di consumo ColData in ConsRec
listConsumi = RigheConsumi.Select(x => new ConsRec()
{
FileRef = recipeFile,
DtRif = recExeDt,
ColourCode = x.ColourCode,
Description = x.Description,
WeightGrTot = x.Weightgrtotal
}).ToList();
}
}
return listConsumi;
}
// // recupero data-ora ricetta da campo string
// DateTime recExeDt = DateTime.Today;
// DateTime.TryParse(currRecipe.DesRecipe.DesData.Date, out recExeDt);
// // converto le righe di consumo ColData in ConsRec
// listConsumi = RigheConsumi.Select(x => new ConsRec()
// {
// FileRef = recipeFile,
// DtRif = recExeDt,
// ColourCode = x.ColourCode,
// Description = x.Description,
// WeightGrTot = x.Weightgrtotal
// }).ToList();
// }
// }
// }
// catch (Exception exc)
// {
// lgError($"Eccezione in RecipeGetCons{Environment.NewLine}{exc}");
// }
// }
// return listConsumi;
// }
/// <summary>
/// Prepara la ricetta indicata partendo dai dati della ricetta template + dati ODL di riferimento
/// </summary>
/// <param name="recFilePath">Path del file template della ricetta</param>
/// <param name="podlReq">Record del PODL da inviare</param>
/// <returns>Path della ricetta da inviare</returns>
protected virtual bool RecipePrepare(string tempPath, string recFilePath, PODLModel podlReq)
{
bool fatto = false;
// leggo il file template ricetta
var rawData = File.ReadAllText(recFilePath);
string fixContent = rawData;
// eseguo sostituzioni
foreach (var rule in RecipeReplRules)
{
string replVal = rule.Value;
// calcolo la sostituzione per i valori SPECIFICI...
if (replVal.Contains("{{PODL}}"))
{
replVal = replVal.Replace("{{PODL}}", $"PODL{podlReq.IdxPromessa:00000000}");
}
if (replVal.Contains("{{Qty}}"))
{
replVal = replVal.Replace("{{Qty}}", $"{podlReq.NumPezzi}");
}
if (replVal.Contains("{{Note}}"))
{
replVal = replVal.Replace("{{Note}}", $"{podlReq.Note}");
}
fixContent = fixContent.Replace(rule.Key, replVal);
}
// scrivo file (secondo quantità...)!
string destPath = Path.Combine(tempPath, $"PODL{podlReq.IdxPromessa:00000000}.xml");
if (podlReq.NumPezzi <= maxQtyPerFile)
{
File.WriteAllText(destPath, fixContent);
fatto = true;
}
else
{
int numReq = (int)Math.Ceiling((double)podlReq.NumPezzi / maxQtyPerFile);
// splitto e scrivo...
for (int i = 1; i <= numReq; i++)
{
destPath = Path.Combine(tempPath, $"PODL{podlReq.IdxPromessa:00000000}-{i}.xml");
File.WriteAllText(destPath, fixContent);
}
fatto = true;
}
return fatto;
}
/// <summary>
/// Prepara le ricette dato fullPath temp scaricando elenco PODL
/// </summary>
/// <param name="tempPath">
/// Percorso temp di appoggio x preparare ricette (compreso nome macchina)
/// </param>
/// <param name="useLocal">
/// Indica se usare le copie locali delle ricette oppure richiedere da remoto (REST get)
/// </param>
/// <returns></returns>
protected virtual bool RecipeReqWriteLocal(string tempPath, bool useLocal)
{
bool fatto = false;
lgTrace($"RecipeReqWriteLocal start | tempPath {tempPath} | useLocal {useLocal}");
List<PODLModel> reqPOdlList = new List<PODLModel>();
// per prima cosa recupero elenco PODL da gestire....
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
var rawListPODL = await callUrl(urlGetNextPODL, false);
if (!string.IsNullOrEmpty(rawListPODL))
{
try
{
reqPOdlList = JsonConvert.DeserializeObject<List<PODLModel>>(rawListPODL);
}
catch (Exception exc)
{
lg.Error($"Errore: chiamata elenco PODL ha restituito errore{Environment.NewLine}{exc}");
}
}
else
{
lg.Error($"Errore: chiamata elenco PODL ({urlGetNextPODL}) ha restituito valore vuoto");
}
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in RecipeReqWriteLocal{Environment.NewLine}{ex.Message}");
}
// proseguo se ne ho trovati...
if (reqPOdlList.Count > 0)
{
// in questo elenco devo considerare solo PODL ATTIVI...
List<PODLModel> actPOdlList = reqPOdlList.Where(x => x.Attivabile).ToList();
// da questo elenco,se è rimasto qualcosa, ciclo x ricette da inviare
foreach (var actPodlReq in actPOdlList)
{
// devo escludere i PODL già gestiti
if (!POdlSentList.ContainsKey(actPodlReq.IdxPromessa))
{
string recFilePath = "";
try
{
// calcolo fullPath ricetta...
recFilePath = Path.Combine(pathList["fullPath-locBase"], pathList["fullPath-00-Arch"], actPodlReq.Recipe);
// preparo file ricetta x PODL
bool fileOk = RecipePrepare(tempPath, recFilePath, actPodlReq);
if (fileOk)
{
// aggiungo PODL ad elenco dei processati
POdlSentList.Add(actPodlReq.IdxPromessa, actPodlReq);
// indico okReport (almeno per 1 è ok...)
fatto = true;
}
}
catch (Exception exc)
{
lgError($"recipeReqWriteLocal | Errore durante ciclo verifica ricetta | idxPODL {actPodlReq.IdxPromessa} | recFilePath {recFilePath}{Environment.NewLine}{exc}");
}
}
}
// salvo su file elenco PODL gestiti/inviati
POdlSentFileArch = POdlSentList;
lgInfo($"RecipeReqWriteLocal | reqPOdlList: {reqPOdlList.Count} | actPOdlList: {actPOdlList.Count} | preparate: {POdlSentList.Count}");
}
return fatto;
}
/// <summary>
/// Effettua invio delle ricette alla macchina
/// </summary>
/// <param name="localPath">Area dove si trovano i fiel da trasmettere</param>
/// <param name="localArch">Area archivio</param>
/// <param name="remotePath">Area remota dove inviare files</param>
/// <returns></returns>
protected bool RecipeSend(string localPath, string localArch, string remotePath)
{
bool fatto = false;
lgTrace($"RecipeSend start | localPath {localPath} | localArch {localArch} | remotePath {remotePath}");
// recupero elenco files...
var fileList = Directory.GetFiles(localPath);
string archName = "";
string destName = "";
if (fileList != null && fileList.Count() > 0)
{
int nMove = 0;
foreach (var file in fileList)
{
string fileName = Path.GetFileName(file);
archName = Path.Combine(localArch, fileName);
destName = Path.Combine(remotePath, fileName);
lgInfo($"RecipeSend | fileName: {fileName} | archName: {archName} | destName: {destName} ");
File.Copy(file, destName, true);
File.Move(file, archName);
nMove++;
}
fatto = true;
lgInfo($"RecipeSend | trovate {fileList.Count()} file | {nMove} trasferiti");
}
return fatto;
}
/// <summary>
/// Effettua recupero in locale dei task eseguiti dalla macchina
/// </summary>
/// <param name="remotePath">Area remota da dove recuperare files</param>
/// <param name="localPath">Area dove parcheggiare temporaneamente i fiels x processing</param>
/// <returns></returns>
protected virtual bool RecipeTaskDoneRetrieve(string remotePath, string localPath)
{
bool fatto = false;
// recupero elenco files...
var fileList = Directory.GetFiles(remotePath);
string destName = "";
if (fileList != null && fileList.Count() > 0)
{
foreach (var file in fileList)
{
string fileName = Path.GetFileName(file);
destName = Path.Combine(localPath, fileName);
lgInfo($"Recupero Task Eseguiti | fileName: {fileName} | destName: {destName} ");
File.Move(file, destName);
}
fatto = true;
}
return fatto;
}
/// <summary>
/// Cancella dal server i task eseguiti
/// </summary>
/// <param name="taskName"></param>
/// <param name="esitoTask"></param>
/// <param name="codTav"></param>
/// <returns></returns>
protected async Task<string> remTask2exe(string taskName, string esitoTask, string codTav)
{
string answ = "";
bool srvAlive = await CheckServerAliveAsync();
if (srvAlive)
{
string url2call = $"{urlRemTask2Exe}{taskName}";
if (!string.IsNullOrEmpty(codTav))
{
url2call = $"{urlRemTask2ExeTav(codTav)}{taskName}";
}
await utils.callUrlAsync(url2call);
}
return answ;
}
/// <summary>
/// Salva nel dizionario il num di valori letti e filtrati/non inviati
/// </summary>
/// <param name="codFlux"></param>
protected void SaveFiltFluxLog(string codFlux)
{
if (DictFiltFLog.ContainsKey(codFlux))
{
DictFiltFLog[codFlux]++;
}
else
{
DictFiltFLog.Add(codFlux, 1);
_logger.Info($"FluxLog | veto | {codFlux}");
}
}
/// <summary>
/// Invio reset allarmi all'avvio per sicurezza...
/// </summary>
protected void SendAlarmReset()
{
if (alarmMaps != null)
{
// leggo a ciclo le aree degli allarmi CONFIGURATI, se ne trovo processo
foreach (var item in alarmMaps)
{
// banchi in array Int16 --> scompongo
for (int i = 0; i < item.size / 2; i++)
{
// INVIO CON LAST = 1 E NEW = 0 PER FORZARE INVIO...
sendAlarmVariations(item.memAddr, i, 1, 0, item.messages);
}
}
}
}
/// <summary>
/// Processa chiusura ODL
/// </summary>
/// <param name="idxOdl">Idx dell'ODL da chiudere</param>
/// <param name="dtRif">DataOra di riferimento evento stop</param>
protected async Task<bool> SendCloseOdl(int idxOdl, DateTime dtRif)
{
bool answ = false;
DateTime dtCurr = DateTime.Now;
string resp = await callUrl($"{urlODLClose}{idxOdl}&dtEve={dtRif}&dtCurr={dtCurr}", false);
answ = resp == "OK";
return answ;
}
/// <summary>
/// Processa chiusura PODL
/// </summary>
/// <param name="idxPOdl">Idx del PODL da chiudere</param>
/// <param name="dtRif">DataOra di riferimento evento stop</param>
protected bool SendClosePOdl(int idxPOdl, DateTime dtRif)
{
bool answ = false;
DateTime dtCurr = DateTime.Now;
string fullUrl = $"{urlPODLClose}{idxPOdl}&dtEve={dtRif:yyyyMMddHHmmssfff}&dtCurr={dtCurr:yyyyMMddHHmmssfff}";
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
// invio chiamata URL x chiusura ODL su macchina
string callResp = await callUrl(fullUrl, false);
answ = callResp == "OK";
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in SendClosePOdl | {Environment.NewLine}{ex.Message}");
}
return answ;
}
/// <summary>
/// Verifica se l'invio di un dato flux log sia abilitato o vietato secondo configurazione
/// - verifica rpesenza di un veto preesistente non scaduto
/// - se manca. impone nuovo veto dal "period" impostato in aree memMap (default: 60 sec)
/// </summary>
/// <param name="codFlux"></param>
/// <returns></returns>
protected bool sendEnabledFLog(string codFlux)
{
int vetoSec = vetoCodFluxDur(codFlux);
DateTime adesso = DateTime.Now;
bool hasVeto = vetoSendFLog.ContainsKey(codFlux);
// se ho un veto controllo scadenza...
if (hasVeto)
{
hasVeto = vetoSendFLog[codFlux] >= adesso;
// se fosse scaduto --> imposto nuovo...
if (!hasVeto)
{
vetoSendFLog[codFlux] = adesso.AddSeconds(vetoSec);
}
}
// se NON ci fosse veto o fosse scaduto --> imposto nuovo...
else
{
vetoSendFLog.Add(codFlux, adesso.AddSeconds(vetoSec));
}
// restituisco send enabled = NON ha veto
return !hasVeto;
}
/// <summary>
/// Invia informazioni associazione IOB 2 machine
/// </summary>
protected void SendM2IOB()
{
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () => await SendM2IobAsync())
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in SendM2IOB: {ex.Message}");
}
}
/// <summary>
/// Invia informazioni associazione IOB 2 machine
/// </summary>
protected async Task SendM2IobAsync()
{
if (await CheckServerAliveAsync())
{
string sendKey = "SendM2Iob";
bool sendEnab = CheckSendVeto(sendKey, TimeSpan.FromMinutes(120));
// se abilitato faccio invio e salvo nuovo valore
if (sendEnab)
{
// salvo nuovo valore invio
LastSendSet(sendKey, DateTime.Now);
// procedo
var result = await utils.callUrlAsync(urlSetM2IOB);
lgInfo($"chiamata URL {urlSetM2IOB} | result: {result}");
}
}
}
/// <summary>
/// Ponte Async invio MachineConf
/// </summary>
protected virtual void SendMachineConf()
{
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () => await SendMachineConfAsync())
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in SendMachineConf: {ex.Message}");
}
}
/// <summary>
/// Effettua invio al server IO dei dati di IOB + macchina:
/// - dati dell'IOB (IdxMacchina, IobName, IobIp, Tipo, counter...) "classici"
/// - dati di configurazione IOB "extra", ad esempio
/// - IobType (FANUC, Siemens, ModBus...)
/// - MachineIp
/// - MachinePort
/// - Abilitazione gestione FOLDER di ritorno x SPEC (es x FTP)
/// </summary>
protected virtual async Task SendMachineConfAsync()
{
// se non espressamente vietato da conf...
if (enabSendMachineConf)
{
if (await CheckServerAliveAsync())
{
string sendKey = "SendMachineConf";
bool sendEnab = CheckSendVeto(sendKey, TimeSpan.FromMinutes(120));
// se abilitato faccio invio e salvo nuovo valore
if (sendEnab)
{
// salvo nuovo valore invio
LastSendSet(sendKey, DateTime.Now);
// preparo dizionario da inviare
Dictionary<string, string> currDict = new Dictionary<string, string>();
Assembly entryAssembly = Assembly.GetEntryAssembly();
if (IOBConfFull != null)
{
// creo genObj dati necessari...
currDict.Add("IobName", IOBConfFull.General.FilenameIOB);
currDict.Add("IobType", $"{IOBConfFull.General.IobType}");
currDict.Add("MachIp", IOBConfFull.Device.Connect.IpAddr);
currDict.Add("MachPort", IOBConfFull.Device.Connect.Port);
currDict.Add("Vendor", IOBConfFull.Device.Vendor);
currDict.Add("Model", IOBConfFull.Device.Model);
currDict.Add("IobExe", $"{entryAssembly?.GetName().Name}");
currDict.Add("IobVersion", IOBConfFull.General.RelVers);
if (IOBConfFull.OptPar != null)
{
var ordPar = IOBConfFull.OptPar.OrderBy(x => x.Key).ToList();
foreach (var item in ordPar)
{
currDict.Add($"OP_{item.Key}", item.Value);
}
}
// invio e salvo...
string remUrl = urlSaveMachIobConf;
string dictPayload = JsonConvert.SerializeObject(currDict);
await callUrlWithPayloadAsync(remUrl, dictPayload, true);
lgTrace("Invio MachineConf effettuato");
}
}
}
}
}
/// <summary>
/// Invia al server IO i valori dei parametri opzionali (es counters)
/// </summary>
/// <param name="paramName">Nome parametro</param>
/// <param name="paramValue">Valore parametro</param>
protected async Task sendOptVal(string paramName, string paramValue)
{
// salvo comunque in fluxLog...
bool sent = accodaFLog(paramName, $"{paramName}|{paramValue}", qEncodeFLog(paramName, paramValue));
if (sent)
{
// traccio valore DynData x analisi
trackDynData(paramName, paramValue);
bool srvAlive = await CheckServerAliveAsync();
if (srvAlive)
{
string url2call = $"{urlSetOptVal}pName={paramName}&pValue={paramValue}";
lgInfo("chiamata URL " + url2call);
await utils.callUrlAsync(url2call);
#if false
utils.callUrlNow(url2call);
#endif
}
}
}
/// <summary>
/// Invia al server IO i valori dei parametri opzionali (es counters)
/// </summary>
/// <param name="paramName">Nome parametro</param>
/// <param name="paramValueInt">Valore parametro INT</param>
protected async Task sendOptVal(string paramName, int paramValueInt)
{
// override!
await sendOptVal(paramName, $"{paramValueInt}");
}
/// <summary>
/// Invio contapezzi alla dataora indicata
/// </summary>
/// <param name="numPz">Num pezzi da registrare</param>
/// <param name="dtRif">DataOra di riferimento evento</param>
protected bool SendPzIncrAtDate(int numPz, DateTime dtRif)
{
bool answ = false;
DateTime dtCurr = DateTime.Now;
// chiamata avvio PODL (a posteriori)
string fullUrl = $"{urlAddPzCountAtDate}{numPz}&dtEve={dtRif:yyyyMMddHHmmssfff}&dtCurr={dtCurr:yyyyMMddHHmmssfff}";
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
// invio chiamata URL x chiusura ODL su macchina
string callResp = await callUrl(fullUrl, false);
answ = callResp == "OK";
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in SendPzIncrAtDate | {Environment.NewLine}{ex.Message}");
}
return answ;
}
/// <summary>
/// Processa avvio PODL (ed eventuale chiusura precedente)
/// </summary>
/// <param name="idxPOdl">Idx del PODL da avviare</param>
/// <param name="dtRif">DataOra di riferimento evento start</param>
protected bool SendStartPodl(int idxPOdl, DateTime dtRif)
{
bool answ = false;
DateTime dtCurr = DateTime.Now;
// chiamata avvio PODL (a posteriori)
string fullUrl = $"{urlOdlStartFromPOdl}{idxPOdl}&dtEve={dtRif:yyyyMMddHHmmssfff}&dtCurr={dtCurr:yyyyMMddHHmmssfff}";
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
// invio chiamata URL x chiusura ODL su macchina
string callResp = await callUrl(fullUrl, false);
answ = callResp == "OK";
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in SendStartPodl | {Environment.NewLine}{ex.Message}");
}
return answ;
}
/// <summary>
/// Invia messaggio a logWatcher
/// </summary>
/// <param name="messType"></param>
/// <param name="message"></param>
protected void sendToTaskWatch(string messType, string message, string codTav)
{
string logMsg = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} | {messType} | {message}";
if (string.IsNullOrEmpty(codTav))
{
logMsg += $" | {messType} | {message}";
}
else
{
logMsg += $" | {codTav} | {messType} | {message}";
}
parentForm.taskWatcher = logMsg;
}
/// <summary>
/// Impostazioni parametri PLC
/// </summary>
protected virtual void setParamPlc()
{
loadMemConf();
fixDefaultPar();
if (resetAlarmOnStart)
{
SendAlarmReset();
}
}
/// <summary>
/// setup gestione allarmi da conf
/// </summary>
protected void setupAlarmMap()
{
// indico quanti allarmi
foreach (var item in alarmMaps)
{
item.setupData();
// se ho in redis una tab di allarmi precedenti la carico...
string alarmHash = redisMan.redHash($"IOB:ALARM_STATUS:{IOBConfFull.General.FilenameIOB}:{item.memAddr}");
string rawVal = redisMan.getRSV(alarmHash);
if (!string.IsNullOrEmpty(rawVal))
{
// provo a convertire
var lastState = JsonConvert.DeserializeObject<uint[]>(rawVal);
if (lastState != null && lastState.Length > 0)
{
item.loadPrev(lastState);
}
}
// loggo
lgDebug($"Decodifica aree alarmMap: {item.description} | {item.memAddr} x {item.size} byte | {item.messages.Count} messaggi allarme");
}
// SE NECESSARIO ALL'AVVIO INVIO DATI VUOTI...
if (resetAlarmOnStart)
{
SendAlarmReset();
}
// invio oggetto alarmMap al server x successiva decodifica
// FIXME TODO FARE !!!! invio PUT del file *_alarm.json
}
/// <summary>
/// Setup parametri decode file excel (opzionale)
/// </summary>
protected virtual void setupFileDecod()
{
if (memMap == null)
{
lgWarn($"setupFileDecod | memMap nullo | no processing");
}
else
{
// verifica se siano necessari configuraizoni speciali dalla excDecod:
// es: lettura/invio file excel
if (memMap.FileDecod != null && memMap.FileDecod.Count > 0)
{
FileDecod = memMap.FileDecod;
}
}
}
/// <summary>
/// setup parametri da file di conf
/// </summary>
protected void setupMemMap()
{
bool serverOk = GetPingStatus() == IPStatus.Success;
if (memMap == null)
{
lgWarn($"setupMemMap | memMap nullo");
}
else
{
lgDebug($"setupMemMap | trovati {memMap.mMapRead.Count} parametri Read (TSVC)");
lgDebug($"setupMemMap | trovati {memMap.mMapWrite.Count} parametri Write");
if (utils.CRB("verbose"))
{
string rawMemConf = JsonConvert.SerializeObject(memMap, Formatting.Indented);
lgDebug($"setupMemMap | configurazione memoria R/W:{Environment.NewLine}{rawMemConf}");
}
// se ho variabili read --> genero dati TSVC...
if (memMap.mMapRead.Count > 0)
{
TSVC_Data.Clear();
LastTSVC.Clear();
VCData currConf;
int periodo = 0;
VC_func funz = VC_func.POINT;
// accodo nella conf...
foreach (var item in memMap.mMapRead)
{
funz = item.Value.func;
periodo = item.Value.period;
currConf = new VCData()
{
Funzione = funz,
Period = periodo,
UM = item.Value.unit,
// imposto a NOW per forzare accumulo dati prima di inviare troppo presto (es COMECA Pizzaferri valori 0)
DTStart = DateTime.Now,
//DTStart = DateTime.Now.AddHours(-1),
dataArray = new List<double>()
};
TSVC_Data.Add(item.Key, currConf);
}
// log avvio + setup lastVal...
foreach (var item in TSVC_Data)
{
lgTrace($"TSVC: {item.Key} | periodo: {item.Value.Period} | funz: {item.Value.Funzione}");
// salvo i valori PREC...
LastTSVC.Add(item.Key, 0);
}
}
// infine se genObj memoria valido salvo in MP-IO x sue applicazioni
if (memMap != null)
{
// invio su cloud conf memoria...
string rawData = JsonConvert.SerializeObject(memMap, Formatting.Indented);
// controllo ping al server...
if (serverOk)
{
string sendKey = "SendMemMap";
bool sendEnab = CheckSendVeto(sendKey, TimeSpan.FromMinutes(480));
// se abilitato faccio invio e salvo nuovo valore
if (sendEnab)
{
var resp = utils.callUrl($"{urlSaveMemMap}", rawData);
}
}
else
{
lgInfo($"Mancata risposta ping da server, saltato step invio memMap a {urlSaveMemMap}");
}
// salvo ANCHE come parametri i valori...
objItem currItem = new objItem();
List<objItem> allParam = new List<objItem>();
string currValore = "";
// valori WRITE
foreach (var item in memMap.mMapWrite)
{
currValore = "";
//se ho valori current li impiego...
if (currProdData.ContainsKey(item.Value.name))
{
currValore = currProdData[item.Value.name];
item.Value.value = currValore;
}
currItem = new objItem()
{
uid = item.Value.name,
name = item.Value.name,
description = !string.IsNullOrEmpty(item.Value.description) ? item.Value.description : item.Value.name,
writable = true,
UM = item.Value.unit,
valMin = item.Value.minVal,
valMax = item.Value.maxVal,
value = currValore,
displOrdinal = item.Value.displOrdinal
};
allParam.Add(currItem);
}
// valori READ
foreach (var item in memMap.mMapRead)
{
// se non ci fosse aggiungo
var trovato = allParam.FirstOrDefault(x => x.uid == item.Value.name);
if (trovato == null)
{
currValore = "";
// se ho valori current li impiego...
if (currProdData.ContainsKey(item.Value.name))
{
currValore = currProdData[item.Value.name];
item.Value.value = currValore;
}
currItem = new objItem()
{
uid = item.Value.name,
name = item.Value.name,
description = !string.IsNullOrEmpty(item.Value.description) ? item.Value.description : item.Value.name,
writable = false,
UM = item.Value.unit,
valMin = item.Value.minVal,
valMax = item.Value.maxVal,
value = currValore,
displOrdinal = item.Value.displOrdinal
};
allParam.Add(currItem);
}
}
// invio su cloud parametri SE sono connesso alla macchina... in pratica reset parametri...
string tipoCall = urlSaveAllParams;
rawData = JsonConvert.SerializeObject(allParam, Formatting.Indented);
if (serverOk)
{
// verifica se sia un IOB "parziale" --> salva solo update ai parametri
if (IOBConfFull.Device.DisabExeTask || IOBConfFull.Device.DisabStateCh)
{
// versione upsert
tipoCall = urlUpdateWriteParams;
}
var resp = utils.callUrl($"{tipoCall}", rawData);
}
else
{
lgInfo($"Mancata risposta ping server, non effettuata chiamata {tipoCall}");
}
lgDebug($"setupMemMap | salvata conf memoria R/W");
}
}
}
/// <summary>
/// Setup parametri memoria opzionali
/// es: BitCond e IntCond x ricerca valori target
/// </summary>
protected virtual void setupOptMemPar()
{
if (memMap == null)
{
lgWarn($"setupOptMemPar | memMap nullo");
}
else
{
// verifica se siano necessari configurazioni speciali dalla OptMemPar:
// es: per ricerca condizioni status booleane come ModbusTCP...
if (memMap.OptMemPar != null && memMap.OptMemPar.Count > 0)
{
// cerco condizioni BIT speciali x Auto, Estop, Work.. devono essere *BitCond
var listPar2add = memMap.OptMemPar.Where(x => x.Key.EndsWith("BitCond")).ToList();
if (listPar2add != null && listPar2add.Count > 0)
{
foreach (var item in listPar2add)
{
addCheckConditionBit(item.Key, item.Value);
}
}
// cerco condizioni INT speciali x Auto, Estop, Work.. devono essere *IntCond
var listParInt2add = memMap.OptMemPar.Where(x => x.Key.EndsWith("IntCond")).ToList();
if (listParInt2add != null && listParInt2add.Count > 0)
{
foreach (var item in listParInt2add)
{
addCheckConditionInt(item.Key, item.Value);
}
}
// cerco valori *2Translate
var listVal2Translate = memMap.OptMemPar.Where(x => x.Key.Contains("2Translate")).ToList();
if (listVal2Translate != null && listVal2Translate.Count > 0)
{
// cerco solo valori BIT...
var listValBit = listVal2Translate.Where(x => x.Key.StartsWith("VarBit2Translate")).ToList();
if (listValBit != null && listValBit.Count > 0)
{
foreach (var item in listValBit)
{
addVal2TranslBit(item.Value);
}
}
// ...e poi valori INT...
var listValInt = listVal2Translate.Where(x => x.Key.StartsWith("VarInt2Translate")).ToList();
if (listValInt != null && listValInt.Count > 0)
{
foreach (var item in listValInt)
{
addVal2TranslInt(item.Value);
}
}
}
}
}
}
/// <summary>
/// Init parametri speciali, tipicamente KVP opzionali da json
/// </summary>
protected virtual void setupSpecialParams()
{
string sCond = "";
// per prima cosa inizializzo lista PODL inviati
POdlSentList = POdlSentFileArch;
// verifico se sia attiva gestione riduzione FluxLog...
if (!string.IsNullOrEmpty(getOptJsonKVP("fluxLogReduce")))
{
bool.TryParse(getOptJsonKVP("fluxLogReduce"), out fluxLogReduce);
double.TryParse(getOptJsonKVP("fluxLogRedDeadBand"), NumberStyles.Any, CultureInfo.InvariantCulture, out fluxLogRedDeadBand);
int.TryParse(getOptJsonKVP("fluxLogResendPeriod"), out fluxLogResendPeriod);
lgInfo($"FluxLog Redux | fluxLogReduce: {fluxLogReduce} | fluxLogRedDeadBand: {fluxLogRedDeadBand:N2} | fluxLogResendPeriod: {fluxLogResendPeriod} min");
}
// check veto livelli allarmi/conditions
string rawVeto = getOptJsonKVP("condLevelVeto");
if (!string.IsNullOrEmpty(rawVeto))
{
char separatore = '|';
// accetto questi separatori '|' o '#'
int indice = rawVeto.IndexOfAny(new char[] { '|', '#' });
if (indice >= 0)
{
separatore = rawVeto[indice];
}
string[] valori = rawVeto.Split(separatore);
ListVetoCond = valori.ToList();
}
// check modalità ricetta attiva
bool.TryParse(getOptJsonKVP("hasRecipe"), out hasRecipe);
// verifico se usare ricette locali/http...
bool.TryParse(getOptJsonKVP("useLocalRecipe"), out useLocalRecipe);
// recupero quantitativo massimo KG x PODL
int.TryParse(getOptJsonKVP("maxPodlQty"), out maxQtyPerFile);
if (memMap == null)
{
lgWarn("setupSpecialParams: memMap nullo!");
}
else
{
// recupero le folder specifiche x IN/OUT ricette filtrando direttamente l'area di memoria...
sCond = "fullPath";
var dirList = memMap.OptKVP
.Where(x => x.Key.StartsWith(sCond))
.ToList();
pathList = dirList.ToDictionary(x => x.Key, x => x.Value);
// gestione replace in file ricette...
sCond = "replace-";
var ruleList = memMap.OptKVP
.Where(x => x.Key.StartsWith(sCond))
.ToList();
RecipeReplRules = ruleList.ToDictionary(x => x.Key.Replace(sCond, ""), x => x.Value);
}
}
/// <summary>
/// Cleanup stringa x impiego tipo ident da char dubbi
/// </summary>
/// <param name="origData"></param>
/// <returns></returns>
protected string strFixId(string origData)
{
return origData.Replace(".", "").Replace(" ", "_");
}
/// <summary>
/// Traccia in redis un dynData x analisi frequenza e campionamento valori FluxLog ...
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
protected void trackDynData(string key, string value)
{
// attenzione: x ora cablato 3 gg e 1 mese le registrazioni
DateTime oggi = DateTime.Today;
string DayCurr = $"{oggi:yyMMdd}";
// se cambiato giorno --> forzo scrittura senza aggiunte
if (!LastDayCurr.Equals(DayCurr))
{
DoTrackDataSave();
}
// accumulo stat Day
DoTrackDataCount(key, value);
// ...e se > soglia registro
DoTrackDataSave();
}
/// <summary>
/// Traccia in redis l'attività di scambio dati (tipicamente non in polling)
/// </summary>
/// <param name="byteSize">Dim pacchetto (numero byte scambiati/ricevuti)</param>
/// <param name="val2rec">Dim minima x registrare info</param>
protected void trackExchData(long byteSize, long val2rec = 512)
{
// accumulo counter byte
currByteCount += byteSize;
// se superato limite scrivo (default 128byte)
if (currByteCount >= val2rec)
{
string rkeyTS = $"{redisMan.redIobTrackKey}:DataInTS";
string rkeyHS = $"{redisMan.redIobTrackKey}:DataInHS";
// chiamo direttamente metodo redis...
string newUid_ = redisMan.TrackExchData(rkeyTS, rkeyHS, currByteCount);
currByteCount = 0;
}
}
/// <summary>
/// Track dati ricevuti generici (serializzando)
/// </summary>
/// <param name="genObj"></param>
protected void trackExchDataRaw(object genObj, long val2rec = 512)
{
try
{
long byteSize = GetObjectSize(genObj, false);
// salvo dim caratteri ricevuti
trackExchData(byteSize, val2rec);
}
catch (Exception exc)
{
lgError($"trackExchDataRaw | errore in fase di track Obj ricevuto{Environment.NewLine}{exc}");
}
}
/// <summary>
/// Traccia in redis l'attività di lettura dati
/// </summary>
/// <param name="byteSize">Dim pacchetto (numero byte letti)</param>
/// <param name="timeSec">Se specificato indica il tempo effettivo di lettura, se zero uso timespan giornaliero...</param>
protected void trackReadData(int byteSize, double timeSec)
{
// attenzione: x ora cablato 1 mese le registrazioni
DateTime adesso = DateTime.Now;
double kbRead = (double)byteSize / 1024;
var cultInv = CultureInfo.InvariantCulture;
// per prima cosa LEGGO il valore precedente
string rKey = $"{redisMan.redIobTrackKey}:DataIn:{adesso:yyMMdd}";
var actData = redisMan.redGetHashDict(rKey);
// KB letti
double dVal = 0;
if (actData.ContainsKey("KbRead"))
{
double.TryParse(actData["KbRead"], NumberStyles.Float, cultInv, out dVal);
}
dVal += kbRead;
string sDouble = dVal.ToString("F3", cultInv);
if (actData.ContainsKey("KbRead"))
{
actData["KbRead"] = sDouble;
}
else
{
actData.Add("KbRead", sDouble);
}
// TIMING: se ho un valore lo aggiungo, altrimenti calcolo da mezzanotte
double dPTime = 0;
if (timeSec > 0)
{
if (actData.ContainsKey("ProcTime"))
{
double.TryParse(actData["ProcTime"], NumberStyles.Float, cultInv, out dPTime);
}
dPTime += timeSec;
}
else
{
dPTime = adesso.Subtract(adesso.Date).TotalSeconds;
}
sDouble = dPTime.ToString("F3", cultInv);
if (actData.ContainsKey("ProcTime"))
{
actData["ProcTime"] = sDouble;
}
else
{
actData.Add("ProcTime", sDouble);
}
// salvo il mio hash aggiornato!
redisMan.redSaveHashDict(rKey, actData);
}
/// <summary>
/// Reset valori trackdata all'avvio dell'adapter IOB
/// </summary>
protected void trackReadDataReset()
{
// per prima cosa LEGGO il valore precedente
DateTime adesso = DateTime.Now;
string rKey = $"{redisMan.redIobTrackKey}:DataIn:{adesso:yyMMdd}";
redisMan.redDelKey(rKey);
}
/// <summary>
/// Versione sync richiesta chiusura ODL corrente
/// </summary>
/// <returns></returns>
protected bool TryAskCloseCurrODL()
{
bool fatto = false;
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
fatto = await TryAskCloseCurrODLAsync();
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in TryAskCloseCurrODL{Environment.NewLine}{ex.Message}");
}
return fatto;
}
/// <summary>
/// Effettua chiamata MP-IO per chiedere all'utente se vuole effettuare chiusura ODL
/// corrente della macchina
/// </summary>
/// <returns></returns>
protected async Task<bool> TryAskCloseCurrODLAsync()
{
bool fatto = false;
string fullUrl = $"{urlODLAskClose}{currIdxODL}";
try
{
// invio chiamata URL x chiusura ODL su macchina
string callResp = await callUrl(fullUrl, false);
fatto = (callResp != "KO") ? true : false;
}
catch
{ }
lgInfo($"Eseguito TryAskCloseCurrODLAsync per {currIdxODL} | url: {fullUrl} | esito: {fatto}");
return fatto;
}
/// <summary>
/// Versione sync chiusura ODL corrente
/// </summary>
/// <returns></returns>
protected bool TryCloseCurrODL()
{
bool fatto = false;
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
fatto = await TryCloseCurrODLAsync();
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in TryCloseCurrODLAsync{Environment.NewLine}{ex.Message}");
}
return fatto;
}
/// <summary>
/// Effettua chiamata MP-IO per tentare chiusura ODL corrente della macchina
/// </summary>
/// <returns></returns>
protected async Task<bool> TryCloseCurrODLAsync()
{
bool fatto = false;
string fullUrl = $"{urlODLClose}{currIdxODL}";
try
{
// invio chiamata URL x chiusura ODL su macchina
string callResp = await callUrl(fullUrl, false);
fatto = (callResp != "KO") ? true : false;
}
catch
{ }
lgInfo($"Eseguito TryCloseCurrODLAsync per {currIdxODL} | url: {fullUrl} | esito: {fatto}");
return fatto;
}
/// <summary>
/// Versione sync chiusura ODL specifico
/// </summary>
/// <param name="IdxODL"></param>
/// <returns></returns>
protected bool TryCloseODL(int IdxODL)
{
bool fatto = false;
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
fatto = await TryCloseODLAsync(IdxODL);
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in TryCloseODLAsync | IdxODL: {IdxODL}{Environment.NewLine}{ex.Message}");
}
return fatto;
}
/// <summary>
/// Effettua chiamata MP-IO per tentare chiusura ODL specifico
/// </summary>
/// <param name="IdxODL"></param>
/// <returns></returns>
protected async Task<bool> TryCloseODLAsync(int IdxODL)
{
bool fatto = false;
string fullUrl = $"{urlODLClose}{IdxODL}";
try
{
// invio chiamata URL x chiusura ODL su macchina
string callResp = await callUrl(fullUrl, false);
fatto = (callResp != "KO") ? true : false;
}
catch
{ }
lgInfo($"Eseguito TryCloseODLAsync per {IdxODL} | url: {fullUrl} | esito: {fatto}");
return fatto;
}
/// <summary>
/// Effettua chiamata MP-IO per tentare chiusura PODL --&gt; ODL specifico
/// </summary>
/// <param name="idxPODL"></param>
/// <returns></returns>
protected bool TryClosePODL(int idxPODL)
{
bool fatto = false;
string fullUrl = $"{urlPODLClose}{idxPODL}";
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
// invio chiamata URL x chiusura ODL su macchina
string callResp = await callUrl(fullUrl, false);
fatto = (callResp != "KO") ? true : false;
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in TryClosePODL | idxPODL: {idxPODL}{Environment.NewLine}{ex.Message}");
}
lgInfo($"Eseguito TryClosePODL per {idxPODL} | url: {fullUrl} | esito: {fatto}");
return fatto;
}
/// <summary>
/// Effettua chiamata MP-IO per tentare chiusura PODL --&gt; ODL specifico
/// </summary>
/// <param name="idxPODL"></param>
/// <param name="doConfirm"></param>
/// <param name="dtEve"></param>
/// <param name="dtCurr"></param>
/// <returns></returns>
protected bool TryClosePODL(int idxPODL, DateTime dtEve, DateTime dtCurr)
{
bool fatto = false;
string fullUrl = $"{urlPODLClose}{idxPODL}&dtEve={dtEve:yyyyMMddHHmmssfff}&dtCurr={dtCurr:yyyyMMddHHmmssfff}";
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
// invio chiamata URL x chiusura ODL su macchina
string callResp = await callUrl(fullUrl, false);
fatto = (callResp != "KO") ? true : false;
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in TryClosePODL | idxPODL: {idxPODL} | dtEve: {dtEve} | dtCurr: {dtCurr}{Environment.NewLine}{ex.Message}");
}
lgInfo($"Eseguito TryClosePODL per {idxPODL} | url: {fullUrl} | esito: {fatto}");
return fatto;
}
/// <summary>
/// Processa avvio PODL (ed eventuale chiusura precedente)
/// </summary>
/// <param name="codArt">Cod Articolo del PODL da avviare</param>
/// <param name="codGruppo">Cod Gruppo dell'impianto come default</param>
/// <param name="numPz">num pz richiesti</param>
protected int TryCreatePodl(string codArt, string codGruppo, int numPz)
{
int answ = 0;
string urlEncoded = $"{urlCreatePOdl}CodArt={codArt}&CodGruppo={codGruppo}&numPz={numPz}";
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
string resp = await callUrl(urlEncoded, false);
int.TryParse(resp, out answ);
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in TryCreatePodl | codArt: {codArt} | codGruppo: {codGruppo} | numPz: {numPz}{Environment.NewLine}{ex.Message}");
}
return answ;
}
/// <summary>
/// Effettua verifica se abilitato invio pezzi in blocco PER TAVOLE e nel caso
/// - invio in blocco pezzi
/// - aggiornamento del contapezzi (passato come ref) x nuovo valore post invio
/// </summary>
/// <param name="fullCode">Idx macchina completo, con tavola/pallet di invio</param>
/// <param name="pzCountMes">Contapezzi MES (IOB) attuale</param>
/// <param name="pzCountImp">Contapezzi impianto (per la tavola indicata)</param>
protected virtual int trySendPzCountBlock(string fullCode, int pzCountMes, int pzCountImp)
{
int qtyAdded = 0;
lgDebug($"Chiamata trySendPzCountBlock MULTI | fullCode: {fullCode} | pzCountMes: {pzCountMes} | pzCountImp: {pzCountImp}");
// in primis HA SENSO procedere SOLO SE server MP è Online...
if (MPOnline)
{
// SOLO SE online la macchina...
if (IobOnline)
{
int numIncr = 0;
// verifico se la funzione SIA abilitata
if (enableSendPzCountBlock)
{
int delta = pzCountImp - pzCountMes;
// se è abilitata verifico differenza: se ho DELTA > minSendPzCountBlock -->
// invio un blocco <= maxSendPzCountBlock
if (delta > minSendPzCountBlock)
{
// init genObj display
newDisplayData currDispData = new newDisplayData();
// resta indietro di ALMENO minSendPzCountBlock pezzi x recuperare 1:1...
numIncr = delta > maxSendPzCountBlock + minSendPzCountBlock ? maxSendPzCountBlock : delta - minSendPzCountBlock;
// invio il num max di pezzi ammesso in blocco!
lastUrl = $"{urlAddPzCount}{numIncr}".Replace(IOBConfFull.General.CodIOB, fullCode);
string resp = utils.CallUrlGet(lastUrl);
if (!string.IsNullOrEmpty(resp))
{
// dalla risposta (come numero) capisco SE ha aggiunto i pezzi (e quanti)
int.TryParse(resp, out qtyAdded);
if (qtyAdded > 0)
{
// aggiorno contapezzi ...
pzCountMes += qtyAdded;
lgInfo($"SEND incremento contapezzi: send: {numIncr} | resp: {qtyAdded} | contapezziMES: {pzCountMes}");
// invio conferma contapezzi..
string fullUrl = $"{urlSetPzCount}{pzCountMes}".Replace(IOBConfFull.General.CodIOB, fullCode);
string retVal = utils.callUrl(fullUrl);
// verifica se tutto OK
if (retVal != $"{pzCountMes}")
{
// errore salvataggio contapezzi
lgError($"trySendPzCountBlock: errore salvataggio contapezzi: contapezziMES {pzCountMes} | risposta: {retVal}");
}
}
else
{
lgError($"Richiesto incremento {numIncr} ma NON registrato su server MP-IO");
}
}
currDispData.newUrlCallData = lastUrl;
currDispData.counter = pzCountMes;
currDispData.semOut = Semaforo.SV;
raiseRefresh(currDispData);
}
}
}
else
{
lgError("Impossibile trySendPzCountBlock: IobOnline è false");
}
}
else
{
lgError("Impossibile trySendPzCountBlock: MPOnline è false");
}
return qtyAdded;
}
/// <summary>
/// Effettua verifica se abilitato invio pezzi in blocco (caso macchina standard/singolo contapezzi) e nel caso
/// - invio in blocco pezzi
/// - aggiornamento del contapezzi (passato come ref) x nuovo valore post invio
/// </summary>
protected virtual void trySendPzCountBlock()
{
lgDebug($"Chiamata trySendPzCountBlock STD | pzCountMes: {contapezziIOB} | pzCountImp: {contapezziPLC}");
// in primis HA SENSO procedere SOLO SE server MP è Online...
if (MPOnline)
{
// SOLO SE online la macchina...
if (IobOnline)
{
int numIncr = 0;
int qtyAdded = 0;
// verifico se la funzione SIA abilitata
if (enableSendPzCountBlock)
{
int delta = contapezziPLC - contapezziIOB;
// se è abilitata verifico differenza: se ho DELTA > minSendPzCountBlock -->
// invio un blocco <= maxSendPzCountBlock
if (delta > minSendPzCountBlock)
{
// init genObj display
newDisplayData currDispData = new newDisplayData();
// resta indietro di ALMENO minSendPzCountBlock pezzi x recuperare 1:1...
numIncr = delta > maxSendPzCountBlock + minSendPzCountBlock ? maxSendPzCountBlock : delta - minSendPzCountBlock;
// invio il num max di pezzi ammesso in blocco!
lastUrl = $"{urlAddPzCount}{numIncr}";
string resp = utils.CallUrlGet(lastUrl);
if (!string.IsNullOrEmpty(resp))
{
// dalla risposta (come numero) capisco SE ha aggiunto i pezzi (e quanti)
int.TryParse(resp, out qtyAdded);
if (qtyAdded > 0)
{
// aggiorno IL MIO contapezzi...
contapezziIOB += qtyAdded;
lgInfo($"SEND incremento contapezzi: send: {numIncr} | resp: {qtyAdded} | contapezziIOB: {contapezziIOB}");
// invio conferma contapezzi..
string fullUrl = $"{urlSetPzCount}{contapezziIOB}";
string retVal = utils.callUrl(fullUrl);
// verifica se tutto OK
if (retVal != contapezziIOB.ToString())
{
// errore salvataggio contapezzi
lgError($"trySendPzCountBlock: errore salvataggio contapezzi: contapezziIOB {contapezziIOB} | risposta: {retVal}");
}
}
else
{
lgError($"Richiesto incremento {numIncr} ma NON registrato su server MP-IO");
}
}
currDispData.newUrlCallData = lastUrl;
currDispData.counter = contapezziIOB;
currDispData.semOut = Semaforo.SV;
raiseRefresh(currDispData);
}
}
}
else
{
lgError("Impossibile trySendPzCountBlock: IobOnline è false");
}
}
else
{
lgError("Impossibile trySendPzCountBlock: MPOnline è false");
}
}
/// <summary>
/// Cerca di inviare su un altro thread i vari dati accumulati...
/// </summary>
protected async Task TrySendValuesAsync()
{
// init genObj display
newDisplayData currDispData = new newDisplayData();
try
{
// verifico se risponde il server...
bool srvAlive = await CheckServerAliveAsync();
if (srvAlive)
{
bool iobOk = false;
iobOk = await CheckIobEnabled();
// verifico SE posso inviare dati
if (iobOk)
{
currDispData.semOut = Semaforo.SV;
// gestione queue SignalIN (invio, display)
await svuotaCodaSignInAsync();
currDispData.counter = contapezziIOB;
raiseRefresh(currDispData);
// provo a svuotare coda contapezzi
await SvuotaCodaContapezziAsync();
currDispData.counter = contapezziIOB;
raiseRefresh(currDispData);
// gestione queue FluxLog (invio, display)
await SvuotaCodaFLogAsync();
// coda RawTransf
await SvuotaCodaRawTransfAsync();
// coda UserLog
await SvuotaCodaULogAsync();
// refresh
raiseRefresh(currDispData);
// se arrivo è tutto ok...
currSendErrors = 0;
}
else
{
// mostro VETO-SEND x invio... GIALLO
currDispData.semOut = Semaforo.SG;
if (periodicLog)
{
lgInfo("IOB - VETO SEND");
}
}
}
else
{
// mostro SERVER KO x invio... ROSSO
currDispData.semOut = Semaforo.SR;
if (periodicLog)
{
lgWarn("IOB | TrySendValuesAsync.CheckServerAliveAsync | SERVER NOT READY");
}
}
}
catch (Exception exc)
{
lgError($"Errore in fase TrySendValuesAsync | currSendErrors: {currSendErrors}{Environment.NewLine}{exc}");
currDispData.semOut = Semaforo.SR;
}
raiseRefresh(currDispData);
}
/// <summary>
/// Versione sync chiamata setup PODL
/// </summary>
/// <param name="idxPODL"></param>
/// <returns></returns>
protected bool TrySetupPODL(int idxPODL)
{
bool fatto = false;
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
fatto = await TrySetupPODLAsync(idxPODL);
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in TrySetupPODL | idxPODL: {idxPODL}{Environment.NewLine}{ex.Message}");
}
return fatto;
}
/// <summary>
/// Effettua chiamata MP-IO per tentare setup del PODL indicato
/// </summary>
/// <param name="idxPODL"></param>
/// <returns></returns>
protected async Task<bool> TrySetupPODLAsync(int idxPODL)
{
bool fatto = false;
string fullUrl = $"{urlOdlStartFromPOdl}{idxPODL}";
try
{
// invio chiamata URL x avvio PODL su macchina
string rawSplit = await callUrl(fullUrl, false);
fatto = (rawSplit != "KO") ? true : false;
}
catch
{ }
return fatto;
}
/// <summary>
/// Effettua chiamata MP-IO per tentare setup del PODL indicato con indicazioni estese
/// (confirm pezzi, dtEvento, dtCorrente)
/// </summary>
/// <param name="idxPODL"></param>
/// <param name="doConfirm"></param>
/// <param name="dtEve"></param>
/// <param name="dtCurr"></param>
/// <returns></returns>
protected async Task<bool> TrySetupPODLAsync(int idxPODL, bool doConfirm, DateTime dtEve, DateTime dtCurr)
{
bool fatto = false;
//string format = "yyyyMMddHHmmssfff";
string fullUrl = $"{urlOdlStartFromPOdl}{idxPODL}&doConfirm={doConfirm}&dtEve={dtEve:yyyyMMddHHmmssfff}&dtCurr={dtCurr:yyyyMMddHHmmssfff}";
try
{
// invio chiamata URL x avvio PODL su macchina
string rawSplit = await callUrl(fullUrl, false);
fatto = (rawSplit != "KO") ? true : false;
}
catch
{ }
return fatto;
}
/// <summary>
/// URL del comando letto da conf in aggiunta al server MES da contattare
/// </summary>
protected string urlCommand(string cmqReq)
{
return $@"{urlMesServer}{IOBConfFull.MapoMes.ApiUrl(cmqReq)}";
}
/// <summary>
/// URL come urlCommand + aggiunta codice IOB da cui viene inviato
/// </summary>
protected string urlCommandIob(string cmqReq)
{
return $@"{urlMesServer}{IOBConfFull.MapoMes.ApiUrl(cmqReq)}{IOBConfFull.General.CodIOB}";
}
/// <summary>
/// URL come urlCommand + aggiunta codice IOB da file (= specifico x Reboot) da cui viene inviato
/// </summary>
protected string urlCommandIobFile(string cmqReq)
{
return $@"{urlMesServer}{IOBConfFull.MapoMes.ApiUrl(cmqReq)}{IOBConfFull.General.FilenameIOB}";
}
/// <summary>
/// URL per richiamo task da eseguire...
/// </summary>
protected string urlRemTask2ExeTav(string codTav)
{
return $@"{urlCommandIob("remTask2Exe")}|{codTav}?taskName=";
}
/// <summary>
/// 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 virtual bool valueOverDBand(string keyName, string actVal)
{
bool answ = true;
float oldVal = 0;
float newVal = 0;
// DEVE esserci un valore global deadband > 0...
if (GLOBAL_DBAND > 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) > GLOBAL_DBAND;
}
}
}
return answ;
}
/// <summary>
/// Recupera da conf durata del veto x FluxLog (default 60sec)
/// </summary>
/// <param name="codFlux"></param>
/// <returns></returns>
protected int vetoCodFluxDur(string codFlux)
{
int answ = 60;
if (memMap != null && memMap.mMapRead != null && memMap.mMapRead.Count > 0)
if (memMap.mMapRead.ContainsKey(codFlux))
{
answ = memMap.mMapRead[codFlux].period;
}
return answ;
}
#endregion Protected Methods
#region Private Fields
private static readonly JsonSerializerOptions options = new JsonSerializerOptions
{
ReferenceHandler = ReferenceHandler.IgnoreCycles,
WriteIndented = false // Optional: set to true for pretty-printing
};
/// <summary>
/// Oggetto logger della classe
/// </summary>
private static Logger _logger = LogManager.GetCurrentClassLogger();
/// <summary>
/// Accumulatore counter byte x inviare meno record in REDIS
/// </summary>
private long currByteCount = 0;
/// <summary>
/// Dizionario dei valori FluxLog filtrati
/// </summary>
private Dictionary<string, long> DictFiltFLog = new Dictionary<string, long>();
/// <summary>
/// Ultima data x statistiche daily x trackDynData
/// </summary>
private string LastDayCurr = "";
/// <summary>
/// Ultimo invio valore a server
/// </summary>
private DateTime lastSigVarSent = DateTime.Now;
/// <summary>
/// periodo minimo di veto invio dati qualora non siano variati
/// </summary>
private int lastSigVarVeto = 55;
private Dictionary<string, int> TrackDayStatsCount = new Dictionary<string, int>();
private Dictionary<string, int> TrackDetStatsCount = new Dictionary<string, int>();
private Dictionary<string, Dictionary<string, string>> TrackDetValsCount = new Dictionary<string, Dictionary<string, string>>();
private DateTime vetoCheckOdl = DateTime.Now;
/// <summary>
/// Valore veto al salvataggio di valori filtrati in FluxLog se NON superato limite chiamate
/// </summary>
private DateTime VetoFlushFiltFL = DateTime.Now.AddHours(1);
/// <summary>
/// Dizionario dei valori bloccati x evitare log eccessivo
/// </summary>
private Dictionary<string, DateTime> vetoLogError = new Dictionary<string, DateTime>();
/// <summary>
/// Periodo di veto log in minuti
/// </summary>
private int vetoPeriodMin = 15;
/// <summary>
/// Periodo Veto prima di eseguire ProcessAutoOdlAsync
/// </summary>
private DateTime VetoProcessAutoOdl = DateTime.Now;
/// <summary>
/// Variabile x vietare rilettura conf memoria (se è stato letto metto veto lungo ad 10 min x evitare loop in avvio)
/// </summary>
private DateTime vetoReloadConf = DateTime.Now.AddMinutes(-1);
/// <summary>
/// Dizionario dei valori bloccati x evitare log basato su veto temporale
/// </summary>
private Dictionary<string, DateTime> vetoSendFLog = new Dictionary<string, DateTime>();
/// <summary>
/// Ms di attesa x uscita processo (std)
/// </summary>
private int waitForExitMsec = 250;
#endregion Private Fields
#region Private Properties
/// <summary>
/// Boolean abilitazione coda eventi IN
/// </summary>
private bool qInEnabCurr { get; set; } = false;
/// <summary>
/// Redis key del dizionari valori currProdData persistiti
/// </summary>
private string rKeyCurrProdData
{
get => redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:CurrProdData");
}
/// <summary>
/// URL di base del server MES da contattare
/// </summary>
private string urlMesServer
{
get => $@"{IOBConfFull.MapoMes.Transport}://{IOBConfFull.MapoMes.IpAddr}";
}
#endregion Private Properties
#region Private Methods
/// <summary>
/// Aggiunge in setup memoria la checkCondition BIT "decodificata"
/// </summary>
/// <param name="ompKey">Chiave da aggiungere</param>
/// <param name="ompVal">Valore da decodificare e poi aggiungere</param>
private void addCheckConditionBit(string ompKey, string ompVal)
{
var newCond = new BitConditionCheck(ompKey, ompVal);
// cerco x aggiornare o aggiungere...
if (OptCheckCondBit.ContainsKey(ompKey))
{
OptCheckCondBit[ompKey] = newCond;
}
else
{
OptCheckCondBit.Add(ompKey, newCond);
}
}
/// <summary>
/// Aggiunge in setup memoria la checkCondition INT "decodificata"
/// </summary>
/// <param name="ompKey">Chiave da aggiungere</param>
/// <param name="ompVal">Valore da decodificare e poi aggiungere</param>
private void addCheckConditionInt(string ompKey, string ompVal)
{
var newCond = new IntConditionCheck(ompKey, ompVal);
// cerco x aggiornare o aggiungere...
if (OptCheckCondInt.ContainsKey(ompKey))
{
OptCheckCondInt[ompKey] = newCond;
}
else
{
OptCheckCondInt.Add(ompKey, newCond);
}
}
/// <summary>
/// Aggiunge in setup memoria l'oggetto tipo valore BIT (NON mutuamente esclusivo) da tradurre
/// </summary>
/// <param name="csvList">Elenco valori da aggiungere formato csv (es "a,b,c")</param>
private void addVal2TranslBit(string csvList)
{
var list2add = csvList.Split(',');
foreach (var memKey in list2add)
{
// cerco x aggiungere...
if (!OptVar2TranslBit.ContainsKey(memKey))
{
// init traduzione vuota
OptVar2TranslBit.Add(memKey, "");
}
}
}
/// <summary>
/// Aggiunge in setup memoria l'oggetto tipo valore INT (mutuamente esclusivo) da tradurre
/// </summary>
/// <param name="csvList">Elenco valori da aggiungere formato csv (es "a,b,c")</param>
private void addVal2TranslInt(string csvList)
{
var list2add = csvList.Split(',');
foreach (var memKey in list2add)
{
// cerco x aggiungere...
if (!OptVar2TranslInt.ContainsKey(memKey))
{
// init traduzione vuota
OptVar2TranslInt.Add(memKey, "");
}
}
}
/// <summary>
/// Verifica e se necessario comprime directory log...
/// </summary>
private void checkShrinkDir()
{
// comprimo x prima cosa la folder dell'IOB corrente... testo sia IOBConf che FilenameIOB
List<string> path2chk = new List<string>() { IOBConfFull.General.CodIOB, IOBConfFull.General.FilenameIOB, "MAIN" };
string fullPath = "";
foreach (var item in path2chk)
{
fullPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs", item);
try
{
baseUtils.shrinkDir(fullPath);
}
catch (Exception exc)
{
lgError($"Eccezione in checkShrinkDir{Environment.NewLine}{exc}");
}
}
}
/// <summary>
/// retituisce data-ora dell'ODL corrente
/// </summary>
/// <returns></returns>
private async Task<DateTime> currOdlStart()
{
DateTime inizioOdl = DateTime.Now;
string rawDataInizio = await callUrl(urlInizioOdlIob, false);
DateTime.TryParse(rawDataInizio, out inizioOdl);
return inizioOdl;
}
/// <summary>
/// Mostra i dati grezzi letti in esadecimale <paramref name="currDispData">Parametri da
/// aggiornare x display in form</paramref>
/// </summary>
private void displayRawData(ref newDisplayData currDispData)
{
// mostro update...
string newString = string.Format("{0:X}", B_input);
currDispData.newInData = newString;
}
/// <summary>
/// Accumula statistiche daily e di dettaglio x la chiave indicata
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
private void DoTrackDataCount(string key, string value)
{
if (TrackDayStatsCount.ContainsKey(key))
{
TrackDayStatsCount[key]++;
}
else
{
TrackDayStatsCount.Add(key, 1);
}
// dettaglio è insieme key_val
string detKey = $"{key}:{value}";
if (TrackDayStatsCount.ContainsKey(detKey))
{
TrackDayStatsCount[detKey]++;
}
else
{
TrackDayStatsCount.Add(detKey, 1);
}
// salvo anche dettaglio...
if (TrackDetValsCount.ContainsKey(key))
{
TrackDetValsCount[key].Add($"{DateTime.Now:yyyy-MM-dd HH:mm:ss}", value);
}
else
{
Dictionary<string, string> nDict = new Dictionary<string, string>();
nDict.Add($"{DateTime.Now:yyyy-MM-dd HH:mm:ss}", value);
TrackDetValsCount.Add(key, nDict);
}
}
/// <summary>
/// Salva le statistiche accumulate alla giornata di riferimento salvata e resetta contatori
/// </summary>
/// <param name="DayCurr"></param>
private void DoTrackDataSave()
{
string rKey = "";
// verifico se ho almeno num minimo di dati da tracciare...
if (TrackDayStatsCount.Count() > 0)
{
// cerco se ho qualcosa oltre soglia...
int numOver = TrackDayStatsCount.Where(x => x.Value > IOBConfFull.FluxLog.TrackDataThreshold).Count();
// se ho --> processo!
if (numOver > 0)
{
// scadenza: 1 mese
DateTime scadHash = DateTime.Today.AddMonths(1);
// salvo statistiche Day
rKey = $"{redisMan.redIobTrackKey}:DayStats:{LastDayCurr}";
foreach (var item in TrackDayStatsCount)
{
// traccio con scadenza 1 mese da oggi pareto chiamate giornaliere
redisMan.redIncrHashCount(rKey, item.Key, scadHash, item.Value);
}
// salvo statistiche Detail, scadenza 10 gg
scadHash = DateTime.Today.AddDays(10);
foreach (var item in TrackDetStatsCount)
{
// separo statistiche...
var kvp = item.Key.Split(':');
if (kvp.Count() > 1)
{
rKey = $"{redisMan.redIobTrackKey}:DetailStats:{LastDayCurr}:{kvp[0]}";
redisMan.redIncrHashCount(rKey, kvp[1], scadHash, item.Value);
}
}
// salvo dizionario valori dettaglio... scadenza 3 gg
scadHash = DateTime.Today.AddDays(3);
foreach (var item in TrackDetValsCount)
{
rKey = $"{redisMan.redIobTrackKey}:DataLog:{LastDayCurr}:{item.Key}";
redisMan.redSaveHashDict(rKey, item.Value, scadHash);
}
// reset dizionari
TrackDayStatsCount.Clear();
TrackDetStatsCount.Clear();
TrackDetValsCount.Clear();
}
}
// salvo variabile giorno di registrazione
LastDayCurr = $"{DateTime.Today:yyMMdd}";
}
/// <summary>
/// Archivia una cartella in un file zip
/// </summary>
/// <param name="folderPath">Cartelal da archiviare</param>
/// <param name="zipName">Nome del file zip da produrre...</param>
private bool doZipArchiveFolder(string folderPath, string zipName)
{
bool fatto = false;
if (Directory.Exists(folderPath))
{
fatto = fileMover.zippaDirectory(folderPath, zipName);
// se sent elimino vecchia directory...
if (fatto)
{
Directory.Delete(folderPath, true);
}
}
return fatto;
}
/// <summary>
/// Oggetto gestione connessioni REDIS
/// </summary>
private RedisService redisService;
private async Task<bool> ExecuteIobCheckWithRetry(int maxRetries)
{
var rand = new Random();
for (int i = 0; i <= maxRetries; i++)
{
try
{
if (i > 0)
{
// Al terzo tentativo fallito resetto i client
if (i == 3) resetWebClients();
int delay = i == 3 ? rand.Next(250, 1000) : rand.Next(250, 500);
await Task.Delay(delay);
}
string callResp = await callUrl(urlIobEnabled, i < 3); // true per i primi tentativi
if (callResp == "OK") return true;
}
catch (Exception exc)
{
lgError($"Eccez<ione in ExecuteIobCheckWithRetry{Environment.NewLine}{exc}");
}
}
return false;
}
/// <summary>
/// Esegue filtraggio dati x bit blinking!!!
/// </summary>
private void filterData()
{
// effettuo filtraggio dei valori letti... inizializzo OUT!
B_output = 0;
// in primis verifico SE ci siano bit blinkng... se non ci sono OUT=IN...
if (IOBConfFull.SignalProc.BlinkFilterMask == 0)
{
B_output = B_input;
}
else
{
// incomincio con i valori NON blinking: questi "passano invariati", inizio a
// sommare nel valore OUT...
B_output = B_input & ~IOBConfFull.SignalProc.BlinkFilterMask;
// calcolo il valore dei BIT che "passano la maschera"
int iBlink = B_input & IOBConfFull.SignalProc.BlinkFilterMask;
// ...aggiungo i "bit che passano"
B_output += iBlink;
// calcolo QUALI valori (tra quelli blink) siano PASSATI da 0 a 1 --> init counters...
BitArray bBlinkStart = new BitArray(new byte[] { Convert.ToByte(iBlink) });
int[] bitsUp = bBlinkStart.Cast<bool>().Select(bit => bit ? 1 : 0).ToArray();
for (int i = 0; i < bitsUp.Length; i++)
{
// SE 1... impostiamo contatori al MAX
if (bitsUp[i] == 1)
{
// se era zero indico START blink...
if (i_counters[i] == 0)
{
lgTrace("START BLINK: B{0}", i);
}
// imposto comunque contatore al cambio fronte...
i_counters[i] = IOBConfFull.SignalProc.BlinkMaxCounter;
}
}
// quelli che sono zero... LI RECUPERO E LI PROCESSO...
int iZero = ~B_input & IOBConfFull.SignalProc.BlinkFilterMask;
BitArray bBlinkEnd = new BitArray(new byte[] { Convert.ToByte(iZero) });
int[] bitsDown = bBlinkEnd.Cast<bool>().Select(bit => bit ? 1 : 0).ToArray();
for (int i = 0; i < bitsDown.Length; i++)
{
// se era a zero (invertito...)
if (bitsDown[i] == 1)
{
// SE è in corso il conteggio...
if (i_counters[i] > 0)
{
// decremento!
i_counters[i] -= 1;
// se è zero NON faccio nulla, altrimenti SOMMO...
if (i_counters[i] > 0)
{
B_output += 1 << i;
}
else
{
lgTrace("END BLINK: B{0}", i);
}
}
}
}
}
}
/// <summary>
/// Legge da conf il valore di demoltiplica lettura dynData (se presente) o ignora e pone a 1...
/// </summary>
private void fixDemFactDynData()
{
demFactDynData = IOBConfFull.FluxLog.DemFactDynData;
}
/// <summary>
/// Test ping all'indirizzo impostato nei parametri
/// </summary>
/// <returns></returns>
private IPStatus GetPingStatus()
{
var pStatus = Task.Run(async () => await GetPingStatusAsync(maxPingRetry + 1))
.GetAwaiter()
.GetResult();
return pStatus;
}
/// <summary>
/// Test ping all'indirizzo impostato nei parametri
/// </summary>
/// <param name="maxRetry">Numero max tentativi (maxPingRetry + 1)</param>
/// <returns></returns>
private async Task<IPStatus> GetPingStatusAsync(int maxAttempts)
{
// 1. Check disabilitazione
if (IOBConfFull.MapoMes.DisabPing) return IPStatus.Success;
// 2. Estrazione Host/IP pulita
string host = IOBConfFull.MapoMes.IpAddr;
try
{
// Rimuove protocollo (http://, ftp://, etc)
if (host.Contains("://"))
host = new Uri(host).Host;
else if (host.Contains(":"))
host = host.Split(':')[0];
}
catch { /* fallback al valore originale se Uri fallisce */ }
// 3. Risoluzione Indirizzo
if (!IPAddress.TryParse(host, out IPAddress address))
{
try
{
var addresses = Dns.GetHostAddresses(host);
if (addresses.Length > 0) address = addresses[0];
}
catch (Exception ex)
{
lgError($"Impossibile risolvere DNS per {host}: {ex.Message}");
return IPStatus.DestinationHostUnreachable;
}
}
if (address == null) return IPStatus.Unknown;
// 4. Ciclo di Ping con Retry
var rand = new Random();
using (Ping pingSender = new Ping())
{
for (int attempt = 1; attempt <= maxAttempts; attempt++)
{
try
{
// Timeout dinamico come nel tuo originale
int timeout = (attempt == 1)
? rand.Next(200, 400)
: (pingServerMsTimeout * attempt / 2);
PingReply reply = pingSender.Send(address, timeout);
if (reply.Status == IPStatus.Success)
{
if (attempt > 1) lgInfo("Server PING OK dopo retry!");
return IPStatus.Success;
}
lgInfo($"Ping tent. {attempt} fallito: {reply.Status} (Timeout: {timeout}ms)");
}
catch (Exception ex)
{
lgError($"Eccezione durante ping tent. {attempt}: {ex.Message}");
}
// Attesa prima del prossimo tentativo (se non è l'ultimo)
if (attempt < maxAttempts)
{
await Task.Delay(rand.Next(50, 200));
}
}
}
return IPStatus.TimedOut;
}
/// <summary>
/// Verifica se il log di un dato errore sia permesso
/// </summary>
/// <param name="logKey">ID del valore log da loggare/verificare</param>
/// <returns></returns>
private bool logValuePermit(string logKey)
{
bool doLog = false;
if (vetoLogError.ContainsKey(logKey))
{
// verifico se veto scaduto...
if (DateTime.Now > vetoLogError[logKey])
{
doLog = true;
vetoLogError[logKey] = DateTime.Now.AddMinutes(vetoPeriodMin);
}
}
else
{
doLog = true;
vetoLogError.Add(logKey, DateTime.Now.AddMinutes(vetoPeriodMin));
}
return doLog;
}
/// <summary>
/// Classe fittizia in caso di processing GLOBALE di tutto in 1 solo colpo...
/// </summary>
private void processAllMemory()
{
// verifico se sia abilitato processing...
if (queueInEnabCurr)
{
// init genObj display
newDisplayData currDispData = new newDisplayData();
// in primis SALVO valori previous/precedenti
B_previous = B_output;
// poi faccio lettura NUOVI valori
readAllData(ref currDispData);
// eseguo il filtering dei valori (per i bit "blinking")
filterData();
// effettuo confronto valori vecchi/nuovi... SE trovo variazione OPPURE se è passato
// + di un timeout di controllo...
DateTime adesso = DateTime.Now;
bool scaduto = Math.Abs(adesso.Subtract(lastSigVarSent).TotalSeconds) > lastSigVarVeto;
if (B_output != B_previous || (scaduto && !IOBConfFull.Device.DisabResendScaduto))
{
lgDebug($"processAllMemory | B_output: {B_output} | B_previous: {B_previous}");
accodaSigIN(ref currDispData);
lastSigVarSent = adesso;
}
else
{
lgTrace($"processAllMemory | scaduto: {scaduto} | lastSigVarSent: {lastSigVarSent} | B_output: {B_output} | B_previous: {B_previous}");
}
raiseRefresh(currDispData);
}
else
{
lgInfo($"VETO on processAllMemory | queueInEnabCurr = {queueInEnabCurr}");
checkVetoQueueIn();
}
}
private void processMem2Write()
{
// solo SE queue in enabled è true...
if (queueInEnabCurr)
{
DateTime adesso = DateTime.Now;
List<objItem> updatedPar = new List<objItem>();
List<objItem> currWritePar = new List<objItem>();
// ciclo tutti gli oggetti write x vedere se modificati...
foreach (var item in currProdData)
{
bool needWrite = false;
string lastVal = item.Value;
// li cerco su last... se non ci sono o modificati --> salvo da scrivere e copio
if (lastProdData.ContainsKey(item.Key))
{
lastVal = lastProdData[item.Key];
// verifico se variato...
if (!item.Value.Equals(lastProdData[item.Key]))
{
needWrite = true;
lastProdData[item.Key] = item.Value;
}
}
// aggiungo
else
{
needWrite = true;
lastProdData.Add(item.Key, item.Value);
}
if (memMap != null && memMap.mMapWrite.ContainsKey(item.Key))
{
// preparo genObj da scrivere
objItem newWrite = new objItem()
{
uid = item.Key,
name = item.Key,
description = memMap.mMapWrite[item.Key].description,
reqValue = needWrite ? item.Value : "",
value = lastVal, //item.Value,
lastRequest = adesso,
writable = true,
displOrdinal = memMap.mMapWrite[item.Key].displOrdinal
};
// se devo scrivere --> riporto
if (needWrite)
{
updatedPar.Add(newWrite);
}
else
{
currWritePar.Add(newWrite);
}
}
}
// se ho da scrivere... scrivo TUTTI!
if (updatedPar.Count > 0 && ENABLE_MEM_REWRITE)
{
// scrivo valore!
lgInfo($"Chiamata di plcWriteParams da processMem2Write: {updatedPar.Count} updatedPar");
plcWriteParams(ref updatedPar);
// invio su cloud parametri!
string rawData = JsonConvert.SerializeObject(updatedPar);
utils.CallUrlPost($"{urlUpdateWriteParams}", rawData);
lgInfo($"Notificato a server scrittura {updatedPar.Count} parametri");
}
else
{
// se scaduto tempo da ultimo invio... e ho valori
if (currWritePar.Count > 0 && (lastWriteParamsUpsert.AddMinutes(vetoSendWriteUpsert) < adesso))
{
// invio su cloud parametri!
string rawData = JsonConvert.SerializeObject(currWritePar);
var res = utils.CallUrlPost($"{urlUpdateWriteParams}", rawData);
lgInfo($"Reinviato a server stato {updatedPar.Count} parametri WRITE");
lastWriteParamsUpsert = adesso;
}
}
}
}
/// <summary>
/// Effettua gestioen programma: legge e mostra su display...
/// </summary>
private void processProgram()
{
currPrgName = "";
// se abilitata lettura prgName
if (enablePrgName)
{
if (connectionOk)
{
try
{
currPrgName = getPrgName();
}
catch (Exception exc)
{
lgError($"Eccezione in getPrgName{Environment.NewLine}{exc}");
}
}
else
{
lgError("Errore connessione mancante x getPrgName");
}
}
else
{
currPrgName = lastPrgName;
}
// verifico SE sia cambiato il programma...
if (lastPrgName != currPrgName)
{
// salvo!
lastPrgName = currPrgName;
string sVal = $"[PROG]{currPrgName}";
// chiamo accodamento...
bool sent = accodaFLog("PROG", sVal, qEncodeFLog("PROG", currPrgName));
if (sent)
{
// traccio valore DynData x analisi
trackDynData("PROG", currPrgName);
}
}
}
/// <summary>
/// Processo lettura dati sysinfo
/// </summary>
private void processSysInfo()
{
if (utils.CRB("enableSysInfo"))
{
Dictionary<string, string> currSysInfo = new Dictionary<string, string>();
if (connectionOk)
{
currSysInfo = getSysInfo();
}
else
{
lgError("Errore connessione mancante x getSysInfo");
}
// verifico SE sia cambiato il programma...
if (lastSysInfo != currSysInfo["SYSINFO"])
{
// salvo!
lastSysInfo = currSysInfo["SYSINFO"];
// per ogni valore del dizionario mostro ed accodo!
string sVal = "";
foreach (var item in currSysInfo)
{
// verifico NON sia un ND...
if (item.Key == "ND" || string.IsNullOrEmpty(item.Key))
{
// log anomalia...
lgTrace($"Errore in processSysInfo: item.key risulta ND! | item.key: {item.Key} | item.Value: {item.Value}");
}
else
{
sVal = string.Format("[SYSINFO]{0}|{1}", item.Key, item.Value);
// chiamo accodamento...
bool sent = accodaFLog(item.Key, sVal, qEncodeFLog(item.Key, item.Value));
if (sent)
{
// traccio valore DynData x analisi
trackDynData(item.Key, item.Value);
}
}
}
}
}
}
/// <summary>
/// Effettua calcolo dei consumi + esportazione in file richiesto
/// </summary>
/// <param name="folderPath">Folder dei file da processare</param>
/// <param name="reportPath">Percorso file out di export</param>
private bool RecipeDoConsumeReport(string folderPath, string reportPath)
{
// var di base
bool fatto = false;
string expMode = "csv";
List<ConsRec> ListConsDet = new List<ConsRec>();
List<ConsOut> ListConsSum = new List<ConsOut>();
// file conf x conversioni...
string confSetupPath = pathList["fullPath-confSetup"];
ConvSetup currConf = new ConvSetup();
bool addHeader = false;
int numDec = 4;
string codMag = "NA";
if (!string.IsNullOrEmpty(confSetupPath))
{
string rawConfFile = File.ReadAllText(confSetupPath);
if (!string.IsNullOrEmpty(rawConfFile))
{
currConf = JsonConvert.DeserializeObject<ConvSetup>(rawConfFile);
if (currConf != null)
{
addHeader = currConf.addHeader;
numDec = currConf.numDec;
codMag = currConf.codMag;
expMode = currConf.mode.ToLower();
}
}
}
// ciclo x ogni file ricevendone i consumi ed accumulandoli in lista...
var fileList = Directory.GetFiles(folderPath);
foreach (var cFile in fileList)
{
var consumiFile = RecipeGetCons(cFile);
ListConsDet.AddRange(consumiFile);
}
// se ho trovato dati
double ratioConv = 1;
if (ListConsDet.Count > 0)
{
var dirName = new DirectoryInfo(folderPath).Name;
// recupero elenco colori
var elencoColori = ListConsDet
.GroupBy(x => x.ColourCode)
.Select(grp => grp.Last())
.ToList();
// faccio le somme x colore
foreach (var colore in elencoColori)
{
ratioConv = 1;
// ricerca in conf (% di consumo)
if (currConf != null && currConf.convRatio != null)
{
if (currConf.convRatio.ContainsKey(colore.ColourCode))
{
ratioConv = currConf.convRatio[colore.ColourCode];
}
}
// calcolo peso equivalente in kg con conversione ratio
var totWeightKg = ListConsDet
.Where(x => x.ColourCode == colore.ColourCode)
.Sum(x => x.WeightGrTot) / 1000 * ratioConv;
// record finale trasformato in KG...
ConsOut colTotal = new ConsOut()
{
CodMag = codMag,
ColourCode = colore.ColourCode,
Description = colore.Description,
UM = "KG",
WeightKgProc = totWeightKg > 0.01 ? Math.Round(totWeightKg, numDec) : 0.01,
DtRif = colore.DtRif
};
ListConsSum.Add(colTotal);
}
// infine serializzo x output in base alla modalità richiesta
switch (expMode)
{
case "fixwidth":
fatto = DataExport.SaveFixedWidth(ListConsSum, $"{reportPath}.txt", currConf.fieldLength, currConf.fieldLPad, currConf.fieldNDec);
break;
case "csv":
default:
fatto = DataExport.SaveToCsv(ListConsSum, $"{reportPath}.csv", addHeader);
break;
}
}
return fatto;
}
/// <summary>
/// Processa la ricetta alla ricerca dei dati PODL x inviare chiamate ad MP-IO
/// </summary>
/// <param name="recipeFile">Path RIcetta</param>
private bool RecipeDoProcessPODL(string recipeFile)
{
bool answ = false;
// init var
int idxPOdl = 0;
string rawVal = "";
// leggo righe file!
var recipeRows = File.ReadAllLines(recipeFile);
// per ogni file ricevuto controlla se viene dal MES (cerca <Variant>PODL)
string tokenSearch = "<Variant>PODL";
// recupero riga PODL...
var rowPodl = recipeRows.Where(x => x.Contains(tokenSearch)).FirstOrDefault();
if (rowPodl != null)
{
DateTime dtStartPOdl = DateTime.Today;
int duration = 0;
DateTime dtEndPOdl = DateTime.Today.AddMinutes(10);
string dtEve = "";
string dtCurr = "";
// inizio ricerca PODL
rawVal = rowPodl.Trim().Replace(tokenSearch, "").Replace("</Variant>", "");
int.TryParse(rawVal, out idxPOdl);
if (idxPOdl > 0)
{
// leggo inizio commessa
tokenSearch = "<Date>";
var rowStart = recipeRows.Where(x => x.Contains(tokenSearch)).FirstOrDefault();
if (rowStart != null)
{
rawVal = rowStart.Trim().Replace(tokenSearch, "").Replace("</Date>", "");
DateTime.TryParse(rawVal, out dtStartPOdl);
}
// leggo durata commessa
tokenSearch = "<DosDuration>";
var rowDurSec = recipeRows.Where(x => x.Contains(tokenSearch)).FirstOrDefault();
if (rowDurSec != null)
{
rawVal = rowDurSec.Trim().Replace(tokenSearch, "").Replace("</DosDuration>", "");
int.TryParse(rawVal, out duration);
dtEndPOdl = dtStartPOdl.AddSeconds(duration);
}
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
// prova ad avviare/chiudere PODL relativo (eventualmente duplicandolo)
dtEve = $"{dtStartPOdl:yyyyMMddHHmmssfff}";
dtCurr = $"{DateTime.Now:yyyyMMddHHmmssfff}";
await callUrl($"{urlOdlStartFromPOdl}{idxPOdl}&dtEve={dtEve}&dtCurr={dtCurr}", false);
// ora chiamo chiusura...
dtEve = $"{dtEndPOdl:yyyyMMddHHmmssfff}";
dtCurr = $"{DateTime.Now:yyyyMMddHHmmssfff}";
await callUrl($"{urlPODLClose}{idxPOdl}&dtEve={dtEve}&dtCurr={dtCurr}", false);
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in RecipeDoProcessPODL.callUrl: {ex.Message}");
}
}
answ = true;
}
return answ;
}
/// <summary>
/// Elimino record da REDIS (locale e remoto)
/// </summary>
/// <param name="keyReq"></param>
private async Task RecipeRemoveWeekStatus(string keyReq)
{
string fullKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:WeekStats");
var okHashDict = redisMan.redRemoveHashField(fullKey, keyReq);
// rileggo status hash
Dictionary<string, string> currDict = redisMan.redGetHashDict(fullKey);
// invio ANCHE in MP-IO l'update delle info...
string remUrl = urlSetHashDict;
string dictPayload = JsonConvert.SerializeObject(currDict);
await callUrlWithPayloadAsync(remUrl, dictPayload, true);
//await callUrlWithPayloadAsync(remUrl, dictPayload, false);
}
private void reportDataProc()
{
// update valori visualizzazione...
parentForm.dataProcLabel = string.Format("RAW: {0} --> IN: {1} --> OUT: {2}", nReadIN, nReadFilt, nSendOut);
}
/// <summary>
/// Chiamate ritorno task eseguiti al server
/// </summary>
/// <param name="listaValori"></param>
/// <returns></returns>
private async Task SendTaskResult(List<string> listaValori)
{
foreach (var rawJob in listaValori)
{
// deserializzo...
JobTaskData jobTask = JsonConvert.DeserializeObject<JobTaskData>(rawJob);
// ora chiamo la cancellazione dei task eseguiti...
var taskDict = JobTaskData.TaskDict(jobTask.RawData);
foreach (var item in taskDict)
{
await remTask2exe(item.Key, item.Value, jobTask.CodTav);
}
}
}
/// <summary>
/// Effettua ciclo recupero richieste server
/// </summary>
private async Task ServerGetRequestsAsync()
{
// solo se NON sono disabilitati i Task2Exe...
if (!IOBConfFull.Device.DisabExeTask)
{
// se non ho veto task2exe
DateTime adesso = DateTime.Now;
if (adesso > dtVetoTask2Exe)
{
// blocco fisso a 5 sec x ora
dtVetoTask2Exe = adesso.AddSeconds(5);
// recupero elenco delle cose da fare
string resp = await getTask2exe("");
// se ho qualcosa --> creo obj e lo accodo...
if (!string.IsNullOrEmpty(resp) && resp.Length > 2)
{
accodaServReq("", resp);
if (isMulti)
{
foreach (var item in IOBConfFull.Device.MultiIobList)
{
resp = await getTask2exe(item);
accodaServReq(item, resp);
}
}
}
}
}
}
/// <summary>
/// Effettua ciclo invio rispsote (=esito esecuzione richieste) al server
/// </summary>
private async Task ServerPutRespAsync()
{
// verifico SE la coda abbia dei valori...
if (QueueSrvResp.Count > 0)
{
// invio pacchetto di dati (max da conf)
for (int i = 0; i < nMaxSend; i++)
{
// SE ho qualcosa in coda...
if (QueueSrvResp.Count > 0)
{
string currVal = "";
if (MPOnline)
{
if (IobOnline)
{
List<string> listaValori = new List<string>();
// se ho + di maxJsonData elementi --> invio un set di dati alla volta
if (QueueSrvResp.Count > maxJsonData)
{
// prendoi primi maxJsonDataValori
for (int j = 0; j < maxJsonData; j++)
{
QueueSrvResp.TryDequeue(out currVal);
listaValori.Add(currVal);
}
await SendTaskResult(listaValori);
}
else
{
// invio in blocco
listaValori = QueueSrvResp.ToList();
await SendTaskResult(listaValori);
// svuoto!
QueueSrvResp = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueServResp", IOBConfFull.General.EnabRedisQue, redisMan);
}
}
else
{
break;
}
}
else
{
break;
}
}
else
{
break;
}
}
}
}
/// <summary>
/// Imposto alcuni valori di default
/// </summary>
/// <param name="resetQueue">indica se sia richiesto di SVUOTARE le code delle info</param>
private void setDefaults(bool resetQueue)
{
numSim = utils.CRI("numSim");
lastPrgName = "";
nReadIN = 0;
nReadFilt = 0;
nSendOut = 0;
currMode = 0;
lastAlarm = "";
doStartMemDump = utils.CRB("doStartMemDump");
doSampleMemory = utils.CRB("doSampleMemory");
// fix url wait random...
urlRandWait = utils.CRI("urlRandWait");
// fix fattore demoltiplica dynData
fixDemFactDynData();
// svuoto code se richiesto
if (resetQueue)
{
string codIob = IOBConfFull.General.FilenameIOB;
bool useRedis = IOBConfFull.General.EnabRedisQue;
QueueIN.Dispose();
QueueIN = new DataQueue(codIob, "QueueIN", useRedis, redisMan);
QueueSrvResp.Dispose();
QueueSrvResp = new DataQueue(codIob, "QueueServResp", useRedis, redisMan);
// no coda redis
QueueAlarm = new DataQueue(codIob, "QueueAlarm", false, redisMan);
QueueFLog = new DataQueue(codIob, "QueueFLog", false, redisMan);
QueueMessages = new DataQueue(codIob, "QueueMessages", false, redisMan);
QueuePing = new DataQueue(codIob, "QueuePing", false, redisMan);
QueueRawTransf = new DataQueue(codIob, "QueueRawTransf", false, redisMan);
QueueSrvReq = new DataQueue(codIob, "QueueServReq", false, redisMan);
QueueULog = new DataQueue(codIob, "QueueULog", false, redisMan);
}
// imposto contatori blink a zero...
i_counters = new int[32];
lastPeriodicLog = DateTime.Now;
// fix parametri generali...
pzCountDelay = IOBConfFull.Device.PzCountDelay;
enablePrgName = IOBConfFull.Device.EnabProgName;
enablePzCountByApp = IOBConfFull.Device.EnabPzCount;
mem2trace = IOBConfFull.Special.BankConf != null ? IOBConfFull.Special.BankConf.Mem2Trace : "";
numArtCharTrim = IOBConfFull.Device.NumArtCharTrim;
// valore standard divieto accodamento segnali IN
vetoQueueIn = IOBConfFull.Device.StartupVetoQueueIN;
// fix slow data
enableSlowData = IOBConfFull.FluxLog.EnableSlowData;
ENABLE_MEM_REWRITE = IOBConfFull.Device.EnabMemRewrite;
GLOBAL_DBAND = IOBConfFull.FluxLog.GlobalDeadBand;
enabSendMachineConf = IOBConfFull.General.SenMachineConf;
resetAlarmOnStart = IOBConfFull.General.ResetAlarmOnStart;
disableOdl = IOBConfFull.Odl.DisableOdl;
}
private async Task SvuotaCodaContapezziAsync()
{
// verifico non sia impedita la gestione contapezzi (per evitare invio su macchine lente nel reset all'attrezzaggio)
if (DateTime.Now <= vetoQueuePzCount)
{
lgInfo($"Ciclo SvuotaCodaContapezziAsync DISABILITATO fino a {vetoQueuePzCount}");
}
else
{
// permetto al max 2 tentativi infruttuosi...
int maxTry = 2;
int oldContapezzi = contapezziIOB;
// SE HO gestione contapezzi...
if (!IOBConfFull.Device.EnabPzCount)
{
lgDebug($"Contapezzi disabilitato da configuraizone: IOBConfFull.Device.EnabPzCount: {IOBConfFull.Device.EnabPzCount}");
}
else
{
// se ho contapezzi OLTRE limite...
while ((MPOnline) && (contapezziPLC > contapezziIOB + minSendPzCountBlock))
{
lgInfo($"Ciclo SvuotaCodaContapezziAsync --> contapezziPLC: {contapezziPLC} | contapezziIOB: {contapezziIOB}");
if (!isMulti)
{
pzCntReload(true);
}
// provo invio
if (!isMulti)
{
trySendPzCountBlock();
}
// verifica per evitare loop infinito invio fallito
if (oldContapezzi == contapezziIOB)
{
maxTry--;
}
else
{
maxTry = 2;
oldContapezzi = contapezziIOB;
}
// verifico maxTry: se li ho esauriti esco!
if (maxTry <= 0)
{
return;
}
// aspetto x dare tempo calcolo
await Task.Delay(500);
}
}
}
}
/// <summary>
/// Processo la coda FLog...
/// </summary>
private async Task SvuotaCodaFLogAsync()
{
bool sendOnMachineOff = false;
// controllo se è passato oltre watchdog e non ho inviato nulla --> RE-INVIO (ultimo inviato)!!!!
if (DateTime.Now.Subtract(lastWatchDog).TotalSeconds > IOBConfFull.General.WatchDogSec)
{
string wdStatus = "elapsed";
string sVal = string.Format("[WDST]{0}", wdStatus);
// chiamo accodamento... SE non disabilitato..
if (!disableWdst)
{
bool sent = accodaFLog("WDST", sVal, qEncodeFLog("WDST", wdStatus));
if (sent)
{
// traccio valore DynData x analisi
trackDynData("WDST", wdStatus);
}
}
lastWatchDog = DateTime.Now;
sendOnMachineOff = true;
lgInfo("Impostato sendOnMachineOff a true");
}
// verifico SE la coda abbia dei valori...
if (QueueFLog.Count > 0)
{
// invio pacchetto di dati (max da conf)
for (int i = 0; i < nMaxSend; i++)
{
// SE ho qualcosa in coda...
if (QueueFLog.Count > 0)
{
string currVal = "";
if (MPOnline)
{
if (IobOnline || sendOnMachineOff)
{
// se ho + di 2 elementi in coda --> uso invio JSON in blocco...
if (QueueFLog.Count > 1)
{
List<string> listaValori = new List<string>();
// se ho + di maxJsonData elementi --> invio un set di dati alla volta
if (QueueFLog.Count > maxJsonData)
{
// prendoi primi maxJsonDataValori
for (int j = 0; j < maxJsonData; j++)
{
QueueFLog.TryDequeue(out currVal);
listaValori.Add(currVal);
}
await sendDataBlock(urlType.FLog, listaValori);
lastWatchDog = DateTime.Now;
}
else
{
// invio in blocco
listaValori = QueueFLog.ToList();
// invio
await sendDataBlock(urlType.FLog, listaValori);
// svuoto! NO redis
QueueFLog = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueFLog", false, redisMan);
//QueueFLog = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueFLog", IOBConfFull.General.EnabRedisQue, redisMan);
lastWatchDog = DateTime.Now;
}
}
else
{
// INVIO SINGOLO...!!!
QueueFLog.TryDequeue(out currVal);
await sendToMoonPro(urlType.FLog, currVal);
lastWatchDog = DateTime.Now;
}
}
else
{
break;
}
}
else
{
break;
}
}
else
{
break;
}
}
}
}
/// <summary>
/// Processo la coda RawTransf...
/// </summary>
private async Task<bool> SvuotaCodaRawTransfAsync(bool force = false)
{
bool fatto = false;
// verifico SE la coda abbia dei valori...
if (QueueRawTransf.Count > 0)
{
// invio pacchetto di dati (max da conf)
for (int i = 0; i < nMaxSend; i++)
{
// SE ho qualcosa in coda...
if (QueueRawTransf.Count > 0)
{
string currVal = "";
if (MPOnline || force)
{
if (IobOnline || force)
{
List<string> listaValori = new List<string>();
// se ho + di maxJsonData elementi --> invio un set di dati alla volta
if (QueueRawTransf.Count > maxJsonData)
{
// prendoi primi maxJsonDataValori
for (int j = 0; j < maxJsonData; j++)
{
QueueRawTransf.TryDequeue(out currVal);
listaValori.Add(currVal);
}
fatto = await sendDataBlock(urlType.RawTransf, listaValori, force);
}
else
{
// invio in blocco
listaValori = QueueRawTransf.ToList();
// invio
fatto = await sendDataBlock(urlType.RawTransf, listaValori, force);
if (fatto)
{
// svuoto se ha okReport!
QueueRawTransf = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueRawTransf", false, redisMan);
}
}
}
else
{
break;
}
}
else
{
break;
}
}
else
{
break;
}
}
}
return fatto;
}
/// <summary>
/// Processo la coda UserLog...
/// </summary>
private async Task SvuotaCodaULogAsync()
{
// verifico SE la coda abbia dei valori...
if (QueueULog.Count > 0)
{
// invio pacchetto di dati (max da conf)
for (int i = 0; i < nMaxSend; i++)
{
// SE ho qualcosa in coda...
if (QueueULog.Count > 0)
{
string currVal = "";
if (MPOnline)
{
if (IobOnline)
{
List<string> listaValori = new List<string>();
// se ho + di maxJsonData elementi --> invio un set di dati alla volta
if (QueueULog.Count > maxJsonData)
{
// prendoi primi maxJsonDataValori
for (int j = 0; j < maxJsonData; j++)
{
QueueULog.TryDequeue(out currVal);
listaValori.Add(currVal);
}
await sendDataBlock(urlType.ULog, listaValori);
}
else
{
// invio in blocco
listaValori = QueueULog.ToList();
// invio
await sendDataBlock(urlType.ULog, listaValori);
// svuoto!
QueueULog = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueULog", false, redisMan);
}
}
else
{
break;
}
}
else
{
break;
}
}
else
{
break;
}
}
}
}
private void UpdateIobState(bool newStatus)
{
// Log se lo stato è cambiato
DateTime adesso = DateTime.Now;
if (IobOnline != newStatus || adesso.Subtract(lastIobStatusDisplUpdate).TotalMilliseconds > (baseUtils.nextPauseSendMSec * 10))
{
lgInfo(newStatus ? "IOB ONLINE for server MP/IO" : "IOB OFFLINE for server MP/IO");
IobOnline = newStatus;
lastIobStatusDisplUpdate = adesso;
if (newStatus)
{
lastIobOnline = adesso;
parentForm.commSrvActive = 2; // Stato Online
}
else
{
parentForm.commSrvActive = 1; // Stato Degradato/Offline
}
}
// Imposto il veto per il prossimo controllo (se ConnOk + rapido sennò meno rapido)
var msWait = baseUtils.nextPauseSendMSec * (connectionOk ? 12 : 150);
dtVetoCheckIOB = adesso.AddMilliseconds(msWait);
}
#endregion Private Methods
}
}