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