using EgwProxy.Ftp;
using IOB_UT_NEXT;
using IOB_UT_NEXT.Config;
using IOB_UT_NEXT.Config.Mem;
using IOB_UT_NEXT.Objects;
using IOB_UT_NEXT.Services.Cache;
using IOB_UT_NEXT.Services.Core;
using IOB_UT_NEXT.Services.Data;
using IOB_UT_NEXT.Services.Files;
using IOB_UT_NEXT.Services.Machine;
using IOB_UT_NEXT.Services.Monitoring;
using IOB_UT_NEXT.Services.Networking;
using IOB_UT_NEXT.Services.Utility;
using MapoSDK;
using MathNet.Numerics.Statistics;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
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.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using System.Windows.Forms;
using static IOB_UT_NEXT.Config.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 Private Fields
private CommunicationService commService;
private MachineCommunicationService machineCommService;
#endregion Private Fields
#region Public Fields
public int numPzReqOdl = 0;
#endregion Public Fields
#region Public Constructors
///
/// inizializzo l'oggetto sulla form SULLA BASE DEL FILE DI CONFIGURAZIONE letto
///
/// Form chiamante
/// Configurazione (v 4.x)
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 communication services
commService = new CommunicationService(IobConfNew, redisMan);
machineCommService = new MachineCommunicationService(IobConfNew, tcMan, memMap);
// 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();
//lastProdData = new Dictionary(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
///
/// Evento Iob ha subito un refresh
///
public event EventHandler eh_refreshed;
#endregion Public Events
#region Public Properties
///
/// Salva verifica stato connessione OK con macchina (PLC/CNC)
///
///
public virtual bool connectionOk
{
get => _connOk || DemoIn;
set => _connOk = value;
}
///
/// Contatore x invio dati FluxLog
///
public int counterFLog { get; set; }
///
/// Contatore x invio dati RawTransf
///
public int counterRawTransf { get; set; }
///
/// Contatore x invio dati SignalIN
///
public int counterSigIN { get; set; }
///
/// Contatore x invio dati UserLog
///
public int counterULog { get; set; }
///
/// nome Programma corrente
///
public string currPrgName { get; set; }
///
/// Verifica se sia in modalità DEMO --> da tipo IOB SIMULA...
///
public bool DemoIn
{
get => IOBConfFull.General.IobType == tipoAdapter.SIMULA;
}
///
/// Dizionario contapezzi Macchina (valori da impianto) x macchine multi tavola/pallet
///
public Dictionary DictPzCountImp { get; set; } = new Dictionary();
///
/// Dizionario contapezzi MES (valori salvati su server) x macchine multi tavola/pallet
///
public Dictionary DictPzCountMes { get; set; } = new Dictionary();
///
/// Indica se la chiamata WDST dit racking watchdog sia disabilitata dall'invio nel FluxLog
///
public bool disableWdst { get; set; } = false;
///
/// Indica lo stato Online/Offline della IOB
///
public bool IobOnline
{
get
{
return utils.IOB_Online;
}
set
{
utils.IOB_Online = value;
}
}
///
/// Verifica se sia macchina multi = DoppioPallet da CONF
///
public bool isMulti
{
get => IOBConfFull.Device.IsMulti;
}
///
/// Log verboso da configurazione (SOLO CHIAVE "verbose"...)
///
public bool isVerboseLog { get; set; } = utils.CRB("verbose");
///
/// Ultimo Alarm letto
///
public string lastAlarm { get; set; }
///
/// Ultimo ARRAY DynData letto
///
public Dictionary lastDynData { get; set; } = new Dictionary();
///
/// Ultimo DynData (sunto) letto
///
public string lastDynDataCtrlVal { get; set; }
///
/// Ultimo Override set letto
///
public string lastOverrideFS { get; set; }
///
/// Ultimo Override set letto
///
public string lastOverrideRapid { get; set; }
///
/// Ultimo programma letto
///
public string lastPrgName { get; set; }
///
/// Ultimo SysInfo letto
///
public string lastSysInfo { get; set; }
///
/// Ultimo URL
///
public string lastUrl { get; set; }
///
/// Valore massimo accettato x incremento pezzi letti dal PLC (valore moltiplicato per
/// 100... 200% --> 200)
///
public int maxPzDeltaPerc => IOBConfFull.Counters.MaxIncrPzCountPerc;
///
/// Verifica SE si debba fare log periodico (ogni "verboseLogTOut" sec...)
///
public bool periodicLog
{
get
{
bool answ = false;
answ = (DateTime.Now.Subtract(lastPeriodicLog).TotalSeconds > utils.CRI("verboseLogTOut"));
if (answ)
{
lastPeriodicLog = DateTime.Now;
}
return answ;
}
}
#if false
///
/// Valore medio del TC rilevato x verifica derive sul delta variazione contapezzi
///
public double plcAvgTc => tcMan.avgTC > 0 ? tcMan.avgTC : 1;
///
/// DataOra dell'ultima lettura variabile contapezzi da CNC
///
public DateTime plcLastPzRead => tcMan.lastObservedData;
#endif
///
/// Contapezzi attuale
///
public Int32 contapezziIOB
{
get => machineCommService.ContapezziIOB;
set => machineCommService.ContapezziIOB = value;
}
///
/// Ultima lettura variabile contapezzi da CNC
///
public Int32 contapezziPLC
{
get => machineCommService.ContapezziPLC;
set => machineCommService.ContapezziPLC = value;
}
///
/// Valore medio del TC rilevato x verifica derive sul delta variazione contapezzi
///
public double plcAvgTc => machineCommService.GetAverageTc();
///
/// DataOra dell'ultima lettura variabile contapezzi da CNC
///
public DateTime plcLastPzRead => machineCommService.GetLastObservedData();
///
/// Determina se il contapezzi plc sia valido (lo è se data avvio adapter è prima di ultimo dato registrato in contapezzi)
///
public bool plcPzCountValid => machineCommService.GetLastObservedData() > dtAvvioAdp;
///
/// Abilitazione coda segnali ingresso
///
public bool queueInEnabCurr
{
get => qInEnabCurr;
set
{
qInEnabCurr = value;
lgInfo($"SET queueInEnabCurr: {value} | {DateTime.Now:HHmmss}");
}
}
///
/// Finestra dei byte da mostrare di default x il RawDataInput
///
public int RawDataInputSize { get; set; } = 8;
///
/// Indice di partenza della memoria RawData Input da mostrare
///
public int RawDataInputStart { get; set; } = 0;
///
/// URL per segnalazione reboot...
///
public string urlReboot => $@"{urlCommandIobFile("sendReboot")}?mac={NetService.GetMACAddress()}";
///
/// URL per salvataggio dati conf YAML completi IOB...
///
public string urlSaveConfYaml => $@"{urlCommandIobFile("saveConfYaml")}";
///
/// Verifica SE si debba fare log verboso (verboso + ogni tot letture IN)
///
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
///
/// Conversione in un dizionario di tipo string/string serializzando e deserializzando
///
///
///
public static Dictionary ConvertToStringDict(Dictionary input)
{
return IOB_UT_NEXT.Services.Data.DataSerializer.ToDictionary(input);
}
///
/// Accumula in coda i valori ALARM e logga...
///
/// VALORE RAW (x display)
/// VALORE già processato con qEncodeFLog(...)
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;
}
}
///
/// Accumula in coda i valori Flux Log e logga. Restituisce true se inviato (x track su REDIS)
///
/// Nome del flusso da inviare (per verifica veto invio)
/// VALORE RAW (x display)
/// VALORE già processato con qEncodeFLog(...)
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;
}
///
/// Accoda (visualizzando in cima allo stack) la nuova stringa di output per area OTHER DATA
///
///
public void accodaOtherData(string newLine)
{
// inserisco in cima allo stack
parentForm.WriteTextSafe(newLine);
}
///
/// Accumula in coda i valori RawData + log
///
///
///
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 = JsonSerialize(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;
}
}
///
/// Accumula in coda i valori Signal IN e logga...
/// Parametri da aggiornare x display in form
///
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}");
}
///
/// Accumula in coda i valori USER LOG e logga...
///
/// VALORE RAW (x display)
/// VALORE già processato con qEncodeULog(...)
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;
}
}
///
/// Verifica se la IOB sia ENABLED (da server o Demo)
///
public async Task 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;
}
///
/// Verifica veto invio per una chiave specifica in Redis
///
///
///
public bool CheckSendVeto(string keyReq, TimeSpan vetoReq)
{
DateTime adesso = DateTime.Now;
DateTime lastSend = LastSendGet(keyReq);
bool sendEnab = lastSend.Add(vetoReq) <= adesso;
return sendEnab;
}
///
/// Verifica se il server sia ALIVE (tramite PING)
///
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;
}
///
/// Verifica se il server sia ALIVE (tramite PING)
///
public async Task 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;
}
///
/// Verifica veto coda QueueIN ed aggiorna abilitazione su variabile
///
public void checkVetoQueueIn()
{
queueInEnabCurr = dtVetoQueueIN < DateTime.Now;
}
///
/// Update visualizzaizone BIT in ingresso Parametri da
/// aggiornare x display in form
///
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;
}
}
///
/// Mostra cosa ha/avrebbe inviato
///
///
public void displayOtherData(string newData)
{
// mostro update...
accodaOtherData(newData);
}
///
/// Effettua i task di comunicazione IN/OUT con la macchina
///
///
///
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);
}
///
/// Effettua i task di comunicazione IN/OUT con il server MAPO
///
///
///
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();
}
}
raiseRefresh(currDispData);
}
public void ExecServerRequests()
{
// verifica se ci siano richieste da eseguire
if (QueueSrvReq.Count > 0)
{
if (MPOnline)
{
if (IobOnline)
{
// prendo elenco
List listaValori = QueueSrvReq.ToList();
foreach (var rawJob in listaValori)
{
// deserializzo...
JobTaskData jobTaskReq = JsonDeserialize(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 = JsonSerialize(taskDone);
accodaServResp(jobTaskReq.CodTav, serVal);
}
}
// svuoto!
QueueSrvReq = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueServResp", IOBConfFull.General.EnabRedisQue, redisMan);
}
}
}
}
///
/// Esecuzione dei task richiesti e pulizia coda richieste eseguite
///
/// Elenco task da eseguire
/// Codice TAV (per macchine multi pallet) - opzionale
public virtual Dictionary executeTasks(Dictionary 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 taskDone = new Dictionary();
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}";
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;
}
///
/// Cerca parametri opzionali in modalità "like" del nome
///
///
///
public Dictionary findOptPar(string keyStartSearch = "")
{
Dictionary answ = new Dictionary();
// 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;
}
///
/// forza reset ODL
///
public void forceResetOdl()
{
lgInfo("Registrato richiesta forzatura reset ODL");
pzCountResetted = true;
}
///
/// Effettua chiamata x split ODL
///
///
public async Task 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 HttpService.CallUrlAsync(urlForceSplit);
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 HttpService.CallUrlAsync(fullUrl);
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;
}
///
/// Processing degli allarmi (se presenti e gestiti)
///
///
public virtual Dictionary getAlarmData()
{
Dictionary outVal = new Dictionary();
// 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;
}
///
/// Effettua conversione da valore bitmap ai valori configurati x allarme
///
/// Configurazione banco allarmi
/// BitState allarmi
///
public string getAlarmState(BaseAlarmConf memConf, byte[] bState)
{
string valTransl = "";
List descAttivi = new List();
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;
}
///
/// Effettua conversione tra gli allarmi attivi secondo configurazione banco allarmi
///
/// Configurazione banco allarmi
/// num allarmi attivi
///
public virtual string getAlarmState(BaseAlarmConf memConf, int numAlarms)
{
string valTransl = "";
List descAttivi = new List();
// FAKE!!!: dovrebbe cercare in aree memoria x ogni valore configurato, vedere
// implementazione specifica es OpcUa
// combino string
valTransl = string.Join(",", descAttivi);
return valTransl;
}
///
/// Effettua conversione da valore bitmap ai valori configurati
///
///
///
///
public string getBitmapState(dataConfTSVC memConf, byte[] bState)
{
string valTransl = "";
List descAttivi = new List();
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;
}
///
/// Recupera eventuali allarmi CNC...
///
public virtual Dictionary getCncAlarms()
{
Dictionary outVal = new Dictionary();
return outVal;
}
///
/// Restituisce info DINAMICHE
///
///
public virtual Dictionary getDynData()
{
Dictionary outVal = new Dictionary();
return outVal;
}
///
/// Cerca se esiste il parametro opzionale nei KVP (JSON) e lo restituisce
///
///
///
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;
}
///
/// Cerca se esiste il parametro opzionale e lo restituisce
///
///
///
public string getOptPar(string key)
{
return IOBConfFull.OptParGet(key);
}
///
/// Cerca se esiste un link tra aree di memoria in write e lo restituisce
///
///
///
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;
}
///
/// Restituisce info OVERRIDES
///
///
public virtual Dictionary getOverrides()
{
Dictionary outVal = new Dictionary();
return outVal;
}
///
/// Restituisce programma in esecuzione
///
public virtual string getPrgName()
{
return "";
}
///
/// Restituisce info sistema
///
///
public virtual Dictionary getSysInfo()
{
Dictionary outVal = new Dictionary();
return outVal;
}
///
/// Recupera la VC x TS, svuotando lista e resettando periodo partenza
///
/// Nome della VC
/// Reimposta e resetta array VC
///
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();
TSVC_Data[VCName].DTStart = DateTime.Now;
}
}
return answ;
}
///
/// Recupera la VC x TS, svuotando lista e resettando periodo partenza
///
/// Nome della VC
/// Reimposta e resetta array VC
///
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();
TSVC_Data[VCName].DTStart = DateTime.Now;
}
}
return answ;
}
///
/// Area init asincrono (fare override async!o)
///
///
public virtual Task InitializeAsync()
{
// da usare per implementare logiche di init specifiche
return Task.CompletedTask;
}
///
/// Restituisce un payload in formato json della lista di valori ricevuta
///
/// Tipo di URL (eventi / FLog)
/// elenco di valori da coda string salvata
///
public string jsonPayload(urlType tipoUrl, List 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();
// 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 = JsonSerialize(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();
// 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 = JsonSerialize(fullEvObj);
}
catch (Exception exc)
{
lgError($"SignIN Errore in costruzione jsonPayload:{Environment.NewLine}{exc}");
}
break;
case urlType.RawTransf:
BaseRawTransf currRTData = new BaseRawTransf();
// 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();
// 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 = JsonSerialize(fullUlObj);
}
catch (Exception exc)
{
lgError($"ULog Errore in costruzione jsonPayload:{Environment.NewLine}{exc}");
}
break;
default:
break;
}
}
return answ;
}
///
/// Restitusice valore ultimo invio di una chiave, se non esistesse fornisce valore di 1 gg prima e lo salva...
///
///
///
public DateTime LastSendGet(string keyReq)
{
DateTime lastSend = DateTime.Now.AddDays(-1);
string lastSendKey = GetStatusField("LastSend");
string rawVal = redisMan.redGetHashField(lastSendKey, keyReq);
if (!string.IsNullOrEmpty(rawVal))
{
lastSend = JsonDeserialize(rawVal);
}
else
{
rawVal = JsonSerialize(lastSend);
KeyValuePair[] hashFields = new KeyValuePair[1];
hashFields[0] = new KeyValuePair(keyReq, rawVal);
redisMan.redSaveHash(lastSendKey, hashFields);
}
return lastSend;
}
///
/// Imposta valore datetime x ultimo invio di una chiamata in REDIS HashSet
///
///
///
public bool LastSendSet(string keyReq, DateTime dtRif)
{
string lastSendKey = GetStatusField("LastSend");
string rawVal = JsonSerialize(dtRif);
KeyValuePair[] hashFields = new KeyValuePair[1];
hashFields[0] = new KeyValuePair(keyReq, rawVal);
bool fatto = redisMan.redSaveHash(lastSendKey, hashFields);
return fatto;
}
///
/// Effettua un trim della stringa al numero max di linee da mostrare a video
///
///
///
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;
}
///
/// riporta il log di tutti i dati di results temporali registrati
///
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));
}
}
///
/// Processa un monitoredItem, e ritorna boolean SE richiede invio (cambio o scadenza)
///
///
///
///
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;
}
///
/// Verifica e processing x gestione Dossier quotidiani automatica
///
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 = HttpService.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}");
}
}
///
/// Wrapper AutoOdl in modalità Sync
///
public void ProcessAutoOdl()
{
try
{
Task.Run(async () => await ProcessAutoOdlAsync())
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError("ProcessAutoOdl | Crash nel ponte Sync/Async: " + ex.Message);
}
}
///
/// Verifica e processing x gestione ODL automatica
///
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 = HttpService.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 = HttpService.CallUrl(urlFixDailyOdlConfPzCount);
}
else
{
// prendo il + vecchio...
foreach (var item in IOBConfFull.Device.MultiIobList)
{
fullUrl = $@"{urlCommand("fixDailyOdlConfPzCount")}{item}";
autoOdlRes = await HttpService.CallUrlAsync(fullUrl);
}
}
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 HttpService.CallUrlAsync(urlInizioOdlIob);
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 HttpService.CallUrlAsync(fullUrl);
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 HttpService.CallUrlAsync(urlIdleTime);
int.TryParse(rawIdle, out idlePeriod);
}
else
{
int tmpIdle = 0;
// prendo il + grande...
foreach (var item in IOBConfFull.Device.MultiIobList)
{
fullUrl = $"{urlIdleTime}|{item}";
rawIdle = await HttpService.CallUrlAsync(fullUrl);
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 HttpService.CallUrlAsync(urlGetNumPzCurrODL);
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();
}
}
///
/// Effettua processing degli allarmi CNC SE disponibili
///
public void processCncAlarms()
{
if (utils.CRB("enableAlarms"))
{
Dictionary currAlarms = new Dictionary();
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}");
}
}
}
}
///
/// Effettua processing contapezzi (ed eventualmente alza il bit di contapezzo...)
///
public virtual void processContapezzi()
{ }
///
/// Effettua processing CUSTOM x l'IOB corrente (ed invia ad IO) (task svolto tipicamente
/// ogni 5 sec se base timer 10ms, vedere app.config)
///
public virtual void processCustomTaskLF()
{ }
///
/// Effettua processing CUSTOM x l'IOB corrente (ed invia ad IO) (task svolto tipicamente
/// ogni 3 sec se base timer 10ms, vedere app.config)
///
public virtual void processCustomTaskMF()
{ }
///
/// Task periodici SE disconnesso
///
public virtual void processDisconnectedTask()
{
}
///
/// Effettua processing del recupero dei valori dinamici:
/// es: speed (RPM, feedrate) degli assi, valori pressioni, temeprature
///
public void processDynData()
{
// FixMe Todo: generalizzare parametri nell'obj?
bool enableByApp = utils.CRB("enableDynData");
Dictionary currDynData = new Dictionary();
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");
}
}
}
}
///
/// Processa gestione memoria x invio dati QUANDO IOB è disconnesso da CNC...
///
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);
}
///
/// Effettua processing mode/status (EDIT/MDI/...)
///
public virtual void processMode()
{ }
///
/// Effettua processing ALTRI contatori/parametri (ed invia ad IO)
///
public virtual async Task processOtherCounters()
{
await Task.Delay(1);
}
///
/// Effettua processing del recupero delle OVERRIDE (spindle, feedrate, rapid)
///
public virtual void processOverride()
{
bool enableByApp = utils.CRB("enableOverrides");
Dictionary currOverride = new Dictionary();
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);
}
}
}
}
}
}
}
}
///
/// Processa esecuzione task ricevuti
///
///
///
/// Restituisce elenco task svolti --> per accodamento risposta
public Dictionary ProcessTask(Dictionary task2exe, string codTav)
{
Dictionary taskDone = new Dictionary();
Dictionary task2Add = new Dictionary();
// 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;
}
///
/// Classe fittizia in caso di processing task in MsVHF
///
public virtual void processVHF()
{
}
///
/// Classe fittizia in caso di processing watchdog data
///
public virtual void processWhatchDog()
{
}
///
/// Effettua rilettura del contapezzi dal server MP/IO
///
/// Forza rilettura da DB tempi ciclo rilevati
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 = HttpService.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 = HttpService.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 = HttpService.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");
}
}
}
}
///
/// Fornisce il valore di flusso e valore in formato valido x messa in coda nel formato
/// dtEve#flusso#valore#cont Flusso datiValore da salvare
///
public string qEncodeFLog(string flusso, string valore)
{
string answ = "";
// solo se valore !="", su DynData...
if (flusso != "DYNDATA" || !string.IsNullOrEmpty(valore))
{
try
{
answ = $"{DateTime.Now:yyyyMMddHHmmssfff}#{flusso}#{valore}#{counterULog}";
}
catch
{ }
}
return answ;
}
///
/// Fornisce il valore di flusso e valore in formato valido x messa in coda nel formato
/// dtEve#flux#valReq#cont DataOra evento
/// registratoFlusso datiValore da salvare
///
public string qEncodeFLog(DateTime eventDT, string flusso, string valore)
{
string answ = "";
try
{
answ = $"{eventDT:yyyyMMddHHmmssfff}#{flusso}#{valore}#{counterFLog}";
}
catch
{ }
return answ;
}
///
/// Fornisce il valore di UserLog e valore in formato valido x messa in coda nel formato:
/// dtEve#flusso#valReq#cont#matrOpr#label#valNum Flusso dati
/// (RC/RS/DI)Valore da inviare
/// (valStringMatricola operatoreValore etichetta: causale scarto / tagCodeValore numerico: esitoOk (0/1) / nuo scarti
///
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;
}
///
/// Effettua lettura dati
/// Parametri da aggiornare x display in form
///
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);
}
///
/// Effettua lettura semafori principale Parametri da
/// aggiornare x display in form
///
public virtual void readSemafori(ref newDisplayData currDispData)
{
lastReadPLC = DateTime.Now;
}
///
/// Aggiunge ai dati da inviare alla parentform i valori di RawInput rilevati (32bit)
/// 2021.08.27: filtrati secondo i valori setup start/size RawDataInput
///
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
{ }
}
///
/// Metodo generico di reset contapezzi...
///
///
public virtual bool resetContapezziPLC(string codTav)
{
lgInfo("Generic.resetContapezziPLC");
return false;
}
///
/// Effettua salvataggio in LUT del valore ricevuto (valori string)
/// - non serve calcolo medie o altro
/// - accoda in out val e basta
///
/// Array eventi da popolare
/// valore da salvare
/// ID/chiave di riferimento
///
public virtual void saveAlarmString(ref Dictionary outVal, string valore, string chiave)
{
DateTime adesso = DateTime.Now;
//check obj preliminare
if (outVal == null)
{
outVal = new Dictionary();
}
// 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;
}
}
///
/// metodo dummy x salvataggio aree memoria conf x CN
///
/// tipo di DUMP
public virtual void saveMemDump(dumpType tipo)
{
}
///
/// Effettua salvataggio in LUT del valore ricevuto (valori numerici)
///
///
///
///
///
public virtual void saveValue(ref Dictionary outVal, string chiave, double valore)
{
//check obj preliminare
if (outVal == null)
{
outVal = new Dictionary();
}
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}");
}
}
///
/// Effettua salvataggio in LUT del valore ricevuto (valori string)
/// - non serve calcolo medie o altro
/// - accoda in out val e basta
///
/// Array eventi da popolare
/// ID/chiave di riferimento
/// valore da salvare
///
public virtual void saveValueString(ref Dictionary outVal, string chiave, string valore)
{
DateTime adesso = DateTime.Now;
//check obj preliminare
if (outVal == null)
{
outVal = new Dictionary();
}
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;
}
}
///
/// Invio la variazione dello stato allarmi (se avvenuta)
///
/// COD memoria allarmi
/// Indice memoria allarmi
/// ultimo stato rilevato
/// stato corrente
/// Lista allarmi validi per l'area
///
public bool sendAlarmVariations(string memAddr, int index, uint lastStatus, uint currStatus, List AlarmList)
{
bool fatto = false;
if (lastStatus != currStatus)
{
List ActiveAlarmList = new List();
// 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 = JsonSerialize(ActiveAlarmList);
string resp = HttpService.CallUrlPost(lastUrl, rawData);
//string resp = HttpService.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;
}
///
/// Invia una LISTA di valori
///
///
///
public async Task sendDataBlock(urlType tipoUrl, List 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 HttpService.CallUrlAsync(lastUrl, payload);
// 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;
}
///
/// Effettua invio a MoonPro del valore richiesto
///
///
///
/// Valore da trasmettere: es
/// INPUT: lo status rilevato in HEX
/// FLog: il valore da trasmettere per il flusso indicato
///
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 HttpService.CallUrlAsync(lastUrl);
// 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");
}
}
///
/// Metodo generico di IMPOSTAZIONE FORZATA del contapezzi...
///
/// Pezzi richiesti
///
public virtual bool setcontapezziPLC(int newPzCount, string codTav)
{
return false;
}
///
/// Effettua impostazione del conteggio pezzi richiesti
///
///
public virtual bool setPzComm(int pzReq)
{
return false;
}
///
/// Processing di Variabili Campionarie x TimeSeries, accoda valori x la VC (se esiste) e
/// restituisce bool val se SCADUTO periodo controllo
///
/// Nome della VC
/// Valore (nuovo) delal VC
///
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;
}
///
/// Avvia l'adapter sulla porta richiesta
///
/// indica se sia richiesto di SVUOTARE le code delle info
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}");
}
///
/// Ferma l'adapter...
///
///
/// indica se si debba tentare di riavviare l'adapter (con caduta connessione viene fermato
/// in automatico)
///
/// indica se sia richiesto di SVUOTARE le code delle info
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 listaValori = new List();
// 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 listaValori = new List();
// 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 = GetCallStatsKey();
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;
}
///
/// Processo la coda SignalIN...
///
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 listaValori = new List();
// 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;
}
}
}
}
///
/// Metodo base connessione...
///
public virtual void tryConnect()
{
dtAvvioAdp = DateTime.Now;
queueInEnabCurr = true;
}
///
/// Metodo base disconnessione...
///
public virtual void tryDisconnect()
{
queueInEnabCurr = false;
}
///
/// Inserimento/aggiornamento chiavi/valore in currProdData + cache REDIS
///
///
///
/// True se modificato/inserito, false se INVARIATO
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;
}
///
/// Inserimento/aggiornamento chiavi/valore in lastProdData
///
///
///
/// True se modificato/inserito, false se INVARIATO
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;
}
///
/// Formatta URL x invio in DataBlock Json dei dati FLog / eventi
///
///
///
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;
}
///
/// Fornisce URL di tipo FluxLog
///
/// valore salvato in coda nel formato dtEve#flux#valore#counter
///
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;
}
///
/// URL per recupero dati ODL alla data...
///
public string urlGetOdlAtDate(DateTime dtRif)
{
string answ = $@"{urlCommandIob("getOdlAtDate")}?dateRif={dtRif:yyyyMMdd}";
return answ;
}
///
/// Fornisce URL INPUT per i parametri richiesti
///
/// valore salvato in coda formato dtEve#valore#counter
///
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;
}
///
/// Fornisce URL di tipo UserLog
///
/// valore salvato in coda nel formato dtEve#flux#valore#counter
///
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
///
/// Test ping + api al server in modalità Async
///
///
///
private async Task 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)
{
await Task.Delay(rand.Next(150, 500));
}
string resp = await HttpService.CallUrlAsync(urlAlive);
if (resp == "OK") return true;
}
catch (Exception ex)
{
if (i == 0) lgError($"Errore API Check: {ex.Message}");
}
}
return false;
}
///
/// Update stato server
///
///
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
///
/// Variabile numero errori vari (in lettura) --> se supera soglia maxErroriCheck --> disconnette
///
protected static int numErroriCheck = 0;
protected bool _connOk = false;
///
/// valore booleano di check se sia in fase di COMUNICAZIONE ATTIVA con il PLC/NC
///
protected bool adpCommAct;
///
/// porta x adapter (x restart)
///
protected int adpPortNum;
///
/// DataOra ultimo avvio adapter x watchdog
///
protected DateTime adpStartRun;
///
/// Vettore 32 BIT valori in ingresso al filtro
///
protected int B_input;
///
/// Vettore 32 BIT valori in uscita dal filtro
///
protected int B_output;
///
/// Vettore 32 BIT valori precedenti
///
protected int B_previous = -1;
///
/// Cod grupo IOB x creazione PODL al volo
///
protected string CodGruppoIob = "ND-00";
///
/// Num errori check alive
///
protected int currAliveErrors = 0;
///
/// Dizionario valori impostati x produzione
///
protected Dictionary currProdData = new Dictionary();
///
/// num corrente errori read PLC
///
protected int currReadErrors = 0;
///
/// num errori send
///
protected int currSendErrors = 0;
///
/// Tempo di attesa in minuti x lettura contapezzi standard (da .ini / OptPar)
///
protected double delayMinReadPzCount = 0;
///
/// Fattore di demoltiplicazione dei DynData x ridurre campionamento
///
protected int demFactDynData = 1;
///
/// Boolean x indicare contapezzi disabilitato forzatamente da IOB
///
protected bool disablePzCountByIob = false;
///
/// Veto x esecuzione task AutoDossier
///
protected DateTime dtVetoAutoDossier = DateTime.Now;
///
/// Veto x esecuzione Task2Exe troppo frequenti
///
protected DateTime dtVetoTask2Exe = DateTime.Now;
///
/// Veto x lettura contapezzi (in caso di autoODL con attesa reset ad esempio...)
///
protected DateTime dtVetoReadPzCount = DateTime.Now;
///
/// Determina se sia prevista gestione PODL (creazione/avvio/chiusura) come Soitaab
///
protected bool EnabelPodlManFull = false;
///
/// Abilita riscrittura memoria se trova differenze lettura/richiesti
///
protected bool ENABLE_MEM_REWRITE = true;
///
/// Abilitazione restart (da opt par...)
///
protected bool enableCliRestart = false;
///
/// Abilitazione invio dataitem
///
protected bool enableSendDataItem = true;
protected int minVetoSendDataItem = 60;
///
/// DataOra x veto all'invio dataItem
///
protected DateTime dtVetoSenDataItem = DateTime.Now;
///
/// Boolean x indicare contapezzi abilitato a livello di conf applicazione
///
protected bool enablePzCountByApp = true;
///
/// Abilitazione gestione slow data
///
protected bool enableSlowData = false;
///
/// dizionario (opzionale) xz decodifica file da importare
///
protected Dictionary FileDecod = new Dictionary();
///
/// DeadBand x riduzione dati FluxLog (se 0 non gestita)
///
protected double fluxLogRedDeadBand = 0;
///
/// Determina se sia gestita riduzione dati FluxLog
///
protected bool fluxLogReduce = false;
///
/// Dizionario dei valori FluxLog ultimi verificati x veto
///
protected Dictionary fluxLogReduceLast = new Dictionary();
///
/// Dizionario dei valori FluxLog ultimi tipo STRING verificati x veto
///
protected Dictionary fluxLogReduceLastString = new Dictionary();
///
/// Dizionario dei veto send x ogni variabile quando non variata
///
protected Dictionary fluxLogReduceVeto = new Dictionary();
///
/// Finestra in minuti x invio dati FluxLog quando invariati
///
protected int fluxLogResendPeriod = 60;
///
/// indica che è richiesto forzatamente reset contapezzi
///
protected bool forcePzReset = false;
///
/// indica che è richiesto forzatamente reset contapezzi
///
protected DateTime forcePzResetUntil = DateTime.Now.AddMinutes(-1);
///
/// DEADBAND globale se impostata (es x gestire variazioni minime valori FluxLog da inviare...)
///
protected float GLOBAL_DBAND = 0;
///
/// Determina se siano gestite le ricette
///
protected bool hasRecipe = false;
///
/// Array dei contatori x segnali blinking
///
protected int[] i_counters;
///
/// Indica impianto IN SETUP (fino a quando SMETTE di esserlo...)
///
protected bool inSetup = false;
///
/// ultimo tentativo connessione...
///
protected DateTime lastConnectTry;
///
/// Dizionario ULTIMI valori impostati x produzione
///
protected Dictionary lastProdData = new Dictionary();
///
/// Ultimo invio contapezzi (x invio delayed)
///
protected DateTime lastPzCountSend;
///
/// Dizionario ultimi valori (string) delle TSS
///
protected Dictionary LastTSS = new Dictionary();
///
/// Dizionario ultimi valori (string) delle TSS
///
protected Dictionary LastTSSSend = new Dictionary();
///
/// Dizionario ultimi valori (double) delle TSVC
///
protected Dictionary LastTSVC = new Dictionary();
///
/// Ultima registrazione warning x ODL mancante (x scrivere solo ogni 15 secondi)
///
protected DateTime lastWarnODL;
///
/// ultima data-ora invio parametri write x ribadire status
///
protected DateTime lastWriteParamsUpsert = DateTime.Now;
///
/// Separatore linea (tipicamente x commenti)
///
protected string lineSep = "--------------------------";
///
/// Elenco parametri calcolati da inviare alla macchina (es ricetta con traduzione, RAMA/TFT)
///
protected List list2Write = new List();
///
/// Elenco delle eventuali condizioni di veto conditions attive
///
protected List ListVetoCond = new List();
///
/// Num massimo di errori in funzionalità check alive
///
protected int maxAliveErrors = utils.CRI("maxAliveErrors");
///
/// Soglia massima errori prima della disconnessione automatica in check
///
protected int maxErroriCheck = utils.CRI("maxErroriCheck");
///
/// Quantità massima per singola ricetta (per determinare num ricette da inviare)
///
protected int maxQtyPerFile = 0;
///
/// Num massimo di errori di rete read (dal PLC)
///
protected int maxReadErrors = utils.CRI("maxReadErrors");
///
/// Periodo massimo (in sec) per letture dati in mancanza di eventi di aggiornamento
///
protected int MaxSecReload = 120;
///
/// Num massimodi errori di rete send al server
///
protected int maxSendErrors = utils.CRI("maxSendErrors");
///
/// Numero massimo di tentativi x test ping preliminare
///
protected int maxTryPing = 1;
protected string mem2trace = "";
///
/// indica se serva refresh parametri e quindi PLC...
///
protected bool needRefresh = true;
///
/// Indica se usare la parte numerica di un articolo come codice INT
///
protected bool numArtCharTrim = false;
///
/// Timeout x ping al server
///
protected int pingServerMsTimeout = utils.CRI("PingMsTimeout");
///
/// DataOra avvio Programma x check avvio
///
protected DateTime prgStarted = DateTime.Now;
///
/// Ritardo minimo x invio contapezzi
///
protected int pzCountDelay = 2500;
///
/// Periodo di campionamento x refresh info
///
protected int samplePeriod = 1000;
///
/// MsSample Period base x campionamenti tpo OCP-UA
///
protected int samplePeriodBase = 600;
///
/// Dizionario di VC da trattare come TimeSeries (con conf decodificata + processing successivo...)
///
protected Dictionary TSVC_Data = new Dictionary();
///
/// Indica se usare archivio locale ricette vs scarico http/REST
///
protected bool useLocalRecipe = true;
///
/// Dizionario di VARIABILI da trattare come eventi (da inviare quando cambiano oppure a
/// scadenza periodo...)
///
protected Dictionary VarArray = new Dictionary();
///
/// Dizionario dei divieti di invio x ogni counter gestito
///
protected Dictionary VetoCounterSend = new Dictionary();
///
/// Durata in secondi del divieto accodamento segnali IN alla fase di startup
///
protected int vetoQueueIn = 10;
///
/// Periodo di veto prima di invio nuova conferma dei valori write correnti, default ogni 5 min
///
protected double vetoSendWriteUpsert = 1;
///
/// Periodo wathdog di default (2 sec se non specificato)
///
protected int watchDogPeriod = 2;
#endregion Protected Fields
#region Protected Properties
///
/// Valore del num max invii consecutivi da coda...
///
protected static int nMaxSend
{
get
{
int answ = 5;
try
{
answ = utils.CRI("nMaxSend");
}
catch
{ }
return answ;
}
}
///
/// Indica il counter della keyReq richiesta attiva
/// - gestito tramite Redis
/// - a scadenza 25h
/// - incrementato ogni invio di auto ODL
///
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");
}
}
///
/// Dizionario delle deadband attive
///
protected Dictionary dictActDBand { get; set; } = new Dictionary();
protected Dictionary DictNumArt { get; set; } = new Dictionary();
protected bool disDynDataRangeCheck { get; set; } = false;
///
/// Folder in cui è presente il tool import excel + file json di conf
///
protected string exclToolDirPath { get; set; } = "";
///
/// Folder in cui salvare i file importati (es excel)
///
protected string fileArchiveFolder { get; set; } = "";
///
/// Folder da cui importare i file (es excel)
///
protected string fileImportFolder { get; set; } = "";
///
/// Tipologia di files da importare (default excel)
///
protected string fileImportType { get; set; } = "*.xslx";
///
/// Dati fluxLog serializzati in redis
///
protected List fluxLogData
{
get
{
List answ = new List();
string redKeyFLog = redisMan.redHash($"IOB:CurrData:{IOBConfFull.General.FilenameIOB}:LogFile:FluxLog");
var rawData = redisMan.getRSV(redKeyFLog);
if (!string.IsNullOrEmpty(rawData))
{
answ = JsonDeserialize>(rawData);
}
return answ;
}
set
{
string redKeyFLog = redisMan.redHash($"IOB:CurrData:{IOBConfFull.General.FilenameIOB}:LogFile:FluxLog");
string fluxLogRaw = JsonSerialize(value);
redisMan.setRSV(redKeyFLog, fluxLogRaw);
}
}
///
/// Variabile di appoggio GLOBALE x indicare che si può forzare reset contapezzi con
/// macchina in RUN
///
protected bool forceResetInRun { get; set; } = false;
///
/// Variabile di appoggio GLOBALE x indicare macchina RUN (es x gestione su reset pezzi)
///
protected bool isRunning { get; set; } = false;
///
/// Variabile isRunning (NON realtime)
///
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);
}
///
/// Wrapper di log
///
protected override Logger lg
{
get => _logger;
}
///
/// Elenco Articoli (x invio verso macchina)
///
protected List ListaArticoli { get; set; } = new List();
///
/// Elenco Job di produzione (x invio verso macchina)
///
protected List ListaJobs { get; set; } = new List();
///
/// Valore limite MASSIMO di invio di dati come array Json
///
protected int maxJsonData { get; set; } = utils.CRI("maxJsonData");
///
/// Valore limite MASSIMO di invio di dati come array Json x EVENTI
///
protected int maxJsonDataEv { get; set; } = utils.CRI("maxJsonDataEv");
///
/// Max tentativi ping permessi (default: 5)
///
protected int maxPingRetry { get; set; } = 5;
///
/// Coda massima ammessa per FLog (se ‹= 0 disattivata...)
///
protected int maxQueueFLog { get; set; } = utils.CRI("maxQueueFLog");
///
/// Coda massima ammessa per FLog (se ‹= 0 disattivata...)
///
protected int maxQueueRawTransf { get; set; } = utils.CRI("maxQueueRawTransf");
///
/// Valore MINIMO limite x decidere invio di dati come array Json
///
protected int minJsonData { get; set; } = utils.CRI("minJsonData");
protected string nextKeyRich
{
get
{
return $"{dailyKey}{countKeyRichiesta:00}";
}
}
///
/// Numero letture IN da avvio
///
protected int nReadFilt { get; set; }
///
/// Numero letture IN da avvio
///
protected int nReadIN { get; set; }
///
/// Numero invii OUT (svuotamento coda)
///
protected int nSendOut { get; set; }
///
/// Numero simulazioni ammesse...
///
protected int numSim { get; set; }
///
/// Dizionario condizioni di check BIT da usare x valori controllo (es ModBus TCP Imax)
///
protected Dictionary OptCheckCondBit { get; set; } = new Dictionary();
///
/// Dizionario condizioni di check INT da usare x valori controllo bitmap (es ModBus TCP Zetapack)
///
protected Dictionary OptCheckCondInt { get; set; } = new Dictionary();
///
/// Elenco di valori da tradurre da valori BIT (es ModBus TCP FIMAT)
/// NB: potrebbero essere contemporaneamente attivi + valori e + traduzioni
///
protected Dictionary OptVar2TranslBit { get; set; } = new Dictionary();
///
/// Elenco di valori da tradurre da valori INT esclusivi (es ModBus TCP FIMAT)
///
protected Dictionary OptVar2TranslInt { get; set; } = new Dictionary();
///
/// Elenco dei fullPath utili x processing
///
protected Dictionary pathList { get; set; } = new Dictionary();
///
/// Gestione archivio serializzato PODL inviati (tramite file temp locale/REDIS)
///
protected Dictionary POdlSentFileArch
{
get
{
Dictionary answ = new Dictionary();
string redKey = GetPOdlSentKey();
string rawData = redisMan.getRSV(redKey);
if (!string.IsNullOrEmpty(rawData))
{
try
{
answ = JsonDeserialize>(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 = JsonSerialize(value);
string redKey = GetPOdlSentKey();
redisMan.setRSV(redKey, rawVal);
lgDebug($"Salvataggio status POdlSentFileArch | {value.Count} record");
}
}
///
/// Elenco corrente (in memory) PODL inviati all'impianto
/// chiave: idxPODL
/// valore: record PODL
///
protected Dictionary POdlSentList { get; set; } = new Dictionary();
///
/// Indica se sia stato resettato un contapezzi
///
protected bool pzCountResetted { get; set; } = false;
///
/// Fornisce il valore letto da BITMAP in formato valido x messa in coda nel formato dtEve#valReq#cont
///
protected string qEncodeIN
{
get
{
string answ = "";
try
{
answ = string.Format("{0:yyyyMMddHHmmssfff}#{1:X2}#{2}", DateTime.Now, B_output, counterSigIN);
}
catch
{ }
return answ;
}
}
///
/// Definizioni x replace in file ricette
///
protected Dictionary RecipeReplRules { get; set; } = new Dictionary();
///
/// Chiave ultima condition registrata redis (da aggiungere eventuale ID specifico condition)
///
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");
}
///
/// Redis key del dizionari valori DataItemMem persistiti
///
protected string rKeyFluxMem
{
get => GetFluxMemKey();
}
protected Random rndGen { get; set; } = new Random();
///
/// Indica se vada inviata la keyReq richiesta con lo split/autoODL
///
protected bool sendKeyRichiesta { get; set; } = false;
///
/// test ping all'indirizzo PLC/CNC impostato nei parametri
///
///
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;
}
}
///
/// URL per INVIO IN BLOCCO di un INCREMENTO x contapezzi (quelli della macchina: PLC/CNC)...
///
protected string urlAddPzCount
{
get => $@"{urlCommandIob("savePzCountInc")}?qty=";
}
///
/// URL per INVIO IN BLOCCO di un INCREMENTO x contapezzi (quelli della macchina: PLC/CNC)
/// in una data SPECIFICA
///
protected string urlAddPzCountAtDate
{
get => $@"{urlCommandIob("savePzCountIncAtDate")}?qty=";
}
///
/// URL per check alive...
///
protected string urlAlive
{
get
{
return $@"{urlCommand("alive")}";
}
}
///
/// URL per creazione di un PODL da cod art (metodo GET)...
///
protected string urlCreatePOdl
{
get => $@"{urlCommandIob("forceCreatePOdl")}?";
}
///
/// URL per chiamata generazione Snapshot Dossier giornalieri alla data
///
protected string urlFixDailyDossier
{
get => $@"{urlCommandIob("fixDailyDossier")}";
}
///
/// URL per chiamata generazione ODL giornalieri alla data
///
protected string urlFixDailyOdl
{
get => $@"{urlCommandIob("fixDailyOdl")}";
}
///
/// URL per chiamata generazione ODL giornalieri alla data + conferma PzCount ad ogni step
///
protected string urlFixDailyOdlConfPzCount
{
get => $@"{urlCommandIob("fixDailyOdlConfPzCount")}";
}
///
/// URL per forzare split ODL...
///
protected string urlForceSplit
{
get => $"{urlCommandIob("forceSplitOdlFull")}?doConfirm=true&qtyFromLast=true&roundStep=500";
}
///
/// URL per recupero PODL NEXT = ATTIVABILI x macchina...
///
protected string urlGetActPODL
{
get => $@"{urlCommandIob("getPOdlAct")}";
}
///
/// URL per recupero ARTICOLI correnti x macchina...
///
protected string urlGetCurrArt
{
get => $@"{urlCommandIob("getLastArtByMacc")}";
}
///
/// URL per recupero DOSS correnti x macchina...
///
protected string urlGetCurrDOSS
{
get => $@"{urlCommandIob("getLastDossByMacc")}";
}
///
/// URL per recupero IDX ODL corrente...
///
protected string urlGetCurrODL
{
get => $@"{urlCommandIob("getCurrODL")}";
}
///
/// URL per recupero dati ODL corrente...
///
protected string urlGetCurrOdlRow
{
get => $@"{urlCommandIob("getCurrOdlRow")}";
}
///
/// URL per recupero PODL ATTIVABILI (quindi correnti in senso di pronti) x macchina...
///
[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")}";
}
///
/// URL per recupero ListValue tipo fasi...
///
protected string urlGetListValFasiPodl
{
get => $@"{urlCommand("getListValByTable")}PODL";
}
///
/// URL per recupero PODL NEXT = ATTIVABILI x macchina...
///
protected string urlGetNextPODL
{
get => $@"{urlCommandIob("getPOdlNext")}";
}
///
/// URL per recupero traduzione CodArt in numerico...
///
protected string urlGetNumArt
{
get => $@"{urlCommandIob("getArtNum")}";
}
///
/// URL per recupero traduzione CodComm in numerico...
///
protected string urlGetNumComm
{
get => $@"{urlCommandIob("getXdlNum")}";
}
///
/// URL per recupero num pezzi ODL corrente...
///
protected string urlGetNumPzCurrODL
{
get => $@"{urlCommandIob("getCurrOdlQtaReq")}";
}
///
/// URL per richiamo parametri da scrivere...
///
protected string urlGetParams2Write
{
get => $@"{urlCommandIob("getObjItems2Write")}";
}
///
/// URL per recupero contapezzi...
///
protected string urlGetPzCount
{
get => $@"{urlCommandIob("getCounter")}";
}
///
/// URL per recupero contapezzi REGISTRATI da TC...
///
protected string urlGetPzCountRec
{
get => $@"{urlCommandIob("getCounterTCRec")}";
}
///
/// URL per richiamo task da eseguire...
///
protected string urlGetTask2Exe
{
get => $@"{urlCommandIob("getTask2Exe")}";
}
///
/// URL per recupero idle time IOB...
///
protected string urlIdleTime
{
get => $@"{urlCommandIob("getIdlePeriod")}";
}
///
/// URL per recupero inizio ODL...
///
protected string urlInizioOdlIob
{
get => $@"{urlCommandIob("getCurrOdlStart")}";
}
///
/// URL per check se abilitato...
///
protected string urlIobEnabled
{
get => $@"{urlCommandIob("enabled")}";
}
///
/// URL per richiesta chiusura manuale ad utente x ODL corrente (metodo GET)...
///
protected string urlODLAskClose
{
get => $@"{urlCommandIob("askCloseODL")}?idxOdl=";
}
///
/// URL per chiusura ODL corrente (metodo GET)...
///
protected string urlODLClose
{
get => $@"{urlCommandIob("closeODL")}/?idxOdl=";
}
///
/// URL per AVVIO di un ODL da PODL indicato (metodo GET)...
///
protected string urlOdlStartFromPOdl
{
get => $@"{urlCommandIob("forceStartPOdl")}?idxPODL=";
}
///
/// URL per chiusura PODL --> ODL corrente (metodo GET)...
///
protected string urlPODLClose
{
get => $@"{urlCommandIob("closePODL")}?idxPOdl=";
}
///
/// URL per richiamo task da eseguire...
///
protected string urlRemTask2Exe
{
get => $@"{urlCommandIob("remTask2Exe")}?taskName=";
}
///
/// URL per salvataggio dati PARAMETRI IOB...
///
protected string urlSaveAllParams
{
get => $@"{urlCommandIob("setObjItems")}";
}
///
/// URL x salvataggio elenco dataItems OpcUa/MTC
///
protected string urlSaveDataItems
{
get => $@"{urlCommandIob("saveDataItems")}";
}
///
/// URL per invio MachineIobConf info
///
protected string urlSaveMachIobConf
{
get => $@"{urlCommandIobFile("saveMachineIobConf")}";
}
///
/// URL per salvataggio dati conf memoria IOB...
///
protected string urlSaveMemMap
{
get => $@"{urlCommandIobFile("saveConf")}";
}
///
/// URL per INVIO di un update dello status di un certo allarme
///
protected string urlSendAlarm
{
get => $@"{urlCommandIob("sendAlarmBankUpdate")}";
}
///
/// URL per invio info HASH gestione recipes
///
protected string urlSetHashDict
{
get => $@"{urlCommandIob("setRedisHashDict")}";
}
///
/// URL per salvataggio dati associazione Machine 2 IOB...
///
protected string urlSetM2IOB
{
get => $@"{urlCommandIobFile("setM2IOB")}?IOB_name={Environment.MachineName}";
}
///
/// URL per salvataggio VALORI opzionali...
///
protected string urlSetOptVal
{
get => $@"{urlCommandIob("addOptPar")}?";
}
///
/// URL per salvataggio contapezzi...
///
protected string urlSetPzCount
{
get => $@"{urlCommandIob("setCounter")}?counter=";
}
///
/// URL per effettuare salvataggio parametri (snapshot) macchina...
///
protected string urlTakeSnapshot
{
get => $@"{urlCommandIob("takeFlogSnapshot")}";
}
///
/// URL per salvataggio in UPSERT dei PARAMETRI IOB scritti...
///
protected string urlUpdateWriteParams
{
get => $@"{urlCommandIob("upsertObjItems")}";
}
///
/// Secondi standard x veto check status e log
///
protected int vetoSeconds { get; set; } = utils.CRI("vetoSeconds");
#endregion Protected Properties
#region Protected Methods
///
/// Decodifica file MAP (caso .bit)
///
///
///
/// indirizzo Byte: indirizzo di partenza memoria
/// dimensione singolo slot in byte
/// indirizzo bit: numero riga x calcolo indice bit
///
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;
}
}
///
/// Decodifica file MAP generico
///
///
///
///
///
///
///
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;
}
///
/// Decodifica valore della coda IN nel formato sendEnab[0]=dtEve sendEnab[1]=valore sendEnab[2]=counter
///
/// dtEve + '#' + valReq + '#' + cont
///
protected static string[] qDecodeIN(string queueVal)
{
string[] answ = null;
if (!string.IsNullOrEmpty(queueVal))
{
try
{
answ = queueVal.Split('#');
}
catch
{ }
}
return answ;
}
///
/// Accodamento richieste server
///
///
///
protected void accodaServReq(string codTav, string newReq)
{
JobTaskData jobTask = new JobTaskData(codTav, newReq);
// accodo richiesta serializzata
string serVal = JsonSerialize(jobTask);
QueueSrvReq.Enqueue(serVal);
// loggo!
lgDebug($"[QUEUE] | QueueSrvReq len: {QueueSrvReq.Count}");
}
///
/// Accodamento risposte per il server
///
///
///
protected void accodaServResp(string codTav, string rawData)
{
JobTaskData jobTask = new JobTaskData(codTav, rawData);
// accodo richiesta serializzata
string serVal = JsonSerialize(jobTask);
QueueSrvResp.Enqueue(serVal);
// loggo!
lgDebug($"[QUEUE] | QueueSrvResp len: {QueueSrvResp.Count}");
}
///
/// Classe di base implementazione traduzione di una LUT da memoria come valore BIT a valore
/// esplicito x FLog
///
protected virtual void checkTranslateBit()
{
// verifico se devo processare decodifica di qualche valore...
if (OptVar2TranslBit != null && OptVar2TranslBit.Count > 0)
{ }
}
///
/// Classe di base implementazione traduzione di una LUT da memoria come valore INT a valore
/// esplicito x FLog
///
protected virtual void checkTranslateInt()
{
if (OptVar2TranslInt != null && OptVar2TranslInt.Count > 0)
{ }
}
///
/// Verifico i dynData x validità:
/// - siano almeno 1
/// - NON siano tutti identici
///
///
///
protected bool checkValidDynData(Dictionary 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;
}
///
/// Conversione string row in log generico
///
///
///
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;
}
///
/// Restituisce stato allarmi in formato byte[]
///
///
///
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;
}
///
/// Recupera ODL attivo su impianto
///
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 = HttpService.CallUrlGet(urlGetCurrODL);
int.TryParse(sCurrODL, out cODL);
currIdxODL = cODL;
vetoCheckOdl = DateTime.Now;
}
else
{
cODL = currIdxODL;
}
return cODL;
}
///
/// Aggiunge o aggiorna nDict ad un Dict
///
///
///
///
protected void DictUpsert(ref Dictionary currDict, string newKey, string newVal)
{
if (currDict.ContainsKey(newKey))
{
currDict[newKey] = newVal;
}
else
{
currDict.Add(newKey, newVal);
}
}
///
/// Verifica se salvare (nel log) info dei FluxLog filtrati
/// - per scadenza valore VetoFlushFiltFL
/// - per un numero complessivo di eventi superiori a soglia
///
/// numero minimo di eventi necessari al salvataggio (come somma complessiva)
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);
}
}
///
/// Imposta eventuali altri valori default
///
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;
}
///
/// Effettua un force kill dato nome processo
///
///
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}");
}
}
}
}
}
///
/// Imposta la memoria PLC in modo forzato (es x oggetti gestiti da FTP come setComm)
///
protected virtual void forceMemMap()
{ }
///
/// Implementazione di riferimento della verifica stato allarmi
///
///
///
protected virtual int getAlarmStatus(BaseAlarmConf item)
{
int answ = 0;
return answ;
}
///
/// Implementazione di riferimento della verifica stato allarmi formato UInt
///
///
///
protected virtual uint getAlarmStatusUInt(BaseAlarmConf item)
{
uint answ = 0;
return answ;
}
///
/// Recupera valore da dizionario CurrProdData o restituisce val default
///
/// Chiave richiesta
/// Valore di default
///
protected string getCurrProdData(string key, string defVal)
{
string answ = "";
if (currProdData.ContainsKey(key))
{
answ = string.IsNullOrEmpty(currProdData[key]) ? defVal : currProdData[key];
}
return answ;
}
///
/// Fornisce formato valido x messa in coda nel formato dtEve#valReq#cont
///
protected string getEncodSigLog(DateTime DtEvent, int val2log, int counter)
{
string answ = "";
try
{
answ = $"{DtEvent:yyyyMMddHHmmssfff}#{val2log:X2}#{counter}";
}
catch
{ }
return answ;
}
///
/// Recupero dati da FluxLog
///
///
///
///
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;
}
///
/// Recupero dati da FluxLog formato double
///
///
///
///
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;
}
///
/// Recupero dati da FluxLog formato INT
///
///
///
///
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;
}
///
/// Converte un file di log della macchina in un dizionario
///
///
///
protected List getGenLogFromMachineLog(string dailyLog)
{
List result = new List();
//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;
}
///
/// Effettua traduzione da tabella listValue per chiave
///
///
///
///
protected string getLV(Dictionary currDict, string key)
{
string answ = currDict.ContainsKey(key) ? currDict[key] : key;
return answ;
}
///
/// Restituisce valore salvato in memMapWrite
///
///
///
protected string getMemMapWriteVal(string keyName)
{
string answ = "";
if (memMap != null && memMap.mMapWrite.ContainsKey(keyName))
{
answ = memMap.mMapWrite[keyName].value;
}
return answ;
}
///
/// Recupera valore numerico ARTICOLO x salvataggio valori INT
///
///
///
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 = HttpService.CallUrlGet(url2call);
// deserializzo e recupero KVP...
var dictArtSrv = JsonConvert.DeserializeObject>(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;
}
///
/// Recupera valore numerico COMMESSA x salvataggio valori INT
///
///
///
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 = HttpService.CallUrlGet(url2call);
}
// restituisco
return answ;
}
///
/// Stringa raw dei parametri da scrivere...
///
///
protected string getParams2write()
{
string answ = "";
string url2call = $"{urlGetParams2Write}";
if (verboseLog)
{
lgInfo("chiamata URL " + url2call);
}
answ = HttpService.CallUrlGet(url2call);
// se vuoto faccio seconda prova...
if (string.IsNullOrEmpty(answ) || answ == "[]")
{
answ = HttpService.CallUrlGet(url2call);
}
return answ;
}
///
/// Recupera file log da analizzare
///
///
protected virtual bool getRemoteLog()
{
bool fatto = false;
return fatto;
}
///
/// 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
///
/// Cod DP/Tavola (opzionale)
///
protected async Task 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 HttpService.CallUrlAsync(url2call);
}
return answ;
}
///
/// Verifica presenza eventuali allarmi
///
///
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 = JsonSerialize(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;
}
///
/// Recupera da server set di dati specifici x IOB
///
///
protected virtual bool IobGetDataFromServer()
{
return false;
}
///
/// Effettua upload verso server FTP della macchina dei files nella folder indicata
///
///
///
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;
}
///
/// Prepara files CSV da inviare alla macchina
///
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;
}
///
/// Prepara files USTD da inviare alle macchine in formato USTD (Emmegi - taglierine)
///
protected virtual bool iobWriteLocalUSTD()
{
return false;
}
///
/// Effettua traduzione ITEM da LUT parametrica (keyReq: tipo+id) del file di conf, se non
/// trovo uso keyReq
///
///
///
///
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;
}
///
/// Effettua traduzione ITEM da LUT parametrica (keyReq: lemma) del file di conf, se non
/// trovo uso keyReq
///
///
///
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;
}
///
/// Legge il file di conf di una MAP di informazioni da gestire con lettura set memoria
///
/// nome vettore memoria
/// file origine
/// dimensione (in byte) della memoria
/// dimensione (in byte) della memoria
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(ref vettoreConf, numRiga);
lgInfo(string.Format("Fine caricamento vettore di {0} variabili per file {1}", numRiga, nomeFile));
}
///
/// Lettura memorie conf speciali (json) ...SE inserito in [OPTPAR] come PARAM_CONF=nome.json
///
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 = JsonDeserialize(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}");
}
}
///
/// Recupera elenco PODL assegnabili
///
///
protected List MachineNextPodl()
{
List reqPOdlList = new List();
// 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 HttpService.CallUrlAsync(urlGetNextPODL);
if (!string.IsNullOrEmpty(rawListPODL))
{
reqPOdlList = JsonDeserialize>(rawListPODL) ?? new List();
}
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lg.Error($"Errore: chiamata MachineNextPodl: {ex.Message}");
}
return reqPOdlList;
}
///
/// Indica il periodo di sampling della memoria (se presente, altrimenti 0)
///
///
///
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;
}
///
/// Metodo da overridare x scrivere DAVVERO i parametri sul PLC
/// NB: deve resettare reqValue
///
///
protected virtual void plcWriteParams(ref List updatedPar)
{
// non faccio nulla di base...
}
///
/// Esegue eventuali step a valle dell'auto ODL (in caso di esito positivo autoODL)
/// Effettuare override x casi specifici (es SiemensRama x reset contapezzi)
///
protected virtual void processAutoOdlExtraStep()
{
// non fa nulla di default...
}
///
/// Effettua sync dati da e verso impianto
///
protected virtual void ProcessDataSync()
{
}
///
/// Ponte esecuzione processi asunc
///
///
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;
}
///
/// Effettua eventuale file import, archiviando file importati
/// - es gestione file excel di Giacovelli
/// - es gestione ritorno ricette FIMAT
///
///
protected virtual async Task 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 statsColl = new Dictionary();
// 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 = HttpService.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 list2Send = new Dictionary();
string rawData = File.ReadAllText(fileItem);
if (!string.IsNullOrEmpty(rawData))
{
var convData = JsonDeserialize>(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;
}
///
/// Processa le richieste di scrittura memoria
///
///
protected string processMemWriteRequests()
{
string answ = "";
// li salvo nei parametri in memoria locale (ogni adapter DOVREBBE salvare POI sul VERO PLC)
List writeList = new List();
List updatedPar = new List();
string currOut = "";
// recupero elenco delle cose da fare
string resp = getParams2write();
if (!string.IsNullOrEmpty(resp))
{
try
{
writeList = JsonDeserialize>(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 = JsonSerialize(updatedPar);
HttpService.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;
}
///
/// Wrapper sync process other info
///
///
///
///
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;
}
///
/// Cerca di recuperare i file generati dall'impianto in merito al processing degli ordini
///
///
///
/// Processing di dati "OtherInfo" da implementare caso x caso (qui riportato caso FIMAT ricette...)
///
///
///
protected virtual async Task 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 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;
}
///
/// Sync archivio ricette (Macchina ‹--› MES)
///
///
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;
}
///
/// Effettua task òettura a bassa velocità
///
///
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));
}
}
}
}
///
/// Registra i file in directory suddivise x anno/settimana e restitusice elenco
///
/// Percorso file XML
/// Percorso base archivi
///
protected List RecipeDoArchiveWeek(string recipeFile, string archBasePath)
{
List weeksProc = new List();
// 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 = XmlDeserialize(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;
}
///
/// Verifica i file recuperati
/// - PODL da inviare a MAPO
/// - accumulo consumo materiali
///
///
///
///
protected virtual async Task RecipeDoCheckFileProc(string localPath, string archBasePath)
{
bool answ = false;
List weekProcNew = new List();
// 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 redHashWeek = new Dictionary();
Dictionary stdActList = new Dictionary();
stdActList.Add("Arch", "Archivia");
stdActList.Add("SendArch", "Invia + Archivia");
List weekHashUpdate = new List();
// 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 = JsonSerialize(cPerInfo);
redHashWeek.Add(cWeek, rawWeek);
}
// salvo in redis...
string fullKey = GetWeekStatsKey();
var okHashDict = redisMan.redSaveHashDict(fullKey, redHashWeek);
// invio ANCHE in MP-IO l'update delle info...
string remUrl = urlSetHashDict;
string dictPayload = JsonSerialize(redHashWeek);
await HttpService.CallUrlAsync(remUrl, dictPayload);
}
}
return answ;
}
///
/// Verifica se si debba registrare/inviare il report periodico di consumo indicato
///
///
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 = GetWeekStatsKey();
Dictionary 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;
}
///
/// Acquisisce eventuali nuove ricette
///
///
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 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($"{namePart[0]}", $"{newNum}");
// 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;
}
///
/// restitusice i record di consumo dei materiali associati al file:
///
/// Percorso file XML
///
protected List RecipeGetCons(string recipeFile)
{
List listConsumi = new List();
// leggo contenuto XML
string rawData = File.ReadAllText(recipeFile);
// deserializza file XML x recuperare righe consumo
var currRecipe = XmlDataSerializer.Deserialize(rawData);
if (currRecipe != null)
{
if (currRecipe.ColRecipe != null && currRecipe.ColRecipe.Count > 0)
{
List RigheConsumi = new List();
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;
// }
///
/// Prepara la ricetta indicata partendo dai dati della ricetta template + dati ODL di riferimento
///
/// Path del file template della ricetta
/// Record del PODL da inviare
/// Path della ricetta da inviare
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;
}
///
/// Prepara le ricette dato fullPath temp scaricando elenco PODL
///
///
/// Percorso temp di appoggio x preparare ricette (compreso nome macchina)
///
///
/// Indica se usare le copie locali delle ricette oppure richiedere da remoto (REST get)
///
///
protected virtual bool RecipeReqWriteLocal(string tempPath, bool useLocal)
{
bool fatto = false;
lgTrace($"RecipeReqWriteLocal start | tempPath {tempPath} | useLocal {useLocal}");
List reqPOdlList = new List();
// 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 HttpService.CallUrlAsync(urlGetNextPODL);
if (!string.IsNullOrEmpty(rawListPODL))
{
try
{
reqPOdlList = JsonConvert.DeserializeObject>(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 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;
}
///
/// Effettua invio delle ricette alla macchina
///
/// Area dove si trovano i fiel da trasmettere
/// Area archivio
/// Area remota dove inviare files
///
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;
}
///
/// Effettua recupero in locale dei task eseguiti dalla macchina
///
/// Area remota da dove recuperare files
/// Area dove parcheggiare temporaneamente i fiels x processing
///
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;
}
///
/// Cancella dal server i task eseguiti
///
///
///
///
///
protected async Task 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 HttpService.CallUrlAsync(url2call);
}
return answ;
}
///
/// Salva nel dizionario il num di valori letti e filtrati/non inviati
///
///
protected void SaveFiltFluxLog(string codFlux)
{
if (DictFiltFLog.ContainsKey(codFlux))
{
DictFiltFLog[codFlux]++;
}
else
{
DictFiltFLog.Add(codFlux, 1);
_logger.Info($"FluxLog | veto | {codFlux}");
}
}
///
/// Invio reset allarmi all'avvio per sicurezza...
///
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);
}
}
}
}
///
/// Processa chiusura ODL
///
/// Idx dell'ODL da chiudere
/// DataOra di riferimento evento stop
protected async Task SendCloseOdl(int idxOdl, DateTime dtRif)
{
bool answ = false;
DateTime dtCurr = DateTime.Now;
string resp = await HttpService.CallUrlAsync($"{urlODLClose}{idxOdl}&dtEve={dtRif}&dtCurr={dtCurr}");
answ = resp == "OK";
return answ;
}
///
/// Processa chiusura PODL
///
/// Idx del PODL da chiudere
/// DataOra di riferimento evento stop
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 HttpService.CallUrlAsync(fullUrl);
answ = callResp == "OK";
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in SendClosePOdl | {Environment.NewLine}{ex.Message}");
}
return answ;
}
///
/// 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)
///
///
///
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;
}
///
/// Invia informazioni associazione IOB 2 machine
///
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}");
}
}
///
/// Invia informazioni associazione IOB 2 machine
///
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 HttpService.CallUrlAsync(urlSetM2IOB);
lgInfo($"chiamata URL {urlSetM2IOB} | result: {result}");
}
}
}
///
/// Ponte Async invio MachineConf
///
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}");
}
}
///
/// 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)
///
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 currDict = new Dictionary();
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 = JsonSerialize(currDict);
await HttpService.CallUrlAsync(remUrl, dictPayload);
lgTrace("Invio MachineConf effettuato");
}
}
}
}
}
///
/// Invia al server IO i valori dei parametri opzionali (es counters)
///
/// Nome parametro
/// Valore parametro
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 HttpService.CallUrlAsync(url2call);
}
}
}
///
/// Invia al server IO i valori dei parametri opzionali (es counters)
///
/// Nome parametro
/// Valore parametro INT
protected async Task sendOptVal(string paramName, int paramValueInt)
{
// override!
await sendOptVal(paramName, $"{paramValueInt}");
}
///
/// Invio contapezzi alla dataora indicata
///
/// Num pezzi da registrare
/// DataOra di riferimento evento
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 HttpService.CallUrlAsync(fullUrl);
answ = callResp == "OK";
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in SendPzIncrAtDate | {Environment.NewLine}{ex.Message}");
}
return answ;
}
///
/// Processa avvio PODL (ed eventuale chiusura precedente)
///
/// Idx del PODL da avviare
/// DataOra di riferimento evento start
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 HttpService.CallUrlAsync(fullUrl);
answ = callResp == "OK";
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in SendStartPodl | {Environment.NewLine}{ex.Message}");
}
return answ;
}
///
/// Invia messaggio a logWatcher
///
///
///
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;
}
///
/// Impostazioni parametri PLC
///
protected virtual void setParamPlc()
{
loadMemConf();
fixDefaultPar();
if (resetAlarmOnStart)
{
SendAlarmReset();
}
}
///
/// setup gestione allarmi da conf
///
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 = JsonDeserialize(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
}
///
/// Setup parametri decode file excel (opzionale)
///
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;
}
}
}
///
/// setup parametri da file di conf
///
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 = JsonSerialize(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()
};
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 = JsonSerialize(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 = HttpService.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 allParam = new List();
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 = JsonSerialize(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 = HttpService.CallUrl($"{tipoCall}", rawData);
}
else
{
lgInfo($"Mancata risposta ping server, non effettuata chiamata {tipoCall}");
}
lgDebug($"setupMemMap | salvata conf memoria R/W");
}
}
}
///
/// Setup parametri memoria opzionali
/// es: BitCond e IntCond x ricerca valori target
///
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);
}
}
}
}
}
}
///
/// Init parametri speciali, tipicamente KVP opzionali da json
///
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);
}
}
///
/// Cleanup stringa x impiego tipo ident da char dubbi
///
///
///
protected string strFixId(string origData)
{
return origData.Replace(".", "").Replace(" ", "_");
}
///
/// Traccia in redis un dynData x analisi frequenza e campionamento valori FluxLog ...
///
///
///
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();
}
///
/// Traccia in redis l'attività di scambio dati (tipicamente non in polling)
///
/// Dim pacchetto (numero byte scambiati/ricevuti)
/// Dim minima x registrare info
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;
}
}
///
/// Track dati ricevuti generici (serializzando)
///
///
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}");
}
}
///
/// Traccia in redis l'attività di lettura dati
///
/// Dim pacchetto (numero byte letti)
/// Se specificato indica il tempo effettivo di lettura, se zero uso timespan giornaliero...
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);
}
///
/// Reset valori trackdata all'avvio dell'adapter IOB
///
protected void trackReadDataReset()
{
// per prima cosa LEGGO il valore precedente
DateTime adesso = DateTime.Now;
string rKey = $"{redisMan.redIobTrackKey}:DataIn:{adesso:yyMMdd}";
redisMan.redDelKey(rKey);
}
///
/// Versione sync richiesta chiusura ODL corrente
///
///
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;
}
///
/// Effettua chiamata MP-IO per chiedere all'utente se vuole effettuare chiusura ODL
/// corrente della macchina
///
///
protected async Task TryAskCloseCurrODLAsync()
{
bool fatto = false;
string fullUrl = $"{urlODLAskClose}{currIdxODL}";
try
{
// invio chiamata URL x chiusura ODL su macchina
string callResp = await HttpService.CallUrlAsync(fullUrl);
fatto = (callResp != "KO") ? true : false;
}
catch
{ }
lgInfo($"Eseguito TryAskCloseCurrODLAsync per {currIdxODL} | url: {fullUrl} | esito: {fatto}");
return fatto;
}
///
/// Versione sync chiusura ODL corrente
///
///
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;
}
///
/// Effettua chiamata MP-IO per tentare chiusura ODL corrente della macchina
///
///
protected async Task TryCloseCurrODLAsync()
{
bool fatto = false;
string fullUrl = $"{urlODLClose}{currIdxODL}";
try
{
// invio chiamata URL x chiusura ODL su macchina
string callResp = await HttpService.CallUrlAsync(fullUrl);
fatto = (callResp != "KO") ? true : false;
}
catch
{ }
lgInfo($"Eseguito TryCloseCurrODLAsync per {currIdxODL} | url: {fullUrl} | esito: {fatto}");
return fatto;
}
///
/// Versione sync chiusura ODL specifico
///
///
///
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;
}
///
/// Effettua chiamata MP-IO per tentare chiusura ODL specifico
///
///
///
protected async Task TryCloseODLAsync(int IdxODL)
{
bool fatto = false;
string fullUrl = $"{urlODLClose}{IdxODL}";
try
{
// invio chiamata URL x chiusura ODL su macchina
string callResp = await HttpService.CallUrlAsync(fullUrl);
fatto = (callResp != "KO") ? true : false;
}
catch
{ }
lgInfo($"Eseguito TryCloseODLAsync per {IdxODL} | url: {fullUrl} | esito: {fatto}");
return fatto;
}
///
/// Effettua chiamata MP-IO per tentare chiusura PODL --> ODL specifico
///
///
///
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 HttpService.CallUrlAsync(fullUrl);
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;
}
///
/// Effettua chiamata MP-IO per tentare chiusura PODL --> ODL specifico
///
///
///
///
///
///
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 HttpService.CallUrlAsync(fullUrl);
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;
}
///
/// Processa avvio PODL (ed eventuale chiusura precedente)
///
/// Cod Articolo del PODL da avviare
/// Cod Gruppo dell'impianto come default
/// num pz richiesti
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 HttpService.CallUrlAsync(urlEncoded);
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;
}
///
/// 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
///
/// Idx macchina completo, con tavola/pallet di invio
/// Contapezzi MES (IOB) attuale
/// Contapezzi impianto (per la tavola indicata)
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 = HttpService.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 = HttpService.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;
}
///
/// 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
///
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 = HttpService.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 = HttpService.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");
}
}
///
/// Cerca di inviare su un altro thread i vari dati accumulati...
///
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);
}
///
/// Versione sync chiamata setup PODL
///
///
///
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;
}
///
/// Effettua chiamata MP-IO per tentare setup del PODL indicato
///
///
///
protected async Task TrySetupPODLAsync(int idxPODL)
{
bool fatto = false;
string fullUrl = $"{urlOdlStartFromPOdl}{idxPODL}";
try
{
// invio chiamata URL x avvio PODL su macchina
string rawSplit = await HttpService.CallUrlAsync(fullUrl);
fatto = (rawSplit != "KO") ? true : false;
}
catch
{ }
return fatto;
}
///
/// Effettua chiamata MP-IO per tentare setup del PODL indicato con indicazioni estese
/// (confirm pezzi, dtEvento, dtCorrente)
///
///
///
///
///
///
protected async Task 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 HttpService.CallUrlAsync(fullUrl);
fatto = (rawSplit != "KO") ? true : false;
}
catch
{ }
return fatto;
}
///
/// URL del comando letto da conf in aggiunta al server MES da contattare
///
protected string urlCommand(string cmqReq)
{
return $@"{urlMesServer}{IOBConfFull.MapoMes.ApiUrl(cmqReq)}";
}
///
/// URL come urlCommand + aggiunta codice IOB da cui viene inviato
///
protected string urlCommandIob(string cmqReq)
{
return $@"{urlMesServer}{IOBConfFull.MapoMes.ApiUrl(cmqReq)}{IOBConfFull.General.CodIOB}";
}
///
/// URL come urlCommand + aggiunta codice IOB da file (= specifico x Reboot) da cui viene inviato
///
protected string urlCommandIobFile(string cmqReq)
{
return $@"{urlMesServer}{IOBConfFull.MapoMes.ApiUrl(cmqReq)}{IOBConfFull.General.FilenameIOB}";
}
///
/// URL per richiamo task da eseguire...
///
protected string urlRemTask2ExeTav(string codTav)
{
return $@"{urlCommandIob("remTask2Exe")}|{codTav}?taskName=";
}
///
/// Verifica se il parametro passi il limite della DeadBand (globale o specifica se
/// configurata) Il riferimento è al prec valore currProdData
///
/// nome del parametro da verificare
/// valore attuale da testare
///
/// true = passa controllo, è da inviare / false = variazione entro deadband non da inviare
///
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;
}
///
/// Recupera da conf durata del veto x FluxLog (default 60sec)
///
///
///
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
};
///
/// Oggetto logger della classe
///
private static Logger _logger = LogManager.GetCurrentClassLogger();
///
/// Accumulatore counter byte x inviare meno record in REDIS
///
private long currByteCount = 0;
///
/// Dizionario dei valori FluxLog filtrati
///
private Dictionary DictFiltFLog = new Dictionary();
///
/// Ultima data x statistiche daily x trackDynData
///
private string LastDayCurr = "";
///
/// Ultimo invio valore a server
///
private DateTime lastSigVarSent = DateTime.Now;
///
/// periodo minimo di veto invio dati qualora non siano variati
///
private int lastSigVarVeto = 55;
private Dictionary TrackDayStatsCount = new Dictionary();
private Dictionary TrackDetStatsCount = new Dictionary();
private Dictionary> TrackDetValsCount = new Dictionary>();
private DateTime vetoCheckOdl = DateTime.Now;
///
/// Valore veto al salvataggio di valori filtrati in FluxLog se NON superato limite chiamate
///
private DateTime VetoFlushFiltFL = DateTime.Now.AddHours(1);
///
/// Dizionario dei valori bloccati x evitare log eccessivo
///
private Dictionary vetoLogError = new Dictionary();
///
/// Periodo di veto log in minuti
///
private int vetoPeriodMin = 15;
///
/// Periodo Veto prima di eseguire ProcessAutoOdlAsync
///
private DateTime VetoProcessAutoOdl = DateTime.Now;
///
/// Variabile x vietare rilettura conf memoria (se è stato letto metto veto lungo ad 10 min x evitare loop in avvio)
///
private DateTime vetoReloadConf = DateTime.Now.AddMinutes(-1);
///
/// Dizionario dei valori bloccati x evitare log basato su veto temporale
///
private Dictionary vetoSendFLog = new Dictionary();
///
/// Ms di attesa x uscita processo (std)
///
private int waitForExitMsec = 250;
#endregion Private Fields
#region Private Properties
///
/// Boolean abilitazione coda eventi IN
///
private bool qInEnabCurr { get; set; } = false;
///
/// Redis key del dizionari valori currProdData persistiti
///
private string rKeyCurrProdData
{
get => redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:CurrProdData");
}
///
/// URL di base del server MES da contattare
///
private string urlMesServer
{
get => $@"{IOBConfFull.MapoMes.Transport}://{IOBConfFull.MapoMes.IpAddr}";
}
#endregion Private Properties
#region Private Methods
///
/// Aggiunge in setup memoria la checkCondition BIT "decodificata"
///
/// Chiave da aggiungere
/// Valore da decodificare e poi aggiungere
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);
}
}
///
/// Aggiunge in setup memoria la checkCondition INT "decodificata"
///
/// Chiave da aggiungere
/// Valore da decodificare e poi aggiungere
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);
}
}
///
/// Aggiunge in setup memoria l'oggetto tipo valore BIT (NON mutuamente esclusivo) da tradurre
///
/// Elenco valori da aggiungere formato csv (es "a,b,c")
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, "");
}
}
}
///
/// Aggiunge in setup memoria l'oggetto tipo valore INT (mutuamente esclusivo) da tradurre
///
/// Elenco valori da aggiungere formato csv (es "a,b,c")
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, "");
}
}
}
///
/// Verifica e se necessario comprime directory log...
///
private void checkShrinkDir()
{
// comprimo x prima cosa la folder dell'IOB corrente... testo sia IOBConf che FilenameIOB
List path2chk = new List() { 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}");
}
}
}
///
/// retituisce data-ora dell'ODL corrente
///
///
private async Task currOdlStart()
{
DateTime inizioOdl = DateTime.Now;
string rawDataInizio = await HttpService.CallUrlAsync(urlInizioOdlIob);
DateTime.TryParse(rawDataInizio, out inizioOdl);
return inizioOdl;
}
///
/// Mostra i dati grezzi letti in esadecimale Parametri da
/// aggiornare x display in form
///
private void displayRawData(ref newDisplayData currDispData)
{
// mostro update...
string newString = string.Format("{0:X}", B_input);
currDispData.newInData = newString;
}
///
/// Accumula statistiche daily e di dettaglio x la chiave indicata
///
///
///
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 nDict = new Dictionary();
nDict.Add($"{DateTime.Now:yyyy-MM-dd HH:mm:ss}", value);
TrackDetValsCount.Add(key, nDict);
}
}
///
/// Salva le statistiche accumulate alla giornata di riferimento salvata e resetta contatori
///
///
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}";
}
///
/// Archivia una cartella in un file zip
///
/// Cartelal da archiviare
/// Nome del file zip da produrre...
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;
}
private async Task ExecuteIobCheckWithRetry(int maxRetries)
{
var rand = new Random();
for (int i = 0; i <= maxRetries; i++)
{
try
{
if (i > 0)
{
int delay = i == 3 ? rand.Next(250, 1000) : rand.Next(250, 500);
await Task.Delay(delay);
}
string callResp = await HttpService.CallUrlAsync(urlIobEnabled);
if (callResp == "OK") return true;
}
catch (Exception exc)
{
lgError($"Eccez
/// Esegue filtraggio dati x bit blinking!!!
///
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().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().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);
}
}
}
}
}
}
///
/// Legge da conf il valore di demoltiplica lettura dynData (se presente) o ignora e pone a 1...
///
private void fixDemFactDynData()
{
demFactDynData = IOBConfFull.FluxLog.DemFactDynData;
}
///
/// Test ping all'indirizzo impostato nei parametri
///
///
private IPStatus GetPingStatus()
{
var pStatus = Task.Run(async () => await GetPingStatusAsync(maxPingRetry + 1))
.GetAwaiter()
.GetResult();
return pStatus;
}
///
/// Test ping all'indirizzo impostato nei parametri
///
/// Numero max tentativi (maxPingRetry + 1)
///
private async Task 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;
}
///
/// Verifica se il log di un dato errore sia permesso
///
/// ID del valore log da loggare/verificare
///
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;
}
///
/// Classe fittizia in caso di processing GLOBALE di tutto in 1 solo colpo...
///
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 updatedPar = new List();
List currWritePar = new List();
// 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 = JsonSerialize(updatedPar);
HttpService.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 = JsonSerialize(currWritePar);
var res = HttpService.CallUrlPost($"{urlUpdateWriteParams}", rawData);
lgInfo($"Reinviato a server stato {updatedPar.Count} parametri WRITE");
lastWriteParamsUpsert = adesso;
}
}
}
}
///
/// Effettua gestioen programma: legge e mostra su display...
///
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);
}
}
}
///
/// Processo lettura dati sysinfo
///
private void processSysInfo()
{
if (utils.CRB("enableSysInfo"))
{
Dictionary currSysInfo = new Dictionary();
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);
}
}
}
}
}
}
///
/// Effettua calcolo dei consumi + esportazione in file richiesto
///
/// Folder dei file da processare
/// Percorso file out di export
private bool RecipeDoConsumeReport(string folderPath, string reportPath)
{
// var di base
bool fatto = false;
string expMode = "csv";
List ListConsDet = new List();
List ListConsSum = new List();
// 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 = JsonDeserialize(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;
}
///
/// Processa la ricetta alla ricerca dei dati PODL x inviare chiamate ad MP-IO
///
/// Path RIcetta
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 PODL)
string tokenSearch = "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("", "");
int.TryParse(rawVal, out idxPOdl);
if (idxPOdl > 0)
{
// leggo inizio commessa
tokenSearch = "";
var rowStart = recipeRows.Where(x => x.Contains(tokenSearch)).FirstOrDefault();
if (rowStart != null)
{
rawVal = rowStart.Trim().Replace(tokenSearch, "").Replace("", "");
DateTime.TryParse(rawVal, out dtStartPOdl);
}
// leggo durata commessa
tokenSearch = "";
var rowDurSec = recipeRows.Where(x => x.Contains(tokenSearch)).FirstOrDefault();
if (rowDurSec != null)
{
rawVal = rowDurSec.Trim().Replace(tokenSearch, "").Replace("", "");
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 HttpService.CallUrlAsync($"{urlOdlStartFromPOdl}{idxPOdl}&dtEve={dtEve}&dtCurr={dtCurr}");
// ora chiamo chiusura...
dtEve = $"{dtEndPOdl:yyyyMMddHHmmssfff}";
dtCurr = $"{DateTime.Now:yyyyMMddHHmmssfff}";
await HttpService.CallUrlAsync($"{urlPODLClose}{idxPOdl}&dtEve={dtEve}&dtCurr={dtCurr}");
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in RecipeDoProcessPODL.callUrl: {ex.Message}");
}
}
answ = true;
}
return answ;
}
///
/// Elimino record da REDIS (locale e remoto)
///
///
private async Task RecipeRemoveWeekStatus(string keyReq)
{
string fullKey = GetWeekStatsKey();
var okHashDict = redisMan.redRemoveHashField(fullKey, keyReq);
// rileggo status hash
Dictionary currDict = redisMan.redGetHashDict(fullKey);
// invio ANCHE in MP-IO l'update delle info...
string remUrl = urlSetHashDict;
string dictPayload = JsonSerialize(currDict);
await HttpService.CallUrlAsync(remUrl, dictPayload);
//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);
}
///
/// Chiamate ritorno task eseguiti al server
///
///
///
private async Task SendTaskResult(List listaValori)
{
foreach (var rawJob in listaValori)
{
// deserializzo...
JobTaskData jobTask = JsonDeserialize(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);
}
}
}
///
/// Effettua ciclo recupero richieste server
///
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);
}
}
}
}
}
}
///
/// Effettua ciclo invio rispsote (=esito esecuzione richieste) al server
///
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 listaValori = new List();
// 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;
}
}
}
}
///
/// Imposto alcuni valori di default
///
/// indica se sia richiesto di SVUOTARE le code delle info
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);
}
}
}
}
///
/// Processo la coda FLog...
///
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 listaValori = new List();
// 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;
}
}
}
}
///
/// Processo la coda RawTransf...
///
private async Task 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