using EgwProxy.Ftp; using IOB_UT_NEXT; using MapoSDK; using Newtonsoft.Json; using NLog; using NLog.Targets; using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Reflection; using System.Runtime.Serialization.Formatters.Binary; using System.Security.AccessControl; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using System.Xml.Serialization; using YamlDotNet.Core.Tokens; using static IOB_UT_NEXT.BaseAlarmConf; using static IOB_UT_NEXT.CustomObj; using static IOB_UT_NEXT.DataModel.Fimat; using static MapoSDK.WharehouseData; namespace IOB_WIN_FORM.Iob { public partial class Generic : BaseObj { #region 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 = JsonConvert.DeserializeObject>(rawData); } return answ; } set { string redKeyFLog = redisMan.redHash($"IOB:CurrData:{IOBConfFull.General.FilenameIOB}:LogFile:FluxLog"); string fluxLogRaw = JsonConvert.SerializeObject(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 = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:POdlSent"); string rawData = redisMan.getRSV(redKey); if (!string.IsNullOrEmpty(rawData)) { try { answ = JsonConvert.DeserializeObject>(rawData); lgInfo($"Rilettura status POdlSentFileArch: trovati {answ.Count} record"); } catch (Exception exc) { lgError($"Errore in deserializzazione POdlSentFileArch{Environment.NewLine}{exc}"); } } return answ; } set { string rawVal = JsonConvert.SerializeObject(value); string redKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:POdlSent"); redisMan.setRSV(redKey, rawVal); lgDebug($"Salvataggio status POdlSentFileArch | {value.Count} record"); } } /// /// 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 => redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:FluxMem"); } 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 = JsonConvert.SerializeObject(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 = JsonConvert.SerializeObject(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 = utils.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 = utils.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 = utils.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 = utils.CallUrlGet(url2call); // se vuoto faccio seconda prova... if (string.IsNullOrEmpty(answ) || answ == "[]") { answ = utils.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 utils.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 = JsonConvert.SerializeObject(item.alarmsState); redisMan.setRSV(alarmHash, rawAlarms); } else { lgError($"Errore in sendAlarmVariations | {item.memAddr} | i: {i} | prev: {item.alarmsState[i]} | curr: {currStatus}"); } } else { if (currStatus > 0) { lgTrace($"Alarm UNCHANGED but Active | {item.memAddr} | i: {i} | prev: {item.alarmsState[i]} | curr: {currStatus}"); } } } } } answ = numRaised > 0; } // gestione allarmi come elenco else if (alarmType == AlarmBlockType.ActiveList) { // verifico SE ho allarmi if (alarmMaps != null && alarmMaps.Count > 0) { // ciclo x ogni blocco foreach (var item in alarmMaps) { // cerco se sia superiore al livello minimo da conf if (item.blockLevel >= alarmLevelMin) { // controllo variabile counter SE > 0... int numCount = getAlarmStatus(item); answ = numCount > 0; } } } } return answ; } /// /// 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() { bool answ = false; #if false // conf ftp var ftpConf = IOBConfFull.Special.FtpConf; // salvo articoli string locDir = ftpConf.DirLocal; bool addHeader = ftpConf.CsvAddHeader; string basePath = Application.StartupPath; string tempDir = Path.Combine(basePath, locDir); lgInfo($"iobWriteLocalCSV | locDir: {locDir} | addHeader: {addHeader} | tempDir: {tempDir}"); baseUtils.checkDir(tempDir); string filePath = Path.Combine(tempDir, "articoli.csv"); answ = DataExport.SaveToCsv(ListaArticoli, filePath, addHeader); if (answ) { lgInfo($"CSV: saved ART file as articoli.csv at {filePath}"); // salvo PODL string csvName = $"{DateTime.Now:dd-MM-yyyy}.csv"; filePath = Path.Combine(tempDir, $"{csvName}"); answ = DataExport.SaveToCsv(ListaJobs, filePath, addHeader); if (answ) { lgInfo($"CSV: saved PODL file {csvName} at {filePath}"); } } #endif return answ; } /// /// 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 = JsonConvert.DeserializeObject(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 callUrl(urlGetNextPODL, false); if (!string.IsNullOrEmpty(rawListPODL)) { reqPOdlList = JsonConvert.DeserializeObject>(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 = utils.callUrl(urlGetOdlAtDate(dataDoc)); int.TryParse(sIdxODL, out idxODL); // chiamo conversione TimeSpan timeElaps = fpm.doProcess(fileItem, idxODL); statsColl.Add($"ExcImport conversion executed for {fileItem}", timeElaps); lgTrace(($"ExcImport conversion executed for {fileItem} | {timeElaps.Milliseconds}ms")); } } // cerco i file json x invio var listFiles2Upload = Directory.GetFiles(dirConvert, "*.json"); // se li trovo --> chiamo import! if (listFiles2Upload != null && listFiles2Upload.Length > 0) { foreach (var fileItem in listFiles2Upload) { // leggo il file json Dictionary list2Send = new Dictionary(); string rawData = File.ReadAllText(fileItem); if (!string.IsNullOrEmpty(rawData)) { var convData = JsonConvert.DeserializeObject>(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 = JsonConvert.DeserializeObject>(resp); // se ho da fare chiamo esecuzione.. if (writeList.Count > 0) { foreach (var item in writeList) { // scrivo in memoria if (memMap.mMapWrite.ContainsKey(item.uid)) { memMap.mMapWrite[item.uid].value = item.reqValue; currOut = $" | Parameter {item.uid} | {item.value} --> {item.reqValue}"; // sistemo valori item.value = item.reqValue; // forzo variabile writeable item.writable = true; lgInfo($"Richiesta update parametro {currOut}"); // salvo in lista da ritrasmettere updatedPar.Add(item); } else { currOut = $" | Error: parameter {item.uid} not found"; lgError($"Errore {currOut}"); } // accodo in stringa taskVal... answ += currOut; } // richiamo scrittura parametri su PLC plcWriteParams(ref updatedPar); // invio su cloud parametri! string rawData = JsonConvert.SerializeObject(updatedPar); utils.callUrl($"{urlUpdateWriteParams}", rawData); lgInfo($"Notifica a server scrittura {updatedPar.Count} parametri"); } } catch (Exception exc) { lgError($"Eccezione in processMemWriteRequests:{Environment.NewLine}{exc}"); } } else { lgError("Non è stata ricevuta risposta x task da eseguire"); } return answ; } /// /// 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 XmlSerializer serializer = new XmlSerializer(typeof(ARecipe)); using (StringReader reader = new StringReader(rawData)) { try { var currRecipe = (ARecipe)serializer.Deserialize(reader); 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); } } catch (Exception exc) { lgError($"Eccezione in RecipeDoArchiveWeek{Environment.NewLine}{exc}"); } } 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 = JsonConvert.SerializeObject(cPerInfo); redHashWeek.Add(cWeek, rawWeek); } // salvo in redis... string fullKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:WeekStats"); var okHashDict = redisMan.redSaveHashDict(fullKey, redHashWeek); // invio ANCHE in MP-IO l'update delle info... string remUrl = urlSetHashDict; string dictPayload = JsonConvert.SerializeObject(redHashWeek); //await callUrlWithPayloadAsync(remUrl, dictPayload, false); await callUrlWithPayloadAsync(remUrl, dictPayload, true); } } return answ; } /// /// 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 = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:WeekStats"); 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 XmlSerializer serializer = new XmlSerializer(typeof(ARecipe)); using (StringReader reader = new StringReader(rawData)) { try { var currRecipe = (ARecipe)serializer.Deserialize(reader); 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(); } } } 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 callUrl(urlGetNextPODL, false); 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 utils.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 callUrl($"{urlODLClose}{idxOdl}&dtEve={dtRif}&dtCurr={dtCurr}", false); 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 callUrl(fullUrl, false); 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 utils.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 = JsonConvert.SerializeObject(currDict); await callUrlWithPayloadAsync(remUrl, dictPayload, true); 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 utils.callUrlAsync(url2call); #if false utils.callUrlNow(url2call); #endif } } } /// /// 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 callUrl(fullUrl, false); 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 callUrl(fullUrl, false); 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 = JsonConvert.DeserializeObject(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 = JsonConvert.SerializeObject(memMap, Formatting.Indented); lgDebug($"setupMemMap | configurazione memoria R/W:{Environment.NewLine}{rawMemConf}"); } // se ho variabili read --> genero dati TSVC... if (memMap.mMapRead.Count > 0) { TSVC_Data.Clear(); LastTSVC.Clear(); VCData currConf; int periodo = 0; VC_func funz = VC_func.POINT; // accodo nella conf... foreach (var item in memMap.mMapRead) { funz = item.Value.func; periodo = item.Value.period; currConf = new VCData() { Funzione = funz, Period = periodo, UM = item.Value.unit, // imposto a NOW per forzare accumulo dati prima di inviare troppo presto (es COMECA Pizzaferri valori 0) DTStart = DateTime.Now, //DTStart = DateTime.Now.AddHours(-1), dataArray = new List() }; TSVC_Data.Add(item.Key, currConf); } // log avvio + setup lastVal... foreach (var item in TSVC_Data) { lgTrace($"TSVC: {item.Key} | periodo: {item.Value.Period} | funz: {item.Value.Funzione}"); // salvo i valori PREC... LastTSVC.Add(item.Key, 0); } } // infine se genObj memoria valido salvo in MP-IO x sue applicazioni if (memMap != null) { // invio su cloud conf memoria... string rawData = JsonConvert.SerializeObject(memMap, Formatting.Indented); // controllo ping al server... if (serverOk) { string sendKey = "SendMemMap"; bool sendEnab = CheckSendVeto(sendKey, TimeSpan.FromMinutes(480)); // se abilitato faccio invio e salvo nuovo valore if (sendEnab) { var resp = utils.callUrl($"{urlSaveMemMap}", rawData); } } else { lgInfo($"Mancata risposta ping da server, saltato step invio memMap a {urlSaveMemMap}"); } // salvo ANCHE come parametri i valori... objItem currItem = new objItem(); List 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 = JsonConvert.SerializeObject(allParam, Formatting.Indented); if (serverOk) { // verifica se sia un IOB "parziale" --> salva solo update ai parametri if (IOBConfFull.Device.DisabExeTask || IOBConfFull.Device.DisabStateCh) { // versione upsert tipoCall = urlUpdateWriteParams; } var resp = utils.callUrl($"{tipoCall}", rawData); } else { lgInfo($"Mancata risposta ping server, non effettuata chiamata {tipoCall}"); } lgDebug($"setupMemMap | salvata conf memoria R/W"); } } } /// /// 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 callUrl(fullUrl, false); 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 callUrl(fullUrl, false); 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 callUrl(fullUrl, false); 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 callUrl(fullUrl, false); fatto = (callResp != "KO") ? true : false; }) .GetAwaiter() .GetResult(); } catch (Exception ex) { lgError($"Errore in TryClosePODL | idxPODL: {idxPODL}{Environment.NewLine}{ex.Message}"); } lgInfo($"Eseguito TryClosePODL per {idxPODL} | url: {fullUrl} | esito: {fatto}"); return fatto; } /// /// 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 callUrl(fullUrl, false); fatto = (callResp != "KO") ? true : false; }) .GetAwaiter() .GetResult(); } catch (Exception ex) { lgError($"Errore in TryClosePODL | idxPODL: {idxPODL} | dtEve: {dtEve} | dtCurr: {dtCurr}{Environment.NewLine}{ex.Message}"); } lgInfo($"Eseguito TryClosePODL per {idxPODL} | url: {fullUrl} | esito: {fatto}"); return fatto; } /// /// 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 callUrl(urlEncoded, false); int.TryParse(resp, out answ); }) .GetAwaiter() .GetResult(); } catch (Exception ex) { lgError($"Errore in TryCreatePodl | codArt: {codArt} | codGruppo: {codGruppo} | numPz: {numPz}{Environment.NewLine}{ex.Message}"); } return answ; } /// /// 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 = utils.CallUrlGet(lastUrl); if (!string.IsNullOrEmpty(resp)) { // dalla risposta (come numero) capisco SE ha aggiunto i pezzi (e quanti) int.TryParse(resp, out qtyAdded); if (qtyAdded > 0) { // aggiorno contapezzi ... pzCountMes += qtyAdded; lgInfo($"SEND incremento contapezzi: send: {numIncr} | resp: {qtyAdded} | contapezziMES: {pzCountMes}"); // invio conferma contapezzi.. string fullUrl = $"{urlSetPzCount}{pzCountMes}".Replace(IOBConfFull.General.CodIOB, fullCode); string retVal = utils.callUrl(fullUrl); // verifica se tutto OK if (retVal != $"{pzCountMes}") { // errore salvataggio contapezzi lgError($"trySendPzCountBlock: errore salvataggio contapezzi: contapezziMES {pzCountMes} | risposta: {retVal}"); } } else { lgError($"Richiesto incremento {numIncr} ma NON registrato su server MP-IO"); } } currDispData.newUrlCallData = lastUrl; currDispData.counter = pzCountMes; currDispData.semOut = Semaforo.SV; raiseRefresh(currDispData); } } } else { lgError("Impossibile trySendPzCountBlock: IobOnline è false"); } } else { lgError("Impossibile trySendPzCountBlock: MPOnline è false"); } return qtyAdded; } /// /// 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 = utils.CallUrlGet(lastUrl); if (!string.IsNullOrEmpty(resp)) { // dalla risposta (come numero) capisco SE ha aggiunto i pezzi (e quanti) int.TryParse(resp, out qtyAdded); if (qtyAdded > 0) { // aggiorno IL MIO contapezzi... contapezziIOB += qtyAdded; lgInfo($"SEND incremento contapezzi: send: {numIncr} | resp: {qtyAdded} | contapezziIOB: {contapezziIOB}"); // invio conferma contapezzi.. string fullUrl = $"{urlSetPzCount}{contapezziIOB}"; string retVal = utils.callUrl(fullUrl); // verifica se tutto OK if (retVal != contapezziIOB.ToString()) { // errore salvataggio contapezzi lgError($"trySendPzCountBlock: errore salvataggio contapezzi: contapezziIOB {contapezziIOB} | risposta: {retVal}"); } } else { lgError($"Richiesto incremento {numIncr} ma NON registrato su server MP-IO"); } } currDispData.newUrlCallData = lastUrl; currDispData.counter = contapezziIOB; currDispData.semOut = Semaforo.SV; raiseRefresh(currDispData); } } } else { lgError("Impossibile trySendPzCountBlock: IobOnline è false"); } } else { lgError("Impossibile trySendPzCountBlock: MPOnline è false"); } } /// /// 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 callUrl(fullUrl, false); 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 callUrl(fullUrl, false); 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 callUrl(urlInizioOdlIob, false); 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) { // Al terzo tentativo fallito resetto i client if (i == 3) resetWebClients(); int delay = i == 3 ? rand.Next(250, 1000) : rand.Next(250, 500); await Task.Delay(delay); } string callResp = await callUrl(urlIobEnabled, i < 3); // true per i primi tentativi if (callResp == "OK") return true; } catch (Exception exc) { lgError($"Eccez /// 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 = JsonConvert.SerializeObject(updatedPar); utils.CallUrlPost($"{urlUpdateWriteParams}", rawData); lgInfo($"Notificato a server scrittura {updatedPar.Count} parametri"); } else { // se scaduto tempo da ultimo invio... e ho valori if (currWritePar.Count > 0 && (lastWriteParamsUpsert.AddMinutes(vetoSendWriteUpsert) < adesso)) { // invio su cloud parametri! string rawData = JsonConvert.SerializeObject(currWritePar); var res = utils.CallUrlPost($"{urlUpdateWriteParams}", rawData); lgInfo($"Reinviato a server stato {updatedPar.Count} parametri WRITE"); lastWriteParamsUpsert = adesso; } } } } /// /// 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 = JsonConvert.DeserializeObject(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 callUrl($"{urlOdlStartFromPOdl}{idxPOdl}&dtEve={dtEve}&dtCurr={dtCurr}", false); // ora chiamo chiusura... dtEve = $"{dtEndPOdl:yyyyMMddHHmmssfff}"; dtCurr = $"{DateTime.Now:yyyyMMddHHmmssfff}"; await callUrl($"{urlPODLClose}{idxPOdl}&dtEve={dtEve}&dtCurr={dtCurr}", false); }) .GetAwaiter() .GetResult(); } catch (Exception ex) { lgError($"Errore in RecipeDoProcessPODL.callUrl: {ex.Message}"); } } answ = true; } return answ; } /// /// Elimino record da REDIS (locale e remoto) /// /// private async Task RecipeRemoveWeekStatus(string keyReq) { string fullKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:WeekStats"); var okHashDict = redisMan.redRemoveHashField(fullKey, keyReq); // rileggo status hash Dictionary currDict = redisMan.redGetHashDict(fullKey); // invio ANCHE in MP-IO l'update delle info... string remUrl = urlSetHashDict; string dictPayload = JsonConvert.SerializeObject(currDict); await callUrlWithPayloadAsync(remUrl, dictPayload, true); //await callUrlWithPayloadAsync(remUrl, dictPayload, false); } private void reportDataProc() { // update valori visualizzazione... parentForm.dataProcLabel = string.Format("RAW: {0} --> IN: {1} --> OUT: {2}", nReadIN, nReadFilt, nSendOut); } /// /// Chiamate ritorno task eseguiti al server /// /// /// private async Task SendTaskResult(List listaValori) { foreach (var rawJob in listaValori) { // deserializzo... JobTaskData jobTask = JsonConvert.DeserializeObject(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 listaValori = new List(); // se ho + di maxJsonData elementi --> invio un set di dati alla volta if (QueueRawTransf.Count > maxJsonData) { // prendoi primi maxJsonDataValori for (int j = 0; j < maxJsonData; j++) { QueueRawTransf.TryDequeue(out currVal); listaValori.Add(currVal); } fatto = await sendDataBlock(urlType.RawTransf, listaValori, force); } else { // invio in blocco listaValori = QueueRawTransf.ToList(); // invio fatto = await sendDataBlock(urlType.RawTransf, listaValori, force); if (fatto) { // svuoto se ha okReport! QueueRawTransf = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueRawTransf", false, redisMan); } } } else { break; } } else { break; } } else { break; } } } return fatto; } /// /// Processo la coda UserLog... /// private async Task SvuotaCodaULogAsync() { // verifico SE la coda abbia dei valori... if (QueueULog.Count > 0) { // invio pacchetto di dati (max da conf) for (int i = 0; i < nMaxSend; i++) { // SE ho qualcosa in coda... if (QueueULog.Count > 0) { string currVal = ""; if (MPOnline) { if (IobOnline) { List listaValori = new List(); // se ho + di maxJsonData elementi --> invio un set di dati alla volta if (QueueULog.Count > maxJsonData) { // prendoi primi maxJsonDataValori for (int j = 0; j < maxJsonData; j++) { QueueULog.TryDequeue(out currVal); listaValori.Add(currVal); } await sendDataBlock(urlType.ULog, listaValori); } else { // invio in blocco listaValori = QueueULog.ToList(); // invio await sendDataBlock(urlType.ULog, listaValori); // svuoto! QueueULog = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueULog", false, redisMan); } } else { break; } } else { break; } } else { break; } } } } private void UpdateIobState(bool newStatus) { // Log se lo stato è cambiato DateTime adesso = DateTime.Now; if (IobOnline != newStatus || adesso.Subtract(lastIobStatusDisplUpdate).TotalMilliseconds > (baseUtils.nextPauseSendMSec * 10)) { lgInfo(newStatus ? "IOB ONLINE for server MP/IO" : "IOB OFFLINE for server MP/IO"); IobOnline = newStatus; lastIobStatusDisplUpdate = adesso; if (newStatus) { lastIobOnline = adesso; parentForm.commSrvActive = 2; // Stato Online } else { parentForm.commSrvActive = 1; // Stato Degradato/Offline } } // Imposto il veto per il prossimo controllo (se ConnOk + rapido sennò meno rapido) var msWait = baseUtils.nextPauseSendMSec * (connectionOk ? 12 : 150); dtVetoCheckIOB = adesso.AddMilliseconds(msWait); } #endregion Private Methods } }