diff --git a/AGENTS.md b/AGENTS.md index c77fd7e8..cee8e2b8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,16 +10,22 @@ A collection of .NET (C#/VB) projects for industrial communication with NC contr ```powershell & "$env:MSBUILD_PATH" "ProjectName\ProjectName.csproj" -target:Build /p:Configuration=Release /p:Platform="x86" /p:OutputPath=bin/ /nodeReuse:false /verbosity:minimal /m ``` -- **NuGet**: Requires Steamware Nexus Proxy sources (defined in `.gitlab-ci.yml`). -- **Versioning**: Automated during CI via `VersGen\VersGen.cs` and `.nuspec` updates. -- **Release Artifacts**: Zipped using `7z.exe`; MD5/SHA1 hashes are generated; uploaded to Nexus. +- **NuGet**: Requires Steamware Nexus Proxy sources. Use `dotnet nuget restore` on the specific `.sln` before building. +- **Versioning**: Automated via `VersGen\VersGen.cs` and `.nuspec` updates during CI. Manual updates to `VersGen.cs` might be needed if mimicking CI. +- **Release Artifacts**: Zipped using `7z.exe`; MD5/SHA1 hashes are generated. ## Key Directories & Files - **`IOB-WIN-*`**: Protocol-specific implementations (e.g., `FANUC`, `SIEMENS`, `SHELLY`). -- **`VersGen\`**: Handles automated assembly versioning during build. -- **`UtilityScripts\`**: Contains `alarmFormatter.py` for converting alarm CSV/Excel to JSON. Requires `python3` and `pip install inquirer`. +- **`VersGen\`**: Handles automated assembly versioning. +- **`UtilityScripts\`**: Contains `alarmFormatter.py`. Requires `python3` and `pip install inquirer`. +- **`EgwCApp\`**: Core application logic and testing projects. ## Environment & Setup - **Siemens PLC**: Must enable "PUT/GET" permission in TIA Portal. -- **Dependencies**: Uses `saltminion` (via Chocolatey) and specific Windows accounts (`steamware`/`IOB`). -- **Docs**: Documentation for several modules is generated via `docfx`. +- **Dependencies**: Uses `saltminion` and specific Windows accounts (`steamware`/`IOB`). +- **Documentation**: Generated via `docfx`. + +## Important Workflow Notes +- **CI Logic**: The `.gitlab-ci.yml` contains critical automation logic for NuGet sources, versioning, and deployment. Refer to it for exact operational steps. +- **NuGet Fixes**: The CI uses a `.nuget-fix` helper to manage Steamware Nexus Proxy sources. If encountering NuGet errors, check source configuration. +- **Versioning Flow**: Versioning involves updating `VersGen.cs` (replacing `0.0.0.0`) and updating `.nuspec` files. diff --git a/IOB-UT-NEXT/IOB-UT-NEXT.csproj b/IOB-UT-NEXT/IOB-UT-NEXT.csproj index a0dc1387..be331be1 100644 --- a/IOB-UT-NEXT/IOB-UT-NEXT.csproj +++ b/IOB-UT-NEXT/IOB-UT-NEXT.csproj @@ -217,7 +217,7 @@ - + diff --git a/IOB-UT-NEXT/Iob/BaseObj.cs b/IOB-UT-NEXT/Iob/BaseObj.cs index 9d886dbd..a7c5c09c 100644 --- a/IOB-UT-NEXT/Iob/BaseObj.cs +++ b/IOB-UT-NEXT/Iob/BaseObj.cs @@ -49,13 +49,6 @@ namespace IOB_UT_NEXT.Iob /// public AlarmBlockType alarmType = AlarmBlockType.Bitmap; -#if false - /// - /// Conf adapter corrente - /// - public IobConfiguration cIobConf; -#endif - /// /// Conteggio ATTUALE ore macchina IN LAVORO /// @@ -146,6 +139,11 @@ namespace IOB_UT_NEXT.Iob /// public DateTime lastIobOnline = DateTime.Now.AddHours(-1); + /// + /// Ultima verifica status IOB x forzare display status SRV + /// + public DateTime lastIobStatusDisplUpdate = DateTime.Now; + /// /// dataOra ultimo log periodico... /// @@ -171,11 +169,6 @@ namespace IOB_UT_NEXT.Iob /// public DateTime lastSim; - /// - /// Ultima verifica status IOB x forzare display status SRV - /// - public DateTime lastIobStatusDisplUpdate = DateTime.Now; - /// /// dataOra ultimo segnale inviato al SERVER... /// @@ -206,11 +199,6 @@ namespace IOB_UT_NEXT.Iob /// public bool needRefreshPzCount = true; - /// - /// Coda degli esiti di ping x calcolo stato macchina - /// - public DataQueue QueuePing; - /// /// Determina se utilizzare blocchi di memoria IOT contigui (e quindi processing /// "monoblocco" semplificato"= @@ -220,22 +208,27 @@ namespace IOB_UT_NEXT.Iob /// /// Coda valori ALLARMI ove gestiti... /// - public DataQueue QueueAlarm;// = new DataQueue("000", "QueueAlarm", false); + public DataQueue QueueAlarm; /// /// Oggetto della coda degli elementi letti di tipo FluxLog (e non ancora trasmessi) /// - public DataQueue QueueFLog;// = new DataQueue("000", "QueueFLog", false); + public DataQueue QueueFLog; /// /// Oggetto della coda degli elementi letti (e non ancora trasmessi) /// - public DataQueue QueueIN;// = new DataQueue("000", "QueueIN", false); + public DataQueue QueueIN; /// /// Coda valori MESSAGGI/EVENTI (da non sottocampionare come samples)... /// - public DataQueue QueueMessages;// = new DataQueue("000", "QueueMessages", false); + public DataQueue QueueMessages; + + /// + /// Coda degli esiti di ping x calcolo stato macchina + /// + public DataQueue QueuePing; /// /// Oggetto della coda degli elementi di tipo RawTransf (e non ancora trasmessi) @@ -326,38 +319,20 @@ namespace IOB_UT_NEXT.Iob /// /// Verifica se sia in modalità DEMO avanzata (campionamento da set di valori ammessi...) /// - public static bool DemoInSample - { - get - { - return baseUtils.CRB("DemoInSample"); - } - } + public static bool DemoInSample => baseUtils.CRB("DemoInSample"); /// /// Verifica se sia in modalità DEMO x dati OUTPUT /// - public static bool DemoOut - { - get - { - return utils.CRB("DemoOut"); - } - } + public static bool DemoOut => utils.CRB("DemoOut"); /// /// Indicazione VETO PING a server sino alla data-ora indicata /// public static DateTime dtVetoPing { - get - { - return utils.dtVetoPing; - } - set - { - utils.dtVetoPing = value; - } + get => utils.dtVetoPing; + set => utils.dtVetoPing = value; } /// @@ -365,14 +340,8 @@ namespace IOB_UT_NEXT.Iob /// public static DateTime dtVetoQueueIN { - get - { - return utils.dtVetoQueueIN; - } - set - { - utils.dtVetoQueueIN = value; - } + get => utils.dtVetoQueueIN; + set => utils.dtVetoQueueIN = value; } /// @@ -380,46 +349,29 @@ namespace IOB_UT_NEXT.Iob /// public static DateTime dtVetoSend { - get - { - return utils.dtVetoSend; - } - set - { - utils.dtVetoSend = value; - } + get => utils.dtVetoSend; + set => utils.dtVetoSend = value; } /// /// Verifica se sia abilitato test lettura blocchi memoria all'avvio /// - public static bool EnableTest - { - get - { - return baseUtils.CRB("enableTest"); - } - } + public static bool EnableTest => baseUtils.CRB("enableTest"); /// /// stato Online/Offline del server MP IO (su REDIS) /// public static bool MPOnline { - get - { - return utils.MPIO_Online; - } - set - { - utils.MPIO_Online = value; - } + get => utils.MPIO_Online; + set => utils.MPIO_Online = value; } #endregion Public Properties #region Public Methods +#if false /// /// Effettua chiamata URL e restituisce risultato /// @@ -452,7 +404,7 @@ namespace IOB_UT_NEXT.Iob } } return answ; - } + } /// /// Effettua chiamata URL e restituisce risultato @@ -480,6 +432,7 @@ namespace IOB_UT_NEXT.Iob } return answ; } +#endif /// /// processa dataLayer e se necessario salva/mostra @@ -504,22 +457,12 @@ namespace IOB_UT_NEXT.Iob return sMacAddress; } - public static void resetDebugConsole() - { - } - - /// - /// Reset dei webclients - /// - public static void resetWebClients() - { - utils.resetWebClients(); - } - #endregion Public Methods #region Protected Fields + + /// /// Valore di attesa (random) dopo ogni invio x evitare congestione send... /// @@ -642,6 +585,49 @@ namespace IOB_UT_NEXT.Iob return doVeto; } + /// + /// Recupera la chiave per le statistiche delle chiamate. + /// + protected string GetCallStatsKey() => redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:CallStats"); + + /// + /// Recupera la chiave per i dati di produzione correnti. + /// + protected string GetCurrProdDataKey() => redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:CurrProdData"); + + /// + /// Recupera la chiave per il flusso di memoria. + /// + protected string GetFluxMemKey() => redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:FluxMem"); + + /// + /// Recupera la chiave per l'invio dei PODL. + /// + protected string GetPOdlSentKey() => redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:POdlSent"); + + /// + /// Recupera un valore specifico dal hash dello stato dell'IOB. + /// + protected string GetStatusField(string field) + { + string baseKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}"); + return redisMan.redGetHashField(baseKey, field); + } + + /// + /// Recupera la chiave per le statistiche settimanali. + /// + protected string GetWeekStatsKey() => redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:WeekStats"); + + /// + /// Imposta un valore nel hash dello stato dell'IOB. + /// + protected void SetStatusField(string field, string value) + { + string baseKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}"); + redisMan.redSetHashField(baseKey, field, value); + } + /// /// Setup di tutti gli oggetti Queue, ma solo alcuni hanno coda REDIS (quelli senza sono "sacrificabili" /// diff --git a/IOB-UT-NEXT/RedisMan.cs b/IOB-UT-NEXT/RedisIobCache.cs similarity index 98% rename from IOB-UT-NEXT/RedisMan.cs rename to IOB-UT-NEXT/RedisIobCache.cs index 714100a4..d4fb3ca7 100644 --- a/IOB-UT-NEXT/RedisMan.cs +++ b/IOB-UT-NEXT/RedisIobCache.cs @@ -1171,6 +1171,28 @@ namespace IOB_UT_NEXT return answ; } + /// + /// Salva un SINGOLO valore nella hash dati key e field + /// + /// + /// + /// + /// + public bool redSetHashField(string hashKey, string hashField, string value) + { + bool answ = false; + // cerco se ci sia valore in redis... + try + { + answ = currDb.HashSet((RedisKey)hashKey, (RedisValue)hashField, (RedisValue)value); + } + catch (Exception exc) + { + Logging.Instance.Error($"redSetHashField {exc}"); + } + return answ; + } + /// /// Conteggio elementi in QUEUE (LIFO) /// diff --git a/IOB-UT-NEXT/baseUtils.cs b/IOB-UT-NEXT/baseUtils.cs index c39aa38b..330d2642 100644 --- a/IOB-UT-NEXT/baseUtils.cs +++ b/IOB-UT-NEXT/baseUtils.cs @@ -600,12 +600,6 @@ namespace IOB_UT_NEXT if (num >= 100) num /= 100; if (num >= 10) num /= 10; -#if false - // formulazione alternativa con ciclo... - while (num >= 10) - num /= 10; -#endif - return num; } @@ -812,15 +806,6 @@ namespace IOB_UT_NEXT return result; } - public static void resetWebClients() - { -#if false - // resetto i webclients... - client = new WebClientWT(); - clientPayload = new WebClientWT(); -#endif - } - /// /// Effettua reverse della stringa /// diff --git a/IOB-WIN-FANUC/DATA/CONF/MAIN.ini b/IOB-WIN-FANUC/DATA/CONF/MAIN.ini index f621315d..51e0748d 100644 --- a/IOB-WIN-FANUC/DATA/CONF/MAIN.ini +++ b/IOB-WIN-FANUC/DATA/CONF/MAIN.ini @@ -34,3 +34,4 @@ CLI_INST=SteamWareSim STARTLIST=SIMUL_01 MAXCNC=10 + diff --git a/IOB-WIN-FORM/AdapterForm.cs b/IOB-WIN-FORM/AdapterForm.cs index 39a2c8a5..89233c02 100644 --- a/IOB-WIN-FORM/AdapterForm.cs +++ b/IOB-WIN-FORM/AdapterForm.cs @@ -1466,7 +1466,7 @@ namespace IOB_WIN_FORM // salvo nuovo valore invio iobObj.LastSendSet(sendKey, DateTime.Now); // segnalo reboot (programma - url file)... - await Iob.Generic.callUrl(iobObj.urlReboot, true); + await utils.callUrlAsync(iobObj.urlReboot); } } else diff --git a/IOB-WIN-FORM/IOB-WIN-FORM.csproj b/IOB-WIN-FORM/IOB-WIN-FORM.csproj index f066e5f4..9df513cc 100644 --- a/IOB-WIN-FORM/IOB-WIN-FORM.csproj +++ b/IOB-WIN-FORM/IOB-WIN-FORM.csproj @@ -131,9 +131,10 @@ - - + + + Form diff --git a/IOB-WIN-FORM/Iob/Generic.Public.cs b/IOB-WIN-FORM/Iob/Generic.Public.cs deleted file mode 100644 index 8e46735d..00000000 --- a/IOB-WIN-FORM/Iob/Generic.Public.cs +++ /dev/null @@ -1,4053 +0,0 @@ -using EgwProxy.Ftp; -using IOB_UT_NEXT; -using IOB_UT_NEXT.Config; -using MapoSDK; -using MathNet.Numerics.Statistics; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using NLog; -using NLog.Targets.Wrappers; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.NetworkInformation; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Forms; -using System.Xml.Serialization; -using static IOB_UT_NEXT.BaseAlarmConf; -using static IOB_UT_NEXT.CustomObj; -using static IOB_UT_NEXT.DataModel.Fimat; -using static MapoSDK.WharehouseData; - -namespace IOB_WIN_FORM.Iob -{ - public partial class Generic : BaseObj - { - #region Public Fields - - public int numPzReqOdl = 0; - - #endregion Public Fields - - #region Public Constructors - - /// - /// 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 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 - { - return _connOk || DemoIn; - } - set - { - _connOk = value; - } - } - - /// - /// Contapezzi attuale - /// - public Int32 contapezziIOB - { - get - { - return tcMan.pzCountIOB; - } - set - { - tcMan.pzCountIOB = value; - } - } - - /// - /// Ultima lettura variabile contapezzi da CNC - /// - public Int32 contapezziPLC - { - get - { - return tcMan.pzCountPLC; - } - set - { - tcMan.pzCountPLC = 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 - { - get => 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; - } - } - - /// - /// Valore medio del TC rilevato x verifica derive sul delta variazione contapezzi - /// - public double plcAvgTc - { - get - { - double answ = tcMan.avgTC > 0 ? tcMan.avgTC : 1; - return answ; - } - } - - /// - /// DataOra dell'ultima lettura variabile contapezzi da CNC - /// - public DateTime plcLastPzRead - { - get - { - return tcMan.lastObservedData; - } - } - - /// - /// Determina se il contapezzi plc sia valido (lo è se data avvio adapter è prima di ultimo dato registrato in contapezzi) - /// - public bool plcPzCountValid - { - get => tcMan.lastObservedData > 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 - { - get => $@"{urlCommandIobFile("sendReboot")}?mac={GetMACAddress()}"; - } - - /// - /// URL per salvataggio dati conf YAML completi IOB... - /// - public string urlSaveConfYaml - { - get => $@"{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 - - /// - /// Esegue conversione in un dizionario di tipo string/string serializzando e deserializzando - /// - /// - /// - public static Dictionary ConvertToStringDict(Dictionary input) - { - return input.ToDictionary(pair => pair.Key, pair => pair.Value?.ToString()); - } - - /// - /// 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 = JsonConvert.SerializeObject(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(); - } - - //// provo a riconnettere SE abilitato tryRestart... - //if (adpTryRestart && !connectionOk) - //{ - // // controllo se sia scaduto periodi di veto al tryConnect... - // int waitRecMSec = utils.CRI("waitRecMSec"); - // // cerco se ci sia un valore in ovverride x il singolo IOB... - // if (IOBConfFull.General.WaitRecMsec > 0) - // { - // waitRecMSec = IOBConfFull.General.WaitRecMsec; - // } - // DateTime dtVeto = lastConnectTry.AddMilliseconds(waitRecMSec); - // if (DateTime.Now > dtVeto) - // { - // lgInfo($"Veto Time Elapsed | lastConnectTry: {lastConnectTry} | waitRecMSec {waitRecMSec} ms) | NOW tryConnect"); - // lastConnectTry = DateTime.Now; - // tryConnect(); - // } - //} - //currDispData.semIn = Semaforo.SR; - //processDisconnectedTask(); - //processMemoryDiscon(); - } - raiseRefresh(currDispData); - } - - public void ExecServerRequests() - { - // verifica se ci siano richieste da eseguire - if (QueueSrvReq.Count > 0) - { - if (MPOnline) - { - if (IobOnline) - { - // prendo elenco - List listaValori = QueueSrvReq.ToList(); - - foreach (var rawJob in listaValori) - { - // deserializzo... - JobTaskData jobTaskReq = JsonConvert.DeserializeObject(rawJob); - // processo! - var reqDict = JobTaskData.TaskDict(jobTaskReq.RawData); - if (reqDict.Count > 0) - { - var taskDone = ProcessTask(JobTaskData.TaskDict(jobTaskReq.RawData), jobTaskReq.CodTav); - // accodo task eseguiti... - string serVal = JsonConvert.SerializeObject(taskDone); - accodaServResp(jobTaskReq.CodTav, serVal); - } - } - - // svuoto! - QueueSrvReq = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueServResp", IOBConfFull.General.EnabRedisQue, redisMan); - } - } - } - } - - /// - /// 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}"; -#if false - try - { - Task.Run(async () => okProc = await ProcessOtherInfoAsync(iKey, item.Value)) - .GetAwaiter() - .GetResult(); - taskVal = okProc ? $"OK ProcessOtherInfoAsync | {iKey} | {item.Value}" : $"ERROR ProcessOtherInfoAsync | {iKey} | {item.Value}"; - } - catch (Exception ex) - { - lgError("ProcessOtherInfoAsync | Crash nel ponte Sync/Async: " + ex.Message); - } -#endif - break; - - default: - taskVal = $"taskReq: {tName} | key: {iKey} | val: {item.Value} | SKIPPED | NO EXEC"; - lgInfo($"Chiamata senza processing: taskOk: {taskOk} | taskVal: {taskVal}"); - break; - } - // aggiungo task! - taskDone.Add(item.Key, taskVal); - } - } - else - { - foreach (var item in task2exe) - { - // converto richiesta in enum... - taskType tName = taskType.nihil; - Enum.TryParse(item.Key, out tName); - string taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | SKIPPED (no BankConf) | NO EXEC"; - // aggiungo task! - taskDone.Add(item.Key, taskVal); - } - lgError($"Attenzione! memMap è nullo, non posso eseguire task2exe! Tutte le richieste sono state chiuse senza esecuzione"); - } - } - - return taskDone; - } - - /// - /// 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 callUrl(urlForceSplit, false); - fatto = (rawSplit != "KO") ? true : false; - } - // se multi gestisco il bit delle tavole... - else - { - foreach (string item in IOBConfFull.Device.MultiIobList) - { - // invio chiamata URL x reset ODL su macchina, ATTENZIONE scriviamo - // | al posto di "#" che in URL sarebbe filtrato... - fullUrl = $"{urlForceSplit}&multi={item}"; - rawSplit = await callUrl(fullUrl, false); - lgDebug($"Esecuzione forceSplit | URL: {fullUrl} | esito: {rawSplit}"); - } - fatto = (rawSplit == "OK") ? true : false; - } - } - catch (Exception exc) - { - lgError($"Eccezione in forceSplitOdl{Environment.NewLine}{exc}"); - } - // se okReport --> resetto contapezzi!!! - if (fatto) - { - contapezziPLC = 0; - contapezziIOB = 0; - } - } - else - { - lgError("Richiesto forceSplitOdl ma MP/IOB offline --> NON eseguito"); - } - } - else - { - lgError("Richiesto forceSplitOdl ma veto attivo --> NON eseguito"); - } - return fatto; - } - - /// - /// 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 = JsonConvert.SerializeObject(fullFlObj); - } - catch (Exception exc) - { - lgError($"FLog Errore in costruzione jsonPayload:{Environment.NewLine}{exc}"); - } - break; - - case urlType.SignIN: - evData currSigData = new evData(); - evJsonPayload fullEvObj = new evJsonPayload(); - fullEvObj.eventList = new List(); - // inizio processando ogni valore - foreach (var item in elencoValori) - { - valori = qDecodeIN(item); - CultureInfo provider = CultureInfo.InvariantCulture; - DateTime.TryParseExact(valori[0], "yyyyMMddHHmmssfff", provider, DateTimeStyles.AssumeLocal, out dtEve); - int.TryParse(valori[2], out counter); - currSigData = new evData() - { - valore = valori[1], - dtEve = dtEve, - dtCurr = DateTime.Now, - cnt = counter - }; - fullEvObj.eventList.Add(currSigData); - } - // conversione finale - try - { - answ = JsonConvert.SerializeObject(fullEvObj); - } - catch (Exception exc) - { - lgError($"SignIN Errore in costruzione jsonPayload:{Environment.NewLine}{exc}"); - } - break; - - case urlType.RawTransf: - BaseRawTransf currRTData = new BaseRawTransf(); -#if false - rawTransfJsonPayload fullRTObj = new rawTransfJsonPayload(); - fullRTObj.rawTransfData = new List(); - // inizio processando ogni valore - foreach (var item in elencoValori) - { - try - { - currRTData = JsonConvert.DeserializeObject(item); - } - catch (Exception exc) - { - lgError($"Eccezione in deserializzazione BaseRawTransf:{Environment.NewLine}{exc}"); - } - fullRTObj.rawTransfData.Add(currRTData); - } - // conversione finale - try - { - answ = JsonConvert.SerializeObject(fullRTObj); - } - catch (Exception exc) - { - lgError($"RawTransf Errore in costruzione jsonPayload:{Environment.NewLine}{exc}"); - } -#endif - - // provo una serializzazione "brutale", ovvero aggiungo alla stringa il - // valore di testa... - string rawResult = ""; - foreach (var item in elencoValori) - { - rawResult += $"{item},"; - } - if (rawResult.Length > 0) - { - rawResult = rawResult.Substring(0, rawResult.Length - 1); - } - answ = $"[{rawResult}]"; - - //hasVeto = "{" + $"\"rawTransfData\":[{rawResult}]" + "}"; - - break; - - case urlType.ULog: - int numVal = 0; - int matrOp = 0; - ulogData currUlData = new ulogData(); - ulogJsonPayload fullUlObj = new ulogJsonPayload(); - fullUlObj.fluxData = new List(); - // inizio processando ogni valore - foreach (var item in elencoValori) - { - valori = qDecodeIN(item); - CultureInfo provider = CultureInfo.InvariantCulture; - DateTime.TryParseExact(valori[0], "yyyyMMddHHmmssfff", provider, DateTimeStyles.AssumeLocal, out dtEve); - int.TryParse(valori[3], out matrOp); - int.TryParse(valori[5], out numVal); - int.TryParse(valori[6], out counter); - currUlData = new ulogData() - { - flux = valori[1], - valore = valori[2], - dtEve = dtEve, - dtCurr = DateTime.Now, - cnt = counter, - matrOpr = matrOp, - label = valori[4], - valNum = numVal - }; - fullUlObj.fluxData.Add(currUlData); - } - // conversione finale - try - { - answ = JsonConvert.SerializeObject(fullUlObj); - } - catch (Exception exc) - { - lgError($"ULog Errore in costruzione jsonPayload:{Environment.NewLine}{exc}"); - } - break; - - default: - break; - } - } - return answ; - } - - /// - /// 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 = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:LastSend"); - string rawVal = redisMan.redGetHashField(lastSendKey, keyReq); - if (!string.IsNullOrEmpty(rawVal)) - { - lastSend = JsonConvert.DeserializeObject(rawVal); - } - else - { - rawVal = JsonConvert.SerializeObject(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 = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:LastSend"); - string rawVal = JsonConvert.SerializeObject(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 = utils.callUrl(urlFixDailyDossier); - fatto = callResp == "OK"; - lgDebug($"Esecuzione ProcessAutoDossier completata --> esito: {callResp}"); - } - else - { - lgTrace("AutoSnapshotDossier DISABILITATO"); - } - } - // loggo se enabled - if (fatto) - { - lgTrace($"Effettuato ProcessAutoDossier, esito positivo | {DateTime.Now:HH.mm.ss}"); - } - } - - /// - /// 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 = utils.callUrl(urlFixDailyOdl); - fatto = autoOdlRes == "OK"; - // imposto x prox controllo veto a 10 min - VetoProcessAutoOdl = adesso.AddMinutes(10); - } - else - { - // imposto x prox controllo veto a 30 min - VetoProcessAutoOdl = adesso.AddMinutes(30); - } - break; - - case "DAILY_CONF_PZ": - // verifico inizio ODL, se è data di oggi NON eseguo... - if (dtStart.Date < adesso.Date) - { - // imposto x prox controllo veto a 10 min - VetoProcessAutoOdl = adesso.AddMinutes(10); - string autoOdlRes = ""; - if (!isMulti) - { - // chiamo stored x creare ODL giornalieri alla data + conferma pezzi... - autoOdlRes = utils.callUrl(urlFixDailyOdlConfPzCount); - } - else - { - // prendo il + vecchio... - foreach (var item in IOBConfFull.Device.MultiIobList) - { - fullUrl = $@"{urlCommand("fixDailyOdlConfPzCount")}{item}"; - autoOdlRes = await callUrl(fullUrl, false); - } - } - fatto = autoOdlRes == "OK"; - } - else - { - // imposto x prox controllo veto a 30 min - VetoProcessAutoOdl = adesso.AddMinutes(30); - } - break; - - case "SIMUL": - case "TIME": - // imposto x prox controllo veto a 1 min - VetoProcessAutoOdl = adesso.AddMinutes(1); - // controllo parametri validi - if (IOBConfFull.Odl.OdlDurationHours > 0 && IOBConfFull.Odl.IdleStateMin >= 0) - { - // leggo da server inizio ODL... se non multi 1 solo... - inizioOdl = DateTime.Now; - rawDataInizio = ""; - if (!isMulti) - { - rawDataInizio = await callUrl(urlInizioOdlIob, false); - DateTime.TryParse(rawDataInizio, out inizioOdl); - } - else - { - DateTime tmpData = DateTime.Now; - // prendo il + vecchio... - foreach (var item in IOBConfFull.Device.MultiIobList) - { - fullUrl = $"{urlInizioOdlIob}|{item}"; - rawDataInizio = await callUrl(fullUrl, false); - DateTime.TryParse(rawDataInizio, out tmpData); - inizioOdl = (tmpData < inizioOdl) ? tmpData : inizioOdl; - } - } - // verifico se sia scaduto... - if (inizioOdl.AddHours(IOBConfFull.Odl.OdlDurationHours) < adesso) - { - string rawIdle = ""; - int idlePeriod = 0; - if (!isMulti) - { - // controllo SE sono fermo (spento o in manuale) per il - // periodo minimo richiesto... - rawIdle = await callUrl(urlIdleTime, false); - int.TryParse(rawIdle, out idlePeriod); - } - else - { - int tmpIdle = 0; - // prendo il + grande... - foreach (var item in IOBConfFull.Device.MultiIobList) - { - fullUrl = $"{urlIdleTime}|{item}"; - rawIdle = await callUrl(fullUrl, false); - int.TryParse(rawIdle, out tmpIdle); - idlePeriod = tmpIdle > idlePeriod ? tmpIdle : idlePeriod; - } - } - if (idlePeriod >= IOBConfFull.Odl.IdleStateMin) - { - callChangeODL = true; - } - } - } - // se NON fosse scaduto MA è simulato esegue controllo sul num pezzi da - // fare, se sfora (RANDOM) > +(50...110)% --> cambia! - if (!callChangeODL && IOBConfFull.Odl.ChangeOdlMode == "SIMUL") - { - var rawCount = await callUrl(urlGetNumPzCurrODL, false); - if (int.TryParse(rawCount, out var numPzReqOdl)) - { - int limitQty = (numPzReqOdl * rndGen.Next(150, 210)) / 100; - callChangeODL = contapezziPLC > limitQty; - } - } - break; - - default: - break; - } - - // vero processing... - if (callChangeODL) - { - lgTrace("Chiamata: ProcessAutoOdlAsync --> forceSplitODL"); - fatto = await forceSplitOdl(); - // metto contapezzi a zero.. - contapezziPLC = 0; - // aspetto 2 sec per proseguire dopo force split... - await Task.Delay(2000); - pzCountResetted = false; - lgInfo("Esecuzione ProcessAutoOdlAsync completata --> pzCountResetted = false"); - // imposto x prox controllo veto a 15 min - VetoProcessAutoOdl = adesso.AddMinutes(15); - } - } - } - else - { - lgTrace("ProcessAutoOdlAsync | AutoChangeOdl DISABILITATO"); - } - // loggo se ok + processing post opzionali - if (fatto) - { - lgInfo($"Effettuato ProcessAutoOdlAsync | mode: {IOBConfFull.Odl.ChangeOdlMode}"); - processAutoOdlExtraStep(); - } - } - - /// - /// 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; - } - -#if false - /// - /// Processa esecuzione task ricevuti - /// - /// - /// - /// - public async Task> 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) - { - string logMsg = $"Task2Exe S01: {task2exe.Count} task ricevuti"; - if (!string.IsNullOrEmpty(codTav)) - { - logMsg += $" | codTav: {codTav}"; - } - lgInfo(logMsg); - int idTask = 0; - foreach (var item in task2exe) - { - idTask++; - lgInfo($"[{idTask:00}] - {item.Key} --> {item.Value}"); - // verifico SE il task abbia un duplo writeLink e nel caso lo aggiungo... - var linkVal = getOptWriteLink(item.Key); - if (!string.IsNullOrEmpty(linkVal)) - { - // aggiungo a task2add SE manca... - if (!task2Add.ContainsKey(linkVal)) - { - task2Add.Add(linkVal, item.Value); - } - lgInfo($"Aggiunta task linked: {linkVal} -> {item.Value}"); - } - } - // se ho task Link da aggiungere li aggiungo! - if (task2Add.Count > 0) - { - foreach (var item in task2Add) - { - task2exe.Add(item.Key, item.Value); - } - } - // chiamo procedura esecutiva (diversa x ogni IOB) - taskDone = executeTasks(task2exe, codTav); - lgInfo($"Task2Exe S02: eseguiti {taskDone.Count} task"); - // loggo tutti i task done... - foreach (var item in taskDone) - { - sendToTaskWatch(item.Key, item.Value, codTav); - } - // ora chiamo la cancellazione dei task eseguiti... - foreach (var item in taskDone) - { - await remTask2exe(item.Key, item.Value, codTav); - } - } - } - return taskDone; - } -#endif - - /// - /// 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 = utils.callUrl(calcUrl); - lgTrace($"Lettura ODL dall'url {calcUrl} --> {lastIdxODL}"); - // se ho valori in coda da trasmettere uso dati REDIS - if (forceCountRec) - { - // uso dati da TCiclo registrati... - calcUrl = string.IsNullOrEmpty(forceMach) ? urlGetPzCountRec : urlGetPzCountRec.Replace(IOBConfFull.General.CodIOB, forceMach); - currServerCount = utils.callUrl(calcUrl); - lgInfo($"Lettura contapezzi da TCiclo registrati dall'url {calcUrl} --> num pz: {currServerCount}"); - } - else - { - // uso il contapezzi dichiarato dall'IOB stesso - calcUrl = string.IsNullOrEmpty(forceMach) ? urlGetPzCount : urlGetPzCount.Replace(IOBConfFull.General.CodIOB, forceMach); - currServerCount = utils.callUrl(calcUrl); - lgInfo($"Lettura contapezzi dall'url {calcUrl} --> {currServerCount}"); - } - // controllo: SE NON HO ODL... - if (string.IsNullOrEmpty(lastIdxODL) || lastIdxODL == "0") - { - // NON AGGIORNO - contapezziIOB = contapezziPLC; - lgError($"Errore lettura ODL (vuoto) resta tutto invariato contapezzi: {contapezziIOB} | contapezziPLC {contapezziPLC}"); - } - else - { - if (!string.IsNullOrEmpty(currServerCount)) - { - // se "-1" resto a ultimo... - if (currServerCount != "-1") - { - int newVal = -1; - Int32.TryParse(currServerCount, out newVal); - contapezziIOB = newVal > -1 ? newVal : contapezziIOB; - lgInfo("Ricevuta conferma da server di {0} pezzi registrati per ODL", currServerCount); - } - else - { - // NON AGGIORNO - contapezziIOB = contapezziPLC; - lgError($"Errore lettura contapezzi (-1) - uso contapezziPLC --> {contapezziPLC}"); - } - } - else - { - // registro che ho UN NUOVO ODL - lgInfo($"Lettura ODL in pzCntReload, currIdxODL {currIdxODL} --> lastIdxODL {lastIdxODL}"); - // se ODL differente e NUOVO è zero --> resetto! - if (currIdxODL.ToString() != lastIdxODL && lastIdxODL == "0") - { - // cambiato ODL quindi reset... - contapezziIOB = 0; - lgInfo("Nuovo ODL==0, RESET contapezzi (-->ZERO)"); - } - } - // provo a salvare nuovo ODL - int.TryParse(lastIdxODL, out currIdxODL); - lgInfo($"ODL | currIdxODL: {currIdxODL}"); - } - } - } - else - { - // se server NON pronto... - contapezziIOB = contapezziPLC; - lgWarn("Errore server NON pronto in pzCntReload"); - } - } - } - } - - /// - /// 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 = JsonConvert.SerializeObject(ActiveAlarmList); - string resp = utils.CallUrlPost(lastUrl, rawData); - //string resp = utils.callUrlAsync(lastUrl, rawData); - if (resp != null) - { - if (resp.ToUpper() == "OK") - { - // segnalo okReport - fatto = true; - } - } - else - { - lgError($"Errore in invio richiesta registrazione allarme | resp: {resp} | URL: {lastUrl}{Environment.NewLine}Payload:{Environment.NewLine}{rawData}"); - } - } - return fatto; - } - - /// - /// 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 callUrlWithPayloadAsync(lastUrl, payload, doAsync); - - // valutare invio REST alternativo... -#if false - // invio alternativo nuovo - if (string.IsNullOrEmpty(answ)) - { - answ = utils.ExecCallPostPlain(lastUrl, payload); - } -#endif - - // loggo! - lgInfo($"[SEND payload] TipoURL: {tipoUrl} | {listQueueVal.Count} records --> {answ}"); - // se "OK" verde, altrimenti errore --> ROSSO - if (answ.Contains("OK")) - { - fatto = true; - currDispData.semOut = Semaforo.SV; - // se oltre 1 min NON era online --> check pezzi! - if (DateTime.Now.Subtract(lastIobOnline).TotalMinutes > 1 && !isMulti) - { - lgInfo($"sendDataBlock --> offline timeout ({lastIobOnline}) --> pzCntReload(true)"); - pzCntReload(true); - } - lastIobOnline = DateTime.Now; - } - else - { - currDispData.semOut = Semaforo.SR; - } - } - else - { - lgInfo($"[SERVER KO] {listQueueVal.Count}"); - } - } - else - { - currDispData.semOut = Semaforo.SV; - // loggo! - lgInfo($"{listQueueVal.Count} records --> [SIM]"); - } - nSendOut += listQueueVal.Count; - // riporto cosa inviato - currDispData.newUrlCallData = lastUrl; - // aggiorno data ultimo watchdog... - lastWatchDog = DateTime.Now; - } - catch - { - currDispData.semOut = Semaforo.SR; - } - } - raiseRefresh(currDispData); - return fatto; - } - - /// - /// 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 callUrl(lastUrl, false); - // loggo! - lgDebug(string.Format("[SEND] {0} -> {1}", queueVal, answ)); - // se oltre 1 min NON era online --> check pezzi! - if (DateTime.Now.Subtract(lastIobOnline).TotalMinutes > 1 && !isMulti) - { - lgInfo($"sendToMoonPro --> offline timeout ({lastIobOnline}) --> pzCntReload(true)"); - pzCntReload(true); - } - // se richiesto effettuo refresh contapezzi - if (needRefreshPzCount && !isMulti) - { - pzCntReload(true); - needRefreshPzCount = false; - } - lastIobOnline = DateTime.Now; - // se "OK" verde, altrimenti errore --> ROSSO - if (answ == "OK") - { - currDispData.semOut = Semaforo.SV; - } - else - { - currDispData.semOut = Semaforo.SR; - } - } - else - { - lgError(string.Format("[SERVER KO] {0}", queueVal)); - } - } - else - { - currDispData.semOut = Semaforo.SV; - // loggo! - lgDebug(string.Format("{0} -> [SIM]", queueVal)); - } - nSendOut++; - currDispData.newUrlCallData = lastUrl; - // aggiorno data ultimo watchdog... - lastWatchDog = DateTime.Now; - } - catch - { - currDispData.semOut = Semaforo.SR; - } - raiseRefresh(currDispData); - } - else - { - lgTrace($"Richiesto invio valore nullo, salto"); - } - } - - /// - /// 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 = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:CallStats"); - await CallMetricsCollector.SaveToRedisAsync(redisMan.currDb, callKey, false); - - // chiudo la connessione all'adapter... - tryDisconnect(); - dtStopAdp = DateTime.Now; - adpTryRestart = tryRestart; - adpRunning = false; - // chiudo! - parentForm.displayTaskAndLog("Adapter Stopped.", true); - DateTime adesso = DateTime.Now; - lastReadPLC = adesso; - lastPING = adesso; - parentForm.commPlcActive = false; - } - - /// - /// 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) - { - if (i == 4) resetWebClients(); // Reset specifico a metà tentativi - await Task.Delay(rand.Next(150, 500)); - } - - string resp = await callUrl(urlAlive, false); - if (resp == "OK") return true; - } - catch (Exception ex) - { - if (i == 0) lgError($"Errore API Check: {ex.Message}"); - } - } - return false; - } - -#if false - /// - /// Processing di una risposta raw di task2exe - /// - /// Risposta come string RAW - /// Cod Tav (opzionale) - private async Task ProcessResp(string resp, string codTav) - { - Dictionary task2exe = new Dictionary(); - Dictionary taskDone = new Dictionary(); - if (!string.IsNullOrEmpty(resp) && resp.Length > 2) - { - try - { - task2exe = JsonConvert.DeserializeObject>(resp); - // se ho da fare chiamo esecuzione.. - if (task2exe.Count > 0) - { - taskDone = await ProcessTask(task2exe, codTav); - } - } - catch (Exception exc) - { - lgError($"Eccezione in ServerGetRequestsAsync.ProcessResp:{Environment.NewLine}{exc}"); - } - } - } -#endif - - /// - /// 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 - } -} \ No newline at end of file diff --git a/IOB-WIN-FORM/Iob/Generic.Protected.cs b/IOB-WIN-FORM/Iob/Generic.cs similarity index 58% rename from IOB-WIN-FORM/Iob/Generic.Protected.cs rename to IOB-WIN-FORM/Iob/Generic.cs index 27835e2d..5b06e56a 100644 --- a/IOB-WIN-FORM/Iob/Generic.Protected.cs +++ b/IOB-WIN-FORM/Iob/Generic.cs @@ -1,7 +1,11 @@ using EgwProxy.Ftp; using IOB_UT_NEXT; +using IOB_UT_NEXT.Config; +using IOB_WIN_FORM.Iob.Services; using MapoSDK; +using MathNet.Numerics.Statistics; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using NLog; using NLog.Targets; using System; @@ -16,12 +20,12 @@ using System.Net.NetworkInformation; using System.Reflection; using System.Runtime.Serialization.Formatters.Binary; using System.Security.AccessControl; +using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; -using System.Xml.Serialization; using YamlDotNet.Core.Tokens; using static IOB_UT_NEXT.BaseAlarmConf; using static IOB_UT_NEXT.CustomObj; @@ -32,6 +36,3883 @@ namespace IOB_WIN_FORM.Iob { public partial class Generic : BaseObj { + #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 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 + { + return _connOk || DemoIn; + } + set + { + _connOk = value; + } + } + + /// + /// Contapezzi attuale + /// + public Int32 contapezziIOB + { + get + { + return tcMan.pzCountIOB; + } + set + { + tcMan.pzCountIOB = value; + } + } + + /// + /// Ultima lettura variabile contapezzi da CNC + /// + public Int32 contapezziPLC + { + get + { + return tcMan.pzCountPLC; + } + set + { + tcMan.pzCountPLC = 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 + { + get => 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; + } + } + + /// + /// Valore medio del TC rilevato x verifica derive sul delta variazione contapezzi + /// + public double plcAvgTc + { + get + { + double answ = tcMan.avgTC > 0 ? tcMan.avgTC : 1; + return answ; + } + } + + /// + /// DataOra dell'ultima lettura variabile contapezzi da CNC + /// + public DateTime plcLastPzRead + { + get + { + return tcMan.lastObservedData; + } + } + + /// + /// Determina se il contapezzi plc sia valido (lo è se data avvio adapter è prima di ultimo dato registrato in contapezzi) + /// + public bool plcPzCountValid + { + get => tcMan.lastObservedData > 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 + { + get => $@"{urlCommandIobFile("sendReboot")}?mac={GetMACAddress()}"; + } + + /// + /// URL per salvataggio dati conf YAML completi IOB... + /// + public string urlSaveConfYaml + { + get => $@"{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 + + /// + /// Esegue conversione in un dizionario di tipo string/string serializzando e deserializzando + /// + /// + /// + public static Dictionary ConvertToStringDict(Dictionary input) + { + return 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 = DataSerializer.Serialize(newVal); + // --> accodo (valore già formattato)! + QueueRawTransf.Enqueue(encodedVal); + // se abilitato controllo coda Max (superiore a 0...) + if (maxQueueRawTransf > 0) + { + // se ho una coda superiore a max ammesso + if (QueueRawTransf.Count > maxQueueRawTransf) + { + // elimino valori iniziali fino a tornare al max ammesso... + while (QueueRawTransf.Count > maxQueueRawTransf) + { + string currVal = ""; + QueueRawTransf.TryDequeue(out currVal); + lgInfo($"Eliminazione da coda RawTransf per superamento maxLengh: {currVal}"); + } + } + } + // loggo! + lgTrace(string.Format("[QUEUE-RTRANSF] {0}", encodedVal)); + counterRawTransf++; + if (counterRawTransf > 9999) + { + counterRawTransf = 0; + } + } + + /// + /// 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(); + } + + //// provo a riconnettere SE abilitato tryRestart... + //if (adpTryRestart && !connectionOk) + //{ + // // controllo se sia scaduto periodi di veto al tryConnect... + // int waitRecMSec = utils.CRI("waitRecMSec"); + // // cerco se ci sia un valore in ovverride x il singolo IOB... + // if (IOBConfFull.General.WaitRecMsec > 0) + // { + // waitRecMSec = IOBConfFull.General.WaitRecMsec; + // } + // DateTime dtVeto = lastConnectTry.AddMilliseconds(waitRecMSec); + // if (DateTime.Now > dtVeto) + // { + // lgInfo($"Veto Time Elapsed | lastConnectTry: {lastConnectTry} | waitRecMSec {waitRecMSec} ms) | NOW tryConnect"); + // lastConnectTry = DateTime.Now; + // tryConnect(); + // } + //} + //currDispData.semIn = Semaforo.SR; + //processDisconnectedTask(); + //processMemoryDiscon(); + } + raiseRefresh(currDispData); + } + + public void ExecServerRequests() + { + // verifica se ci siano richieste da eseguire + if (QueueSrvReq.Count > 0) + { + if (MPOnline) + { + if (IobOnline) + { + // prendo elenco + List listaValori = QueueSrvReq.ToList(); + + foreach (var rawJob in listaValori) + { + // deserializzo... + JobTaskData jobTaskReq = JsonConvert.DeserializeObject(rawJob); + // processo! + var reqDict = JobTaskData.TaskDict(jobTaskReq.RawData); + if (reqDict.Count > 0) + { + var taskDone = ProcessTask(JobTaskData.TaskDict(jobTaskReq.RawData), jobTaskReq.CodTav); + // accodo task eseguiti... + string serVal = JsonConvert.SerializeObject(taskDone); + accodaServResp(jobTaskReq.CodTav, serVal); + } + } + + // svuoto! + QueueSrvReq = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueServResp", IOBConfFull.General.EnabRedisQue, redisMan); + } + } + } + } + + /// + /// 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 utils.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 utils.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 = JsonConvert.SerializeObject(fullFlObj); + } + catch (Exception exc) + { + lgError($"FLog Errore in costruzione jsonPayload:{Environment.NewLine}{exc}"); + } + break; + + case urlType.SignIN: + evData currSigData = new evData(); + evJsonPayload fullEvObj = new evJsonPayload(); + fullEvObj.eventList = new List(); + // inizio processando ogni valore + foreach (var item in elencoValori) + { + valori = qDecodeIN(item); + CultureInfo provider = CultureInfo.InvariantCulture; + DateTime.TryParseExact(valori[0], "yyyyMMddHHmmssfff", provider, DateTimeStyles.AssumeLocal, out dtEve); + int.TryParse(valori[2], out counter); + currSigData = new evData() + { + valore = valori[1], + dtEve = dtEve, + dtCurr = DateTime.Now, + cnt = counter + }; + fullEvObj.eventList.Add(currSigData); + } + // conversione finale + try + { + answ = JsonConvert.SerializeObject(fullEvObj); + } + catch (Exception exc) + { + lgError($"SignIN Errore in costruzione jsonPayload:{Environment.NewLine}{exc}"); + } + break; + + case urlType.RawTransf: + BaseRawTransf currRTData = new BaseRawTransf(); + + // 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 = JsonConvert.SerializeObject(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 = DataSerializer.Deserialize(rawVal); + } + else + { + rawVal = DataSerializer.Serialize(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 = DataSerializer.Serialize(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 = utils.callUrl(urlFixDailyDossier); + fatto = callResp == "OK"; + lgDebug($"Esecuzione ProcessAutoDossier completata --> esito: {callResp}"); + } + else + { + lgTrace("AutoSnapshotDossier DISABILITATO"); + } + } + // loggo se enabled + if (fatto) + { + lgTrace($"Effettuato ProcessAutoDossier, esito positivo | {DateTime.Now:HH.mm.ss}"); + } + } + + /// + /// 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 = utils.callUrl(urlFixDailyOdl); + fatto = autoOdlRes == "OK"; + // imposto x prox controllo veto a 10 min + VetoProcessAutoOdl = adesso.AddMinutes(10); + } + else + { + // imposto x prox controllo veto a 30 min + VetoProcessAutoOdl = adesso.AddMinutes(30); + } + break; + + case "DAILY_CONF_PZ": + // verifico inizio ODL, se è data di oggi NON eseguo... + if (dtStart.Date < adesso.Date) + { + // imposto x prox controllo veto a 10 min + VetoProcessAutoOdl = adesso.AddMinutes(10); + string autoOdlRes = ""; + if (!isMulti) + { + // chiamo stored x creare ODL giornalieri alla data + conferma pezzi... + autoOdlRes = utils.callUrl(urlFixDailyOdlConfPzCount); + } + else + { + // prendo il + vecchio... + foreach (var item in IOBConfFull.Device.MultiIobList) + { + fullUrl = $@"{urlCommand("fixDailyOdlConfPzCount")}{item}"; + autoOdlRes = await utils.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 utils.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 utils.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 utils.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 utils.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 utils.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 = utils.callUrl(calcUrl); + lgTrace($"Lettura ODL dall'url {calcUrl} --> {lastIdxODL}"); + // se ho valori in coda da trasmettere uso dati REDIS + if (forceCountRec) + { + // uso dati da TCiclo registrati... + calcUrl = string.IsNullOrEmpty(forceMach) ? urlGetPzCountRec : urlGetPzCountRec.Replace(IOBConfFull.General.CodIOB, forceMach); + currServerCount = utils.callUrl(calcUrl); + lgInfo($"Lettura contapezzi da TCiclo registrati dall'url {calcUrl} --> num pz: {currServerCount}"); + } + else + { + // uso il contapezzi dichiarato dall'IOB stesso + calcUrl = string.IsNullOrEmpty(forceMach) ? urlGetPzCount : urlGetPzCount.Replace(IOBConfFull.General.CodIOB, forceMach); + currServerCount = utils.callUrl(calcUrl); + lgInfo($"Lettura contapezzi dall'url {calcUrl} --> {currServerCount}"); + } + // controllo: SE NON HO ODL... + if (string.IsNullOrEmpty(lastIdxODL) || lastIdxODL == "0") + { + // NON AGGIORNO + contapezziIOB = contapezziPLC; + lgError($"Errore lettura ODL (vuoto) resta tutto invariato contapezzi: {contapezziIOB} | contapezziPLC {contapezziPLC}"); + } + else + { + if (!string.IsNullOrEmpty(currServerCount)) + { + // se "-1" resto a ultimo... + if (currServerCount != "-1") + { + int newVal = -1; + Int32.TryParse(currServerCount, out newVal); + contapezziIOB = newVal > -1 ? newVal : contapezziIOB; + lgInfo("Ricevuta conferma da server di {0} pezzi registrati per ODL", currServerCount); + } + else + { + // NON AGGIORNO + contapezziIOB = contapezziPLC; + lgError($"Errore lettura contapezzi (-1) - uso contapezziPLC --> {contapezziPLC}"); + } + } + else + { + // registro che ho UN NUOVO ODL + lgInfo($"Lettura ODL in pzCntReload, currIdxODL {currIdxODL} --> lastIdxODL {lastIdxODL}"); + // se ODL differente e NUOVO è zero --> resetto! + if (currIdxODL.ToString() != lastIdxODL && lastIdxODL == "0") + { + // cambiato ODL quindi reset... + contapezziIOB = 0; + lgInfo("Nuovo ODL==0, RESET contapezzi (-->ZERO)"); + } + } + // provo a salvare nuovo ODL + int.TryParse(lastIdxODL, out currIdxODL); + lgInfo($"ODL | currIdxODL: {currIdxODL}"); + } + } + } + else + { + // se server NON pronto... + contapezziIOB = contapezziPLC; + lgWarn("Errore server NON pronto in pzCntReload"); + } + } + } + } + + /// + /// 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 = JsonConvert.SerializeObject(ActiveAlarmList); + string resp = utils.CallUrlPost(lastUrl, rawData); + //string resp = utils.callUrlAsync(lastUrl, rawData); + if (resp != null) + { + if (resp.ToUpper() == "OK") + { + // segnalo okReport + fatto = true; + } + } + else + { + lgError($"Errore in invio richiesta registrazione allarme | resp: {resp} | URL: {lastUrl}{Environment.NewLine}Payload:{Environment.NewLine}{rawData}"); + } + } + return fatto; + } + + /// + /// 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 utils.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 utils.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 utils.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 /// @@ -638,7 +4519,7 @@ namespace IOB_WIN_FORM.Iob get { Dictionary answ = new Dictionary(); - string redKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:POdlSent"); + string redKey = GetPOdlSentKey(); string rawData = redisMan.getRSV(redKey); if (!string.IsNullOrEmpty(rawData)) { @@ -657,7 +4538,7 @@ namespace IOB_WIN_FORM.Iob set { string rawVal = JsonConvert.SerializeObject(value); - string redKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:POdlSent"); + string redKey = GetPOdlSentKey(); redisMan.setRSV(redKey, rawVal); lgDebug($"Salvataggio status POdlSentFileArch | {value.Count} record"); } @@ -730,7 +4611,7 @@ namespace IOB_WIN_FORM.Iob /// protected string rKeyFluxMem { - get => redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:FluxMem"); + get => GetFluxMemKey(); } protected Random rndGen { get; set; } = new Random(); @@ -2050,33 +5931,7 @@ namespace IOB_WIN_FORM.Iob /// protected virtual bool iobWriteLocalUSTD() { - bool answ = false; -#if false - // conf ftp - var ftpConf = IOBConfFull.Special.FtpConf; - // salvo articoli - string locDir = ftpConf.DirLocal; - bool addHeader = ftpConf.CsvAddHeader; - string basePath = Application.StartupPath; - string tempDir = Path.Combine(basePath, locDir); - lgInfo($"iobWriteLocalCSV | locDir: {locDir} | addHeader: {addHeader} | tempDir: {tempDir}"); - baseUtils.checkDir(tempDir); - string filePath = Path.Combine(tempDir, "articoli.csv"); - answ = DataExport.SaveToCsv(ListaArticoli, filePath, addHeader); - if (answ) - { - lgInfo($"CSV: saved ART file as articoli.csv at {filePath}"); - // salvo PODL - string csvName = $"{DateTime.Now:dd-MM-yyyy}.csv"; - filePath = Path.Combine(tempDir, $"{csvName}"); - answ = DataExport.SaveToCsv(ListaJobs, filePath, addHeader); - if (answ) - { - lgInfo($"CSV: saved PODL file {csvName} at {filePath}"); - } - } -#endif - return answ; + return false; } /// @@ -2327,7 +6182,7 @@ namespace IOB_WIN_FORM.Iob // PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione Task.Run(async () => { - var rawListPODL = await callUrl(urlGetNextPODL, false); + var rawListPODL = await utils.callUrlAsync(urlGetNextPODL); if (!string.IsNullOrEmpty(rawListPODL)) { reqPOdlList = JsonConvert.DeserializeObject>(rawListPODL) ?? new List(); @@ -2782,38 +6637,28 @@ namespace IOB_WIN_FORM.Iob // leggo contenuto XML string rawData = File.ReadAllText(recipeFile); // deserializza file XML x recuperare righe consumo - XmlSerializer serializer = new XmlSerializer(typeof(ARecipe)); - using (StringReader reader = new StringReader(rawData)) + var currRecipe = XmlDataSerializer.Deserialize(rawData); + if (currRecipe != null) { - try + // 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)) { - var currRecipe = (ARecipe)serializer.Deserialize(reader); - if (currRecipe != null) - { - // recupero data-ora ricetta da campo string - DateTime recExeDt = DateTime.Today; - DateTime.TryParse(currRecipe.DesRecipe.DesData.Date, out recExeDt); - // calcolo week da data-ora file... - week = cal.GetWeekOfYear(recExeDt, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday); - // verifico se ci sia la settimana indicata in elenco... - string currWeek = $"{recExeDt:yyyy}-{week:00}"; - if (!weeksProc.Contains(currWeek)) - { - weeksProc.Add(currWeek); - } - // sposto file x in area week - string rName = Path.GetFileName(recipeFile); - archPath = Path.Combine(archBasePath, $"{recExeDt:yyyy}-{week:00}"); - baseUtils.checkDir(archPath); - string destPath = Path.Combine(archPath, rName); - // sposto files - File.Move(recipeFile, destPath); - } - } - catch (Exception exc) - { - lgError($"Eccezione in RecipeDoArchiveWeek{Environment.NewLine}{exc}"); + 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; } @@ -2875,14 +6720,13 @@ namespace IOB_WIN_FORM.Iob redHashWeek.Add(cWeek, rawWeek); } // salvo in redis... - string fullKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:WeekStats"); + string fullKey = GetWeekStatsKey(); var okHashDict = redisMan.redSaveHashDict(fullKey, redHashWeek); // invio ANCHE in MP-IO l'update delle info... string remUrl = urlSetHashDict; string dictPayload = JsonConvert.SerializeObject(redHashWeek); - //await callUrlWithPayloadAsync(remUrl, dictPayload, false); - await callUrlWithPayloadAsync(remUrl, dictPayload, true); + await utils.callUrlAsync(remUrl, dictPayload); } } @@ -2905,7 +6749,7 @@ namespace IOB_WIN_FORM.Iob // verifico se ci sia la settimana indicata in elenco... string currWeek = $"{adesso:yyyy}-{week:00}"; // recupero elenco delle settimane da processare da redis/WeekStats - string fullKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:WeekStats"); + string fullKey = GetWeekStatsKey(); Dictionary currStats = redisMan.redGetHashDict(fullKey); if (currStats != null && currStats.Count > 0) { @@ -3017,45 +6861,57 @@ namespace IOB_WIN_FORM.Iob // leggo contenuto XML string rawData = File.ReadAllText(recipeFile); // deserializza file XML x recuperare righe consumo - XmlSerializer serializer = new XmlSerializer(typeof(ARecipe)); - using (StringReader reader = new StringReader(rawData)) + var currRecipe = XmlDataSerializer.Deserialize(rawData); + if (currRecipe != null) { - try + if (currRecipe.ColRecipe != null && currRecipe.ColRecipe.Count > 0) { - var currRecipe = (ARecipe)serializer.Deserialize(reader); - if (currRecipe != null) + List RigheConsumi = new List(); + foreach (var rigaComp in currRecipe.ColRecipe) { - 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(); - } + ColData datiComp = rigaComp.ColData; + RigheConsumi.Add(datiComp); } - } - catch (Exception exc) - { - lgError($"Eccezione in RecipeGetCons{Environment.NewLine}{exc}"); + // 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 /// @@ -3130,7 +6986,7 @@ namespace IOB_WIN_FORM.Iob // PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione Task.Run(async () => { - var rawListPODL = await callUrl(urlGetNextPODL, false); + var rawListPODL = await utils.callUrlAsync(urlGetNextPODL); if (!string.IsNullOrEmpty(rawListPODL)) { try @@ -3323,7 +7179,7 @@ namespace IOB_WIN_FORM.Iob { bool answ = false; DateTime dtCurr = DateTime.Now; - string resp = await callUrl($"{urlODLClose}{idxOdl}&dtEve={dtRif}&dtCurr={dtCurr}", false); + string resp = await utils.callUrlAsync($"{urlODLClose}{idxOdl}&dtEve={dtRif}&dtCurr={dtCurr}"); answ = resp == "OK"; return answ; } @@ -3344,7 +7200,7 @@ namespace IOB_WIN_FORM.Iob Task.Run(async () => { // invio chiamata URL x chiusura ODL su macchina - string callResp = await callUrl(fullUrl, false); + string callResp = await utils.callUrlAsync(fullUrl); answ = callResp == "OK"; }) .GetAwaiter() @@ -3495,7 +7351,7 @@ namespace IOB_WIN_FORM.Iob // invio e salvo... string remUrl = urlSaveMachIobConf; string dictPayload = JsonConvert.SerializeObject(currDict); - await callUrlWithPayloadAsync(remUrl, dictPayload, true); + await utils.callUrlAsync(remUrl, dictPayload); lgTrace("Invio MachineConf effettuato"); } } @@ -3522,9 +7378,6 @@ namespace IOB_WIN_FORM.Iob string url2call = $"{urlSetOptVal}pName={paramName}&pValue={paramValue}"; lgInfo("chiamata URL " + url2call); await utils.callUrlAsync(url2call); -#if false - utils.callUrlNow(url2call); -#endif } } } @@ -3557,7 +7410,7 @@ namespace IOB_WIN_FORM.Iob Task.Run(async () => { // invio chiamata URL x chiusura ODL su macchina - string callResp = await callUrl(fullUrl, false); + string callResp = await utils.callUrlAsync(fullUrl); answ = callResp == "OK"; }) .GetAwaiter() @@ -3587,7 +7440,7 @@ namespace IOB_WIN_FORM.Iob Task.Run(async () => { // invio chiamata URL x chiusura ODL su macchina - string callResp = await callUrl(fullUrl, false); + string callResp = await utils.callUrlAsync(fullUrl); answ = callResp == "OK"; }) .GetAwaiter() @@ -4140,7 +7993,7 @@ namespace IOB_WIN_FORM.Iob try { // invio chiamata URL x chiusura ODL su macchina - string callResp = await callUrl(fullUrl, false); + string callResp = await utils.callUrlAsync(fullUrl); fatto = (callResp != "KO") ? true : false; } catch @@ -4184,7 +8037,7 @@ namespace IOB_WIN_FORM.Iob try { // invio chiamata URL x chiusura ODL su macchina - string callResp = await callUrl(fullUrl, false); + string callResp = await utils.callUrlAsync(fullUrl); fatto = (callResp != "KO") ? true : false; } catch @@ -4230,7 +8083,7 @@ namespace IOB_WIN_FORM.Iob try { // invio chiamata URL x chiusura ODL su macchina - string callResp = await callUrl(fullUrl, false); + string callResp = await utils.callUrlAsync(fullUrl); fatto = (callResp != "KO") ? true : false; } catch @@ -4254,7 +8107,7 @@ namespace IOB_WIN_FORM.Iob Task.Run(async () => { // invio chiamata URL x chiusura ODL su macchina - string callResp = await callUrl(fullUrl, false); + string callResp = await utils.callUrlAsync(fullUrl); fatto = (callResp != "KO") ? true : false; }) .GetAwaiter() @@ -4286,7 +8139,7 @@ namespace IOB_WIN_FORM.Iob Task.Run(async () => { // invio chiamata URL x chiusura ODL su macchina - string callResp = await callUrl(fullUrl, false); + string callResp = await utils.callUrlAsync(fullUrl); fatto = (callResp != "KO") ? true : false; }) .GetAwaiter() @@ -4315,7 +8168,7 @@ namespace IOB_WIN_FORM.Iob // PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione Task.Run(async () => { - string resp = await callUrl(urlEncoded, false); + string resp = await utils.callUrlAsync(urlEncoded); int.TryParse(resp, out answ); }) .GetAwaiter() @@ -4581,7 +8434,7 @@ namespace IOB_WIN_FORM.Iob try { // invio chiamata URL x avvio PODL su macchina - string rawSplit = await callUrl(fullUrl, false); + string rawSplit = await utils.callUrlAsync(fullUrl); fatto = (rawSplit != "KO") ? true : false; } catch @@ -4606,7 +8459,7 @@ namespace IOB_WIN_FORM.Iob try { // invio chiamata URL x avvio PODL su macchina - string rawSplit = await callUrl(fullUrl, false); + string rawSplit = await utils.callUrlAsync(fullUrl); fatto = (rawSplit != "KO") ? true : false; } catch @@ -4907,7 +8760,7 @@ namespace IOB_WIN_FORM.Iob private async Task currOdlStart() { DateTime inizioOdl = DateTime.Now; - string rawDataInizio = await callUrl(urlInizioOdlIob, false); + string rawDataInizio = await utils.callUrlAsync(urlInizioOdlIob); DateTime.TryParse(rawDataInizio, out inizioOdl); return inizioOdl; } @@ -5043,14 +8896,11 @@ namespace IOB_WIN_FORM.Iob { if (i > 0) { - // Al terzo tentativo fallito resetto i client - if (i == 3) resetWebClients(); - int delay = i == 3 ? rand.Next(250, 1000) : rand.Next(250, 500); await Task.Delay(delay); } - string callResp = await callUrl(urlIobEnabled, i < 3); // true per i primi tentativi + string callResp = await utils.callUrlAsync(urlIobEnabled); if (callResp == "OK") return true; } catch (Exception exc) @@ -5623,12 +9473,12 @@ namespace IOB_WIN_FORM.Iob // prova ad avviare/chiudere PODL relativo (eventualmente duplicandolo) dtEve = $"{dtStartPOdl:yyyyMMddHHmmssfff}"; dtCurr = $"{DateTime.Now:yyyyMMddHHmmssfff}"; - await callUrl($"{urlOdlStartFromPOdl}{idxPOdl}&dtEve={dtEve}&dtCurr={dtCurr}", false); + await utils.callUrlAsync($"{urlOdlStartFromPOdl}{idxPOdl}&dtEve={dtEve}&dtCurr={dtCurr}"); // ora chiamo chiusura... dtEve = $"{dtEndPOdl:yyyyMMddHHmmssfff}"; dtCurr = $"{DateTime.Now:yyyyMMddHHmmssfff}"; - await callUrl($"{urlPODLClose}{idxPOdl}&dtEve={dtEve}&dtCurr={dtCurr}", false); + await utils.callUrlAsync($"{urlPODLClose}{idxPOdl}&dtEve={dtEve}&dtCurr={dtCurr}"); }) .GetAwaiter() .GetResult(); @@ -5649,14 +9499,15 @@ namespace IOB_WIN_FORM.Iob /// private async Task RecipeRemoveWeekStatus(string keyReq) { - string fullKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:WeekStats"); + 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 = JsonConvert.SerializeObject(currDict); - await callUrlWithPayloadAsync(remUrl, dictPayload, true); + await utils.callUrlAsync(remUrl, dictPayload); + //await callUrlWithPayloadAsync(remUrl, dictPayload, true); //await callUrlWithPayloadAsync(remUrl, dictPayload, false); } diff --git a/IOB-WIN-FORM/Iob/Services/DataSerializer.cs b/IOB-WIN-FORM/Iob/Services/DataSerializer.cs new file mode 100644 index 00000000..99ed9405 --- /dev/null +++ b/IOB-WIN-FORM/Iob/Services/DataSerializer.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using Newtonsoft.Json; + +namespace IOB_WIN_FORM.Iob.Services +{ + /// + /// Gestisce tutte le operazioni di serializzazione e deserializzazione dei dati. + /// + public static class DataSerializer + { + /// + /// Serializza un oggetto in una stringa JSON utilizzando la cultura invariante. + /// + public static string Serialize(T obj) + { + if (obj == null) return null; + // Utilizzo di CultureInfo.InvariantCulture per garantire la coerenza dei formati (es. decimali) + return JsonConvert.SerializeObject(obj, new JsonSerializerSettings + { + Culture = CultureInfo.InvariantCulture + }); + } + + /// + /// Deserializza una stringa JSON in un oggetto del tipo specificato. + /// + public static T Deserialize(string json) + { + if (string.IsNullOrWhiteSpace(json)) return default; + return JsonConvert.DeserializeObject(json, new JsonSerializerSettings + { + Culture = CultureInfo.InvariantCulture + }); + } + + /// + /// Helper per convertire un dizionario di oggetti in un dizionario di stringhe. + /// + public static Dictionary ToDictionary(Dictionary input) + { + if (input == null) return new Dictionary(); + + var dict = new Dictionary(); + foreach (var pair in input) + { + dict[pair.Key] = pair.Value?.ToString(); + } + return dict; + } + } +} diff --git a/IOB-WIN-FORM/Iob/Services/XmlDataSerializer.cs b/IOB-WIN-FORM/Iob/Services/XmlDataSerializer.cs new file mode 100644 index 00000000..2770b758 --- /dev/null +++ b/IOB-WIN-FORM/Iob/Services/XmlDataSerializer.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; +using System.Xml.Serialization; + +namespace IOB_WIN_FORM.Iob.Services +{ + /// + /// Gestisce le operazioni di serializzazione e deserializzazione in formato XML. + /// + public static class XmlDataSerializer + { + /// + /// Serializza un oggetto in una stringa XML. + /// + /// Tipo dell'oggetto. + /// L'oggetto da serializzare. + /// Stringa XML o null se l'oggetto è null. + public static string Serialize(T obj) + { + if (obj == null) return null; + + try + { + using (var stringWriter = new StringWriter()) + { + var serializer = new XmlSerializer(typeof(T)); + serializer.Serialize(stringWriter, obj); + return stringWriter.ToString(); + } + } + catch (Exception ex) + { + // Log l'errore se necessario. In un contesto di refactoring, + // potremmo voler passare l'eccezione verso l'alto. + throw new InvalidOperationException($"Errore durante la serializzazione XML per il tipo {typeof(T).Name}", ex); + } + } + + /// + /// Deserializza una stringa XML in un oggetto del tipo specificato. + /// + /// Tipo dell'oggetto. + /// La stringa XML. + /// L'oggetto deserializzato o default se la stringa è nulla/vuota. + public static T Deserialize(string xmlString) + { + if (string.IsNullOrWhiteSpace(xmlString)) return default; + + try + { + using (var stringReader = new StringReader(xmlString)) + { + var serializer = new XmlSerializer(typeof(T)); + return (T)serializer.Deserialize(stringReader); + } + } + catch (Exception ex) + { + throw new InvalidOperationException($"Errore durante la deserializzazione XML per il tipo {typeof(T).Name}", ex); + } + } + } +} diff --git a/IOB-WIN-FORM/Iob/Simula.cs b/IOB-WIN-FORM/Iob/Simula.cs index c7105df3..58c82e1b 100644 --- a/IOB-WIN-FORM/Iob/Simula.cs +++ b/IOB-WIN-FORM/Iob/Simula.cs @@ -1603,10 +1603,10 @@ namespace IOB_WIN_FORM.Iob // PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione Task.Run(async () => { - var rawListArt = await callUrl(urlGetCurrArt, false); - var rawListDOSS = await callUrl(urlGetCurrDOSS, false); - var rawListPODL = await callUrl(urlGetNextPODL, false); - var rawLVFasi = await callUrl(urlGetListValFasiPodl, false); + var rawListArt = await utils.callUrlAsync(urlGetCurrArt); + var rawListDOSS = await utils.callUrlAsync(urlGetCurrDOSS); + var rawListPODL = await utils.callUrlAsync(urlGetNextPODL); + var rawLVFasi = await utils.callUrlAsync(urlGetListValFasiPodl); if (!string.IsNullOrEmpty(rawListArt)) { @@ -1754,9 +1754,9 @@ namespace IOB_WIN_FORM.Iob // PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione Task.Run(async () => { - var rawListArt = await callUrl(urlGetCurrArt, false); - var rawListDOSS = await callUrl(urlGetCurrDOSS, false); - var rawListPODL = await callUrl(urlGetNextPODL, false); + var rawListArt = await utils.callUrlAsync(urlGetCurrArt); + var rawListDOSS = await utils.callUrlAsync(urlGetCurrDOSS); + var rawListPODL = await utils.callUrlAsync(urlGetNextPODL); if (!string.IsNullOrEmpty(rawListArt)) { try diff --git a/IOB-WIN-FTP/DATA/CONF/MAIN.ini b/IOB-WIN-FTP/DATA/CONF/MAIN.ini index ebc0bef6..2cb4f81c 100644 --- a/IOB-WIN-FTP/DATA/CONF/MAIN.ini +++ b/IOB-WIN-FTP/DATA/CONF/MAIN.ini @@ -20,3 +20,4 @@ CLI_INST=SteamWareSim STARTLIST=FTP_SONATEST MAXCNC=10 + diff --git a/IOB-WIN-MTC/DATA/CONF/MAIN.ini b/IOB-WIN-MTC/DATA/CONF/MAIN.ini index 4b123815..b8e74cd4 100644 --- a/IOB-WIN-MTC/DATA/CONF/MAIN.ini +++ b/IOB-WIN-MTC/DATA/CONF/MAIN.ini @@ -27,3 +27,4 @@ STARTLIST=3024 ;STARTLIST=LVF652 MAXCNC=10 + diff --git a/IOB-WIN-OPC-UA/DATA/CONF/MAIN.ini b/IOB-WIN-OPC-UA/DATA/CONF/MAIN.ini index afc5df70..acfaebef 100644 --- a/IOB-WIN-OPC-UA/DATA/CONF/MAIN.ini +++ b/IOB-WIN-OPC-UA/DATA/CONF/MAIN.ini @@ -34,3 +34,4 @@ STARTLIST=3026 ;STARTLIST=SIMUL_01 MAXCNC=10 + diff --git a/IOB-WIN-PING/DATA/CONF/MAIN.ini b/IOB-WIN-PING/DATA/CONF/MAIN.ini index 01bdf429..14fc4c91 100644 --- a/IOB-WIN-PING/DATA/CONF/MAIN.ini +++ b/IOB-WIN-PING/DATA/CONF/MAIN.ini @@ -20,3 +20,4 @@ STARTLIST=SIMUL_01 ;STARTLIST=3023-PING MAXCNC=10 + diff --git a/IOB-WIN-SHELLY/DATA/CONF/MAIN.ini b/IOB-WIN-SHELLY/DATA/CONF/MAIN.ini index a26f68cf..b844fa43 100644 --- a/IOB-WIN-SHELLY/DATA/CONF/MAIN.ini +++ b/IOB-WIN-SHELLY/DATA/CONF/MAIN.ini @@ -27,4 +27,5 @@ STARTLIST=SIMUL_03_SHELLY STARTLIST=SIMUL_03_SHELLY STARTLIST=SIMUL_03_SHELLY -MAXCNC=10 \ No newline at end of file +MAXCNC=10 + diff --git a/IOB-WIN-SIEMENS/DATA/CONF/MAIN.ini b/IOB-WIN-SIEMENS/DATA/CONF/MAIN.ini index 41774edf..babafa34 100644 --- a/IOB-WIN-SIEMENS/DATA/CONF/MAIN.ini +++ b/IOB-WIN-SIEMENS/DATA/CONF/MAIN.ini @@ -42,3 +42,4 @@ CLI_INST=SteamWareSim STARTLIST=3010 MAXCNC=10 + diff --git a/IOB-WIN-WPS/DATA/CONF/MAIN.ini b/IOB-WIN-WPS/DATA/CONF/MAIN.ini index a81dc4b2..5b0016e5 100644 --- a/IOB-WIN-WPS/DATA/CONF/MAIN.ini +++ b/IOB-WIN-WPS/DATA/CONF/MAIN.ini @@ -24,3 +24,4 @@ STARTLIST=3018 ;STARTLIST=SIMUL_06 MAXCNC=10 + diff --git a/refactoring_wip.md b/refactoring_wip.md new file mode 100644 index 00000000..a8bbed85 --- /dev/null +++ b/refactoring_wip.md @@ -0,0 +1,36 @@ +# WIP: Refactoring Phase 1 - Infrastructure Extraction + +## Status: IN_PROGRESS + +## Objective +Extract infrastructure and helper components from `Generic.cs` to improve modularity and reduce the monolithic footprint. + +## Tasks + +### 1. Communication Service Extraction +- [ ] Extract REST/HTTP logic (currently using `utils.callUrl`, `callUrlWithPayloadAsync`, etc.). +- [ ] Implement a singleton/service-based `CommunicationService` using `HttpClient`. +- [ ] **Italian Commenting Requirement**: All new/modified code comments must be in Italian. + +### 2. Redis Service Extraction (COMPLETED) +- [x] Encapsulate all `redisMan` calls into a `RedisService`. +- [x] Define a clean interface for key/value and hash operations. +- [x] **Refactored**: Moved semantic key construction from `RedisService` to `BaseObj` to eliminate redundant service layer. + +### 3. Data Serializer Extraction +- [ ] Move all `JsonConvert` and custom string formatting (e.g., `qEncodeFLog`, `qEncodeIN`) to a `DataSerializer` service. +- [ ] Centralize `CultureInfo.InvariantCulture` usage. + +### 4. BaseObj Simplification (NEW) +- [ ] Analyze `BaseObj` responsibilities (State, Config, Messaging, Diagnostics). +- [ ] Identify candidates for extraction into specialized services (e.g., `QueueManager`, `ConfigService`, `DiagnosticService`). +- [ ] Implement extraction of identified components. + +## Progress Log +- [x] Created WIP document. +- [x] Analyzed `Generic.cs` for Phase 1 candidates. +- [x] Completed Redis Semantic Refactoring: + - Moved key construction logic from `RedisService` to `BaseObj`. + - Refactored `Generic.cs` to use `BaseObj` semantic methods. + - Eliminated redundant `RedisService.cs`. +- [ ] Started Analysis of `BaseObj` to identify extraction candidates.