using Microsoft.EntityFrameworkCore; using MP.Core.Conf; using MP.Core.DTO; using MP.Core.Objects; using MP.Data; using MP.Data.Controllers; using MP.Data.DbModels; using MP.Data.DbModels.Anag; using MP.Data.MgModels; using MP.Data.Services.IOC; using MP.Data.Services.Mtc; using Newtonsoft.Json; using NLog; using StackExchange.Redis; using System.Data; using System.Diagnostics; using System.Globalization; using static MP.Core.Objects.Enums; namespace MP.IOC.Data { public class MpDataService : IDisposable { #region Public Constructors public MpDataService( IConfiguration configuration, ILogger logger, IServiceScopeFactory scopeFactory, IMtcSetupService mtcServ) { _logger = logger; _logger.LogInformation("Starting MpDataService INIT"); _configuration = configuration; _scopeFactory = scopeFactory; // setup compoenti REDIS redisConn = ConnectionMultiplexer.Connect(_configuration.GetConnectionString("Redis")); redisConnAdmin = ConnectionMultiplexer.Connect(_configuration.GetConnectionString("RedisAdmin")); redisDb = redisConn.GetDatabase(); BroadastMsgPipe = new MessagePipe(redisConn, Constants.BROADCAST_M_PIPE); // leggo cache (lungo periodo e corto periodo) int.TryParse(_configuration.GetValue("ServerConf:redisLongTimeCache"), out redisLongTimeCache); int.TryParse(_configuration.GetValue("ServerConf:redisShortTimeCache"), out redisShortTimeCache); Log.Info($"Redis INIT | redisLongTimeCache: {redisLongTimeCache} min | redisShortTimeCache {redisShortTimeCache} min"); // gestione scope factory useFactory = _configuration.GetValue("ServerConf:useFactory"); // conf base x servizi condivisi IO/IOC MpIoNS = _configuration.GetValue("ServerConf:MpIoNS") ?? "MP"; Log.Info($"MpDataService | useFactory {useFactory} | MpIoNS {MpIoNS}"); // conf DB string connStr = _configuration.GetConnectionString("MP.Data") ?? ""; if (string.IsNullOrEmpty(connStr)) { Log.Error("DbController: ConnString empty!"); } else { SpecDbController = new MpSpecController(configuration); IocDbController = new MpIocController(configuration); Log.Info("DbControllers INIT OK"); } MtcService = mtcServ; // conf mongo... connStr = _configuration.GetConnectionString("mdbConnString") ?? ""; if (string.IsNullOrEmpty(connStr)) { Log.Error("MongoController: ConnString empty!"); } else { mongoController = new MpMongoController(configuration); Log.Info("MongoController INIT OK"); } } #endregion Public Constructors #region Public Properties public static MpIocController IocDbController { get; set; } = null!; public static MpMongoController mongoController { get; set; } = null!; public static MpSpecController SpecDbController { get; set; } = null!; public MessagePipe BroadastMsgPipe { get; set; } = null!; /// /// Dizionario dei tag configurati per IOB /// public Dictionary> currTagConf { get; set; } = new Dictionary>(); #endregion Public Properties #region Public Methods /// /// Recupera eventuali azioni richieste /// /// public async Task ActionGetReq() { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); DisplayAction? result = null; // cerco in redis... RedisValue rawData = await redisDb.StringGetAsync(Utils.redisActionReq); if (!string.IsNullOrEmpty($"{rawData}")) { result = JsonConvert.DeserializeObject($"{rawData}"); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"ActionGetReq Read from REDIS: {ts.TotalMilliseconds}ms"); } if (result == null) { result = new DisplayAction(); } return result; } /// /// Salva richiesta azione /// /// /// public bool ActionSetReq(DisplayAction? act2save) { bool fatto = false; Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); // cerco in redis... string rawData = JsonConvert.SerializeObject(act2save); // invio broadcast + salvo in redis BroadastMsgPipe.saveAndSendMessage(Utils.redisActionReq, rawData); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"ActionSetReq REDIS send to broadcast + Write cache: {ts.TotalMilliseconds}ms"); return fatto; } /// /// Verifica se sia da reinviare un tName alla macchina dall'elenco di quelli salvati (in /// modalità upsert) se non scaduti /// /// /// /// /// public bool AddCheckTask4Machine(string idxMacchina, taskType taskKey, string taskVal) { bool answ = false; var currHash = Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS); Log.Info($"addCheckTask4Machine idxMacchina: {idxMacchina} | taskKey: {taskKey} | taskVal: {taskVal}"); try { Dictionary savedTask = mSavedTaskMacchina(idxMacchina); // cerco valOut saved string savedVal = savedTask[taskKey.ToString()]; // se ho un valOut != "" --> rimetto in coda di invio... if (!string.IsNullOrEmpty(savedVal) && (savedVal != taskVal)) { // leggo tName attuali... Dictionary currTask = mTaskMacchina(idxMacchina); // rimetto in tName da eseguire... currTask[taskKey.ToString()] = savedVal; RedisSetHashDict(currHash, currTask); answ = true; Log.Info($"re-issued task4machine: idxMacchina: {idxMacchina} | taskKey: {taskKey} | savedVal: {savedVal}"); } } catch (Exception exc) { Log.Error($"Eccezione in AddCheckTask4Machine{Environment.NewLine}{exc}"); } return answ; } /// /// Aggiunge un PARAMETRO OPZIONALE all'elenco di quelli salvati (in modalità upsert) /// /// /// /// /// public bool AddOptPar4Machine(string idxMacchina, string taskKey, string taskVal) { bool answ = false; RedisKey currHash = Utils.RedKeyOptPar(idxMacchina, MpIoNS); try { // leggo tName attuali... var currVal = mOptParMacchina(idxMacchina); // verifico se esista o se vada aggiunto... if (currVal.ContainsKey(taskKey)) { currVal[taskKey] = taskVal; } else { currVal.Add(taskKey, taskVal); } RedisSetHashDict(currHash, currVal); answ = true; } catch (Exception exc) { Log.Error($"Eccezione in addOptPar4Machine{Environment.NewLine}{exc}"); } return answ; } /// /// Aggiunge un tName all'elenco di quelli salvati (in modalità upsert) /// /// /// /// /// public bool AddTask4Machine(string idxMacchina, Enums.taskType taskKey, string taskVal) { bool answ = false; var currHash = Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS); var currSavedParHash = Utils.RedKeySavedTask2ExeMacc(idxMacchina, MpIoNS); Dictionary currTask = new Dictionary(); Dictionary savedTask = new Dictionary(); try { // leggo tName attuali... currTask = mTaskMacchina(idxMacchina); if (currTask.ContainsKey($"{taskKey}")) { currTask[$"{taskKey}"] = taskVal; } else { currTask.Add($"{taskKey}", taskVal); } RedisSetHashDict(currHash, currTask); answ = true; Log.Info($"Task ADD | hash: {currHash} | idxMacchina: {idxMacchina} | taskKey: {taskKey} | taskVal: {taskVal}"); } catch { } // verifico in base al tipo di tName se fare backup... switch (taskKey) { case Enums.taskType.setArt: case Enums.taskType.setComm: case Enums.taskType.setPzComm: case Enums.taskType.setProg: // leggo tName SALVATI attuali... savedTask = mSavedTaskMacchina(idxMacchina); savedTask[taskKey.ToString()] = taskVal; RedisSetHashDict(currSavedParHash, savedTask); answ = true; break; case Enums.taskType.endProd: // salvo un DICT vuoto x resettare savedTask = new Dictionary(); RedisSetHashDict(currSavedParHash, savedTask); answ = true; break; default: break; } return answ; } /// /// Aggiunge un set di task x macchina all'elenco di quelli salvati (in modalità upsert) /// /// /// /// /// public async Task AddTask4MacListAsync(string idxMacchina, Dictionary taskDict) { bool answ = false; bool needSaveParams = false; var currHash = Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS); var currSavedParHash = Utils.RedKeySavedTask2ExeMacc(idxMacchina, MpIoNS); Dictionary currTask = new Dictionary(); Dictionary savedTask = new Dictionary(); // leggo valori attuali... currTask = await mTaskMacchinaAsync(idxMacchina); // verifico processing dei ricevuti foreach (var item in taskDict) { if (currTask.ContainsKey($"{item.Key}")) { currTask[$"{item.Key}"] = item.Value; } else { currTask.Add($"{item.Key}", item.Value); } Log.Trace($"Task ADD | hash: {currHash} | idxMacchina: {idxMacchina} | taskKey: {item.Key} | taskVal: {item.Value}"); // verifico in base al tipo di tName se fare backup... switch (item.Key) { case Enums.taskType.setArt: case Enums.taskType.setComm: case Enums.taskType.setPzComm: case Enums.taskType.setProg: // leggo tName SALVATI attuali... savedTask = mSavedTaskMacchina(idxMacchina); savedTask[item.Key.ToString()] = item.Value; needSaveParams = true; break; case Enums.taskType.endProd: // salvo un DICT vuoto x resettare savedTask = new Dictionary(); needSaveParams = true; break; default: break; } } // salvo! await RedisSetHashDictAsync(currHash, currTask); answ = true; if (needSaveParams) { await RedisSetHashDictAsync(currSavedParHash, savedTask); } return answ; } /// /// Insert record allarme /// /// Data evento /// Idx macchina /// area memoria /// indice memoria /// valOut status /// valOut decodificato /// public async Task AlarmInsertAsync(DateTime dtRif, string idxMacchina, string memAddress, int memIndex, int statusVal, string valDecoded) { // aggiorno record sul DB bool answ = await IocDbController.AlarmLogInsertAsync(dtRif, idxMacchina, memAddress, memIndex, statusVal, valDecoded); return answ; } public async Task> AnagStatiComm() { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); List? result = new List(); // cerco in redis... RedisValue rawData = await redisDb.StringGetAsync(Utils.redisStatoCom); if (!string.IsNullOrEmpty($"{rawData}")) { result = JsonConvert.DeserializeObject>($"{rawData}"); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"AnagStatiComm Read from REDIS: {ts.TotalMilliseconds}ms"); } else { result = await Task.FromResult(SpecDbController.AnagStatiComm()); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); await redisDb.StringSetAsync(Utils.redisStatoCom, rawData, getRandTOut(redisLongTimeCache)); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"AnagStatiComm Read from DB: {ts.TotalMilliseconds}ms"); } if (result == null) { result = new List(); } return result; } /// /// Restituisce l'anagrafica STATI per intero /// /// public async Task> AnagStatiGetAllAsync() { List dbResult = new List(); // cerco in redis... var currKey = Utils.redisAnagStati; RedisValue rawData = await redisDb.StringGetAsync(currKey); if (rawData.HasValue) { dbResult = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); } else { dbResult = await IocDbController.AnagStatiGetAllAsync(); // serializzo e salvo... rawData = JsonConvert.SerializeObject(dbResult); await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache)); } return dbResult; } public async Task> AnagTipoArtLV() { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string source = "DB"; List? result = new List(); // cerco in redis... RedisValue rawData = await redisDb.StringGetAsync(Utils.redisTipoArt); if (!string.IsNullOrEmpty($"{rawData}")) { result = JsonConvert.DeserializeObject>($"{rawData}"); source = "REDIS"; } else { result = await Task.FromResult(SpecDbController.AnagTipoArtLV()); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); await redisDb.StringSetAsync(Utils.redisTipoArt, rawData, getRandTOut(redisLongTimeCache)); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"AnagTipoArtLV Read from {source}: {ts.TotalMilliseconds}ms"); if (result == null) { result = new List(); } return result; } /// /// Elenco Codice articolo con dati dossier gestiti /// /// public async Task> ArticleWithDossier() { List? result = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; string currKey = Utils.redisArtByDossier; // cerco in redis dato valOut sel macchina... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); readType = "REDIS"; } else { result = await Task.FromResult(SpecDbController.ArticleWithDossier()); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); } if (result == null) { result = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"ArticleWithDossier | Read from {readType}: {ts.TotalMilliseconds}ms"); return result; } /// /// Eliminazione record selezionato /// /// /// public async Task ArticoliDeleteRecord(AnagArticoliModel currRec) { bool fatto = await SpecDbController.ArticoliDeleteRecord(currRec); await resetCacheArticoli(); return fatto; } /// /// Elenco ultimi articoli data amcchina /// /// /// public async Task> ArticoliGetLastByMaccAsync(string idxMacc) { List? result = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; string currKey = $"{Utils.redisArtList}:Last:{idxMacc}"; // cerco in redis dato valOut sel macchina... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); readType = "REDIS"; } else { result = await IocDbController.ArticoliGetLastByMaccAsync(idxMacc); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); } if (result == null) { result = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"ArticoliGetLastByMaccAsync | Read from {readType}: {ts.TotalMilliseconds}ms"); return result; } /// /// Restitusice elenco articoli cercati /// /// /// /// public async Task> ArticoliGetSearch(int numRecord, string azienda, string searchVal) { List? result = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; string sKey = string.IsNullOrEmpty(searchVal) ? "***" : searchVal; string currKey = $"{Utils.redisArtList}:{azienda}:{sKey}"; // cerco in redis dato valOut sel macchina... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); readType = "REDIS"; } else { result = await Task.FromResult(SpecDbController.ArticoliGetSearch(numRecord, azienda, searchVal)); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache / 5)); } if (result == null) { result = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"ArticoliGetSearch | Read from {readType}: {ts.TotalMilliseconds}ms"); return result; } /// /// Aggiornamento record selezionato /// /// /// public async Task ArticoliUpdateRecord(AnagArticoliModel currRec) { bool fatto = await SpecDbController.ArticoliUpdateRecord(currRec); await resetCacheArticoli(); return fatto; } /// /// Verifica se sia possiubile cancellare articolo dato suo CodArt cercando su redis o su /// tab veto da DB /// /// /// public bool ArticoloDelEnabled(object CodArt) { bool answ = false; string codArticolo = $"{CodArt}"; int cacheCheckArtUsato = 1; int.TryParse(_configuration.GetValue("ServerConf:cacheCheckArtUsato"), out cacheCheckArtUsato); TimeSpan TTLCache = getRandTOut(cacheCheckArtUsato); // cerco in cache redis... string redKeyArtUsed = $"{Utils.redKeyArtUsed}:{codArticolo}"; string redKeyTabCheckArt = Utils.redKeyTabCheckArt; var rawData = redisDb.StringGet(redKeyArtUsed); if (!string.IsNullOrEmpty(rawData)) { bool.TryParse(rawData, out answ); } else { // controllo non sia stato mai prodotto sennò non posso cancellare... try { // cerco in cache se ci sia la tabella con gli articoli impiegati... var rawTable = redisDb.StringGet(redKeyTabCheckArt); List? artList = new List(); if (!string.IsNullOrEmpty(rawTable)) { artList = JsonConvert.DeserializeObject>($"{rawTable}"); } // rileggo... if (artList == null || artList.Count == 0) { artList = new List(); var tabArticoli = SpecDbController.ArticoliGetUsed(); var codList = tabArticoli.Select(x => x.CodArticolo); foreach (string cod in codList) { artList.Add(cod); } // SE fosse vuoto aggiungo comunque il cado "ND"... if (artList.Count == 0) { artList.Add("ND"); } // salvo rawTable = JsonConvert.SerializeObject(artList); redisDb.StringSet(redKeyTabCheckArt, rawTable, TTLCache); } // cerco nella tabella: se ci fosse --> disabilitato delete bool usato = false; if (artList != null && artList.Count > 0) { usato = artList.Contains(codArticolo); } answ = !usato; redisDb.StringSet(redKeyArtUsed, $"{answ}", TTLCache); } catch { } } return answ; } /// /// Effettua split ODL /// /// macchina /// effettuare conferma qty /// imposta la qty prox ODL da ODL che si chiude /// matricola operatore /// Round Step quantità prox ODL (corrente come riferimento) /// Cod ext da associare all'ODL /// public async Task AutoStartOdlAsync(string idxMacchina, bool doConfirm, bool qtyFromLast, int matrOpr, int roundStep = 100, string keyRichiesta = "") { string answ = "KO"; DateTime adesso = DateTime.Now; // verifico NON CI SIA un veto a NUOVI split... string redKey = $"{Utils.redisVetoSplitOdl}:{idxMacchina}"; var rawData = await redisDb.StringGetAsync(redKey); if (rawData.HasValue) { Log.Info($"VETO ATTIVO | Richiesto forceSplitOdl per impianto {idxMacchina} | impossibile procedere"); } else { // registro VETO x altri split... 5 minuti await redisDb.StringSetAsync(redKey, $"Inizio SPLIT-ODL {adesso}", TimeSpan.FromMinutes(5)); // setup dati da config bool confRett = false; int modoConfProd = -1; bool slaveConfirmPzProd = false; var configData = await ConfigGetAllAsync(); if (configData != null) { var currRec = configData.FirstOrDefault(x => x.Chiave == "confRett"); if (currRec != null) { bool.TryParse(currRec.Valore, out confRett); } currRec = configData.FirstOrDefault(x => x.Chiave == "modoConfProd"); if (currRec != null) { int.TryParse(currRec.Valore, out modoConfProd); } currRec = configData.FirstOrDefault(x => x.Chiave == "SlaveConfirmPzProd"); if (currRec != null) { bool.TryParse(currRec.Valore, out slaveConfirmPzProd); } } // calcolo la qta da gestire int qtyConf = 0; int qtyNew = 0; int qtyScarto = 0; if (doConfirm) { try { var rigaProd = await IocDbController.PezziProdMacchinaAsync(idxMacchina); var statoProd = await IocDbController.StatoProdMacchinaAsync(idxMacchina, adesso); qtyConf = rigaProd.pezziNonConfermati; qtyScarto = statoProd.Pz2RecScarto; // calcolo nuovi pezzi da confermare if (qtyFromLast) { roundStep = roundStep == 0 ? 1 : roundStep; double ratio = (double)qtyConf / roundStep; qtyNew = (int)Math.Round(Math.Ceiling(ratio) * roundStep, 0); } } catch (Exception exc) { Log.Error($"AutoStartOdlAsync | Errore recupero pezzi da confermare | idxMacchina {idxMacchina}{Environment.NewLine}{exc}"); } } // chiamo metodo redis/db... try { // recupero ODL corrente var currData = await IocDbController.OdlCurrByMaccAsync(idxMacchina); if (currData != null && currData.IdxOdl > 0) { // preparo var x master/slave bool isMachMaster = await IobIsMasterAsync(idxMacchina); List slaveList = new(); if (isMachMaster) { List allSlaveList = await IocDbController.Macchine2SlaveAsync(); slaveList = allSlaveList.Where(x => x.IdxMacchina == idxMacchina).ToList(); } // registro un evento di inizio attrezzaggio (idxTipoEv = 2) int idxEvento = 2; Log.Info($"Invio evento ODL-SPLIT per macchina {idxMacchina} | ev: {idxEvento} | art: {currData.CodArticolo} | qty conf: {qtyConf} | sty sca: {qtyScarto} | new qty: {qtyNew}"); // creo evento EventListModel newRecEv = new EventListModel() { CodArticolo = currData.CodArticolo, IdxMacchina = idxMacchina, IdxTipo = idxEvento, InizioStato = adesso, MatrOpr = matrOpr, pallet = "", Value = "ODL-SPLIT" }; inputComandoMapo resCmd = await scriviRigaEventoBarcodeAsync(newRecEv); // era 100, messo 50... int wTime = 50; // eventuale conferma produzione if (doConfirm) { await Task.Delay(wTime); adesso = DateTime.Now; // chiamo conferma produzione... try { string chiamata = confRett ? "confermaProdMacchinaFull" : "confermaProdMacchina"; Log.Info($"Chiamata a {chiamata} con parametri {currData.IdxMacchina} |{matrOpr} | {DateTime.Now.AddDays(-10)} | {DateTime.Now} | {qtyConf} | 0 | {qtyScarto} | {DateTime.Now} | false"); string idxMacchinaSel = currData.IdxMacchina; bool fatto = false; if (confRett) { // confermo al netto dei pezzi lasciati... fatto = await IocDbController.ConfermaProdMacchinaFullAsync(idxMacchinaSel, modoConfProd, qtyConf, 0, qtyScarto, adesso, matrOpr); if (slaveConfirmPzProd) { foreach (var machine in slaveList) { await IocDbController.ConfermaProdMacchinaFullAsync(machine.IdxMacchinaSlave, modoConfProd, qtyConf, 0, qtyScarto, adesso, matrOpr); } } } else { fatto = await IocDbController.ConfermaProdMacchinaAsync(idxMacchinaSel, modoConfProd, qtyConf, qtyScarto, adesso, matrOpr); if (slaveConfirmPzProd) { foreach (var machine in slaveList) { await IocDbController.ConfermaProdMacchinaAsync(idxMacchinaSel, modoConfProd, qtyConf, qtyScarto, adesso, matrOpr); } } } if (!fatto) { Log.Error($"ERRORE in chiamata {chiamata}"); } } catch (Exception exc) { Log.Error($"Eccezione in ConfermaProduzione{Environment.NewLine}{exc}"); } } // 2025.02.19 portato da 100 a 1000 ms attesa x evitare che ODL sia avviato PRIMA della conferma // attendo 1000 msec x chiudere ODL await Task.Delay(1000); // chiamo splitOdl await IocDbController.AutoStartOdlAsync(currData.IdxOdl, 0, idxMacchina, currData.TCRichAttr, currData.PzPallet, $"Nuovo ODL da forceSplitOdl.autoStart", true, qtyNew, keyRichiesta); // elimino eventuale ODL precedente... string currKey = $"{Utils.redisOdlCurrByMac}:{idxMacchina}"; await redisDb.KeyDeleteAsync(currKey); // ricalcola ODL macchina var newOdl = await GetCurrOdlAsync(idxMacchina); // attendo 1000 msec await Task.Delay(1000); adesso = DateTime.Now; // registro fine ODL (idxTipoEv = 1) idxEvento = 1; Log.Info($"Invio evento FINE ODL-SPLIT | idx: {idxMacchina} | ev: {idxEvento} | art: {currData.CodArticolo}"); // costruisco nuovo evento newRecEv = new EventListModel() { CodArticolo = currData.CodArticolo, IdxMacchina = idxMacchina, IdxTipo = idxEvento, InizioStato = adesso, MatrOpr = matrOpr, pallet = "", Value = "ODL-START" }; resCmd = await scriviRigaEventoBarcodeAsync(newRecEv); // invio eventi setup a macchina.... string setArtVal = $"{currData.CodArticolo}"; string setPzCommVal = $"{qtyNew}"; string setCommVal = $"ODL{newOdl}"; if (!string.IsNullOrEmpty(keyRichiesta)) { setCommVal = $"{keyRichiesta} ODL{newOdl}"; } try { // invio task caricamento dati ODL Dictionary updDict = new Dictionary(); updDict.Add(taskType.setArt, setArtVal); updDict.Add(taskType.setComm, setCommVal); updDict.Add(taskType.setPzComm, setPzCommVal); #if false addTask4Machine(idxMacchina, taskType.setArt, setArtVal); addTask4Machine(idxMacchina, taskType.setComm, setCommVal); addTask4Machine(idxMacchina, taskType.setPzComm, setPzCommVal); updateMachineParameter(idxMacchina, "setArt", setArtVal); updateMachineParameter(idxMacchina, "setComm", setCommVal); updateMachineParameter(idxMacchina, "setPzComm", setPzCommVal); #endif // recupero set attuale parametri var currMachPar = await MachineParamListAsync(idxMacchina); List list2upd = new(); // aggiorno i valori interessati tra quelli nel dizionario foreach (var item in updDict) { var cRec = currMachPar.FirstOrDefault(x => x.uid == $"{item.Key}"); if (cRec != null) { list2upd.Add(cRec); } } // aggiungo task di setParameters x la commessa... updDict.Add(taskType.setParameter, setCommVal); // salvo await AddTask4MacListAsync(idxMacchina, updDict); await MachineParamUpsertAsync(idxMacchina, list2upd); } catch { } // chiamo refresh MSE await IocDbController.RecalcMseAsync(idxMacchina, 0); // resetto stato macchina... var kStatoMacc = $"{Utils.redisStatoMacch}:{idxMacchina}"; await redisDb.KeyDeleteAsync(kStatoMacc); answ = "OK"; Log.Info($"Effettuato reset e ricalcoli x split ODL per macchina {idxMacchina}"); // se è una master richiamo fix x child... if (isMachMaster) { Dictionary updDict = new Dictionary(); string ts = ""; string outData = ""; await IocDbController.OdlFixMachineSlaveAsync(idxMacchina, 30, 1); foreach (var machine in slaveList) { // invio chiusura attrezzaggio ts = string.Format("{0:yyMMdd}T{0:HHmmss.fff}Z", DateTime.Now); outData = $"TS:{ts}|MATR:{matrOpr}|ODL:{newOdl}"; updDict.Clear(); updDict.Add(taskType.setArt, setArtVal); updDict.Add(taskType.setComm, setCommVal); updDict.Add(taskType.setPzComm, setPzCommVal); #if false addTask4Machine(machine.IdxMacchinaSlave, taskType.fixStopSetup, outData); addTask4Machine(machine.IdxMacchinaSlave, taskType.forceResetPzCount, outData); // invio task caricamento dati ODL addTask4Machine(machine.IdxMacchinaSlave, taskType.setArt, setArtVal); addTask4Machine(machine.IdxMacchinaSlave, taskType.setComm, setCommVal); addTask4Machine(machine.IdxMacchinaSlave, taskType.setPzComm, setPzCommVal); updateMachineParameter(machine.IdxMacchinaSlave, "setArt", setArtVal); updateMachineParameter(machine.IdxMacchinaSlave, "setComm", setCommVal); updateMachineParameter(machine.IdxMacchinaSlave, "setPzComm", setPzCommVal); #endif // recupero set attuale parametri var currMachPar = await MachineParamListAsync(machine.IdxMacchinaSlave); List list2upd = new(); // aggiorno i valori interessati tra quelli nel dizionario foreach (var item in updDict) { var cRec = currMachPar.FirstOrDefault(x => x.uid == $"{item.Key}"); if (cRec != null) { list2upd.Add(cRec); } } // aggiungo task di setParameters x la commessa... updDict.Add(taskType.fixStopSetup, outData); updDict.Add(taskType.forceResetPzCount, outData); updDict.Add(taskType.setParameter, setCommVal); // salvo await AddTask4MacListAsync(machine.IdxMacchinaSlave, updDict); await MachineParamUpsertAsync(machine.IdxMacchinaSlave, list2upd); } } } } catch (Exception exc) { Log.Error($"Eccezione in forceSplitOdl{Environment.NewLine}{exc}"); } } return answ; } public string CalcRecipe(RecipeModel currRecipe) { return mongoController.CalcRecipe(currRecipe); } /// /// controlla se da il segnale di "microstato" deriva un evento da generare - modalità OFFLINE /// /// idx macchina /// valOut ingresso /// data-ora evento (server) /// sequenza dati inviati /// dati macchina in cache (opzionale, se null fa lookup) /// public async Task CheckMicroStatoAsync(string idxMacchina, string valore, DateTime dtEve, string contatore, Dictionary? datiMaccCache = null) { // recupero SE IMPIEGATO REDIS i valori del Dictionary della macchina... Dictionary datiMacc = datiMaccCache ?? await mDatiMacchineAsync(idxMacchina); // processing inputComandoMapo answ = new inputComandoMapo(); // SE no trovassi verifico se esista la macchina altrimenti la creo... REDIS compliant if (!datiMacc.ContainsKey(idxMacchina)) { await verificaIdxMacchinaAsync(idxMacchina); } // continuo processing... string CodArticolo = datiMacc["CodArticolo"]; if (string.IsNullOrEmpty(CodArticolo)) { var allDatiMacch = await IocDbController.DatiMacchineGetAllAsync(); var recMacc = allDatiMacch.FirstOrDefault(x => x.IdxMacchina == idxMacchina); if (recMacc != null) { CodArticolo = recMacc.CodArticoloA; } } // preparo gestione val ingresso int? valINT = 0; int idxTipoEv = 0; int next_idxMS = 0; try { valINT = int.Parse(valore, NumberStyles.HexNumber); int idxFamIn = Convert.ToInt32(datiMacc["IdxFamIn"]); int idxMicroStato = Convert.ToInt32(datiMacc["IdxMicroStato"]); int valIOB = Convert.ToInt32(valINT); next_idxMS = idxMicroStato; // recupero singolo valOut (stringa) x chiave string todoSMI = await ValoreSmiAsync(idxFamIn, idxMicroStato, valIOB); // solo se ho trovato un risultato nella tab SMI della famiglia macchina... if (!string.IsNullOrEmpty(todoSMI) && todoSMI.Contains("_")) { // splitto e salvo valori OUT... string[] valori = todoSMI.Split('_'); idxTipoEv = Convert.ToInt32(valori[0]); next_idxMS = Convert.ToInt32(valori[1]); } } catch (Exception exc) { Log.Error($"[ChkMiSt_5a] - - Eccezione in recupero riga Trans ingressi | idxMacchina {idxMacchina} | valINT {valINT}:{Environment.NewLine}{exc}", Environment.NewLine, exc, valINT, idxMacchina); } // effettuo update vari SU DB!!! MicroStatoMacchinaModel newRecMsm = new MicroStatoMacchinaModel() { IdxMacchina = idxMacchina, IdxMicroStato = next_idxMS, InizioStato = dtEve, Value = valore }; if (idxTipoEv > 0) { // preparo record string valEsteso = string.Format("[{0}] {1}", contatore.PadLeft(3, '0'), valore); // creo evento EventListModel newRecEv = new EventListModel() { CodArticolo = CodArticolo, IdxMacchina = idxMacchina, IdxTipo = idxTipoEv, InizioStato = dtEve, MatrOpr = 0, pallet = "-", Value = valEsteso }; // salva e processa evento + microstato answ = await scriviRigaEventoAsync(newRecMsm, newRecEv); } else { if (useFactory) { await using var scope = _scopeFactory.CreateAsyncScope(); var iocService = scope.ServiceProvider.GetRequiredService(); // solo microstato await iocService.MicroStatoMacchinaUpsertAsync(newRecMsm); } else { // solo microstato await IocDbController.MicroStatoMacchinaUpsertAsync(newRecMsm); } } return answ; } public async Task> ConfigGetAllAsync() { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); List? result = new List(); // cerco in redis... RedisValue rawData = await redisDb.StringGetAsync(Utils.redisConfKey); if (!string.IsNullOrEmpty($"{rawData}")) { result = JsonConvert.DeserializeObject>($"{rawData}"); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"ConfigGetAllAsync Read from REDIS: {ts.TotalMilliseconds}ms"); } else { result = await IocDbController.ConfigGetAllAsync(); //result = await Task.FromResult(SpecDbController.ConfigGetAllAsync()); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); await redisDb.StringSetAsync(Utils.redisConfKey, rawData, getRandTOut(redisLongTimeCache)); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"ConfigGetAllAsync Read from DB: {ts.TotalMilliseconds}ms"); } if (result == null) { result = new List(); } return result; } /// /// Reset dati cache config /// /// public async Task ConfigResetCache() { await redisDb.StringSetAsync(Utils.redisConfKey, ""); } /// /// Update chiave config /// /// public async Task ConfigUpdate(ConfigModel updRec) { return await Task.FromResult(SpecDbController.ConfigUpdate(updRec)); } /// /// Elenco completo valori DatiMacchine /// /// public async Task> DatiMacchineGetAll() { List? result = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; string currKey = $"{Utils.redisBaseAddr}:TabDatiMacchine:ALL"; // cerco in redis dato valOut sel macchina... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); readType = "REDIS"; } else { result = await Task.FromResult(SpecDbController.DatiMacchineGetAll()); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); } if (result == null) { result = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"DatiMacchineGetAll | Read from {readType}: {ts.TotalMilliseconds}ms"); return result; } public async Task> DecNumArtGetFiltAsync(string codArt = "") { List result = new(); string tag = string.IsNullOrEmpty(codArt) ? "ALL" : codArt; string currKey = $"{Utils.redisDecNumArtKey}:{tag}"; // cerco in redis dato valOut sel macchina... RedisValue rawData = await redisDb.StringGetAsync(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); } else { result = await IocDbController.DecNumArtGetFiltAsync(codArt); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); } return result; } /// /// Dispose del connettore ai dati /// public void Dispose() { // Clear database controller SpecDbController.Dispose(); mongoController.Dispose(); redisConn.Dispose(); } /// /// Restituisce l'elenco delle date dei dossier x una macchina (se presenti) Impiegata anche /// cache redis /// /// /// public async Task> DossierLastByMachAsync(string idxMacchina) { List result = new List(); var currKey = $"{Utils.redisDossByMacLast}:{idxMacchina}"; RedisValue rawData = await redisDb.StringGetAsync(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); } else { result = await IocDbController.DossGetLastByMaccAsync(idxMacchina); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache)); } if (result == null) { result = new List(); } return result; } /// /// Eliminazione di un dossier /// /// record dossier da eliminare /// public async Task DossiersDeleteRecord(DossierModel selRecord) { bool result = false; Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); result = await SpecDbController.DossiersDeleteRecord(selRecord); // elimino cache redis... RedisValue pattern = new RedisValue($"{Utils.redisDossByMac}:*"); bool answ = await RedisFlushPatternAsync(pattern); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"DossiersDeleteRecord | IdxMacchina {selRecord.IdxMacchina} | DtRif {selRecord.DtRif} | IdxODL {selRecord.IdxODL} | {ts.TotalMilliseconds}ms"); return result; } /// /// Elenco ultimi n record DOssiers (che contengono ad esempio "salvataggi" di FLuxLog) dato /// macchina (ordinato x data registrazione) /// /// * = tutte, altrimenti solo x una data macchina /// Data minima per estrazione records /// Data Massima per estrazione records /// Num Max records da recuperare /// public async Task> DossiersGetLastFilt(string IdxMacchina, string CodArticolo, DateTime DtStart, DateTime DtEnd, int MaxRec) { List? result = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; string currKey = $"{Utils.redisDossByMac}:{IdxMacchina}:{CodArticolo}:{DtStart:yyyyMMddHHmm}:{DtEnd:yyyyMMddHHmm}:{MaxRec}"; // cerco in redis dato valOut sel macchina... RedisValue rawData = await redisDb.StringGetAsync(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); readType = "REDIS"; } else { result = await SpecDbController.DossiersGetLastFiltAsync(IdxMacchina, CodArticolo, DtStart, DtEnd, MaxRec); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache / 5)); } if (result == null) { result = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"DossiersGetLastFilt | Read from {readType}: {ts.TotalMilliseconds}ms"); return result; } /// /// Inserimento nuovo record dossier /// /// /// public async Task DossiersInsert(DossierModel currDoss) { // aggiorno record sul DB bool answ = await SpecDbController.DossiersInsert(currDoss); return answ; } /// /// Effettua salvataggio snapshot parametri (con stored) + svuota eventuale cache redis /// /// macchina /// NUm massimo secondi per recuperare dati correnti /// DataOra riferimento x cui prendere valori antecedenti /// public async Task DossiersTakeParamsSnapshotLast(string IdxMacchina, DateTime dtMin, DateTime dtMax) { bool answ = false; await Task.Delay(1); Log.Info($"Richiesta snapshot per macchina {IdxMacchina} | periodo {dtMin} --> {dtMax}"); // chiamo stored x salvare parametri SpecDbController.DossiersTakeParamsSnapshotLast(IdxMacchina, dtMin, dtMax); // elimino cache redis... RedisValue pattern = new RedisValue($"{Utils.redisDossByMac}:*"); answ = await RedisFlushPatternAsync(pattern); Log.Info($"Svuotata cache dossier | {pattern}"); return answ; } /// /// Update valOut dossier /// /// /// public async Task DossiersUpdateValore(DossierModel currDoss) { // aggiorno record sul DB bool answ = await SpecDbController.DossiersUpdateValore(currDoss); return answ; } /// /// Restitusice elenco aziende /// /// public Task> ElencoAziende() { return Task.FromResult(SpecDbController.AnagGruppiAziende()); } /// /// Restitusice elenco fasi /// /// public Task> ElencoGruppiFase() { List result = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; string currKey = $"{Utils.redisAnagGruppi}"; // cerco in redis dato valOut sel macchina... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { var rawResult = JsonConvert.DeserializeObject>($"{rawData}"); if (rawResult != null) { result = rawResult; } readType = "REDIS"; } else { result = SpecDbController.AnagGruppiFase(); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache / 5)); } if (result == null) { result = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"ElencoGruppiFase | Read from {readType}: {ts.TotalMilliseconds}ms"); return Task.FromResult(result); } public Task> ElencoLink() { return Task.FromResult(SpecDbController.ElencoLink()); } /// /// Aggiunta record EventList /// /// /// public async Task EvListInsert(EventListModel newRec) { return await SpecDbController.EvListInsert(newRec); } /// /// Imposta in redis la scadenza della pagina x il reload /// /// /// public DateTime ExpiryReloadParamGet() { DateTime dtRif = DateTime.Now; string currKey = $"{Utils.redisParamPageExp}"; RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { dtRif = JsonConvert.DeserializeObject($"{rawData}"); } return dtRif; } /// /// Imposta in redis la scadenza della pagina x il reload /// /// /// public bool ExpiryReloadParamSet(DateTime expTime) { bool fatto = false; string currKey = $"{Utils.redisParamPageExp}"; string rawData = JsonConvert.SerializeObject(expTime); fatto = redisDb.StringSet(currKey, rawData); return fatto; } /// /// Task completo sistemazione dossier quotidiani mancanti /// /// /// public async Task FixDailyDossierAsync(string idxMacc) { string answ = ""; // verifico se si possa processare, ovvero tab ConfFlux x macchina sia valorizzata... var confDataMach = await ConfFluxMach(idxMacc); if (confDataMach.Count > 0) { // determino ultima data da processare (inizio oggi, a mezzanotte) DateTime dtTo = DateTime.Today; DateTime dtFrom = dtTo; // determino data di partenza, prima da dossier esistenti var listaDoss = await DossierLastByMachAsync(idxMacc); if (listaDoss.Count > 0) { // primo giorno DOPO ultima registrazione dtFrom = listaDoss .Select(x => x.DtRif) .OrderByDescending(x => x) .FirstOrDefault() .AddDays(1); } else { // ...o da fluxLog acquisiti... var listaFL = await FluxLogFirstByMachAsync(idxMacc); if (listaFL.Count > 0) { // giorno successivo a prima registrazione dtFrom = listaFL .OrderBy(x => x) .FirstOrDefault() .AddDays(1); } } string caller = $"takeFlogSnapshot({idxMacc})"; DateTime dtStart = dtFrom.Date; DateTime dtEnd = dtFrom; // max 10 dossier alla volta (se non configurato diversamente) int maxAdd = 1; string confVal = await tryGetConfigAsync("IO_numDossMaxCreate"); if (!string.IsNullOrEmpty(confVal)) { int.TryParse(confVal, out maxAdd); } if (dtStart < dtTo) { // verifico di avere almeno 1 dossier da produrre ciclo fino ad esaurire le // date da processare while (dtStart < dtTo && maxAdd > 0) { // sistemo end dtEnd = dtStart.AddDays(1); // effettuo chiamata registrazione snapshot! answ = await FluxLogSaveSnapshotAsync(idxMacc, dtStart, dtEnd, caller); // incremento START... dtStart = dtEnd; // riduco il numero di chiamate ammesse x singolo task maxAdd--; } // reset cache dossier... await DossierLastByMachResetAsync(idxMacc); answ = "OK"; } else { Log.Warn("FixDailyDossierAsync | NO more to add"); } } else { Log.Warn("FixDailyDossierAsync | NO ConfFluxData"); } return answ; } public async Task FlushRedisCache() { await Task.Delay(1); RedisValue pattern = Utils.RedValue("*"); bool answ = await RedisFlushPatternAsync(pattern); // rileggo vocabolario.,.. ObjVocabolario = VocabolarioGetAll(); return answ; } public async Task FlushRedisKey(string redKey) { await Task.Delay(1); RedisValue pattern = Utils.RedValue(redKey); bool answ = await RedisFlushPatternAsync(pattern); return answ; } public List FluxLogDtoGetByFlux(string Valore) { List answ = new List(); DossierFluxLogDTO? result = JsonConvert.DeserializeObject(Valore); if (result != null) { if (result.ODL != null) { answ = result .ODL .OrderBy(x => x.CodFlux) .ToList(); // inizializzo SE necessario foreach (var item in answ) { item.ValoreEdit = String.IsNullOrEmpty(item.ValoreEdit) ? item.Valore : item.ValoreEdit; } } } return answ; } /// /// Elenco ultimi n record flux log dato macchina e flusso (ordinato x data registrazione) /// /// Data massima x eventi /// Data minima x eventi /// * = tutte, altrimenti solo x una data macchina /// *=tutti, altrimenti solo selezionato /// numero massimo record da restituire /// public async Task> FluxLogGetLastFilt(DateTime DtMax, DateTime DtMin, string IdxMacchina, string CodFlux, int MaxRec, double redisCacheSec) { List? result = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; string currKey = $"{Utils.redisFluxLogFilt}:{IdxMacchina}:{CodFlux}:{MaxRec}:{DtMax:yyyyMMddHHmm}:{DtMin:yyyyMMddHHmm}"; // cerco in redis dato valOut sel macchina... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); readType = "REDIS"; } else { result = await Task.FromResult(SpecDbController.FluxLogGetLastFilt(DtMax, DtMin, IdxMacchina, CodFlux, MaxRec)); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); if (string.IsNullOrEmpty(canCacheParametri)) { canCacheParametri = await tryGetConfigAsync("SPEC_ParametriEnableRedisCache"); } if (canCacheParametri != "false") { redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisCacheSec)); } } if (result == null) { result = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"FluxLogGetLastFilt | Read from {readType}: {ts.TotalMilliseconds}ms"); return result; } /// /// Restituisce il valOut dell'ODL corrente (ODL deve esserci per gestione contapezzi, senza /// ODL NO invio/gestione ODL) /// /// /// public async Task GetCurrOdlAsync(string idxMacchina) { string result = ""; string currKey = $"{Utils.redisOdlCurrByMac}:{idxMacchina}"; // cerco in redis dato valOut sel macchina... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { result = $"{rawData}"; } else { result = await GetCurrOdlByProdAsync(idxMacchina); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); } return result; } /// /// Restituisce il valOut dell'ODL corrente (ODL deve esserci per gestione contapezzi, senza /// ODL NO invio/gestione ODL) /// /// /// indica se forzare lettura da db (true) o meno /// public async Task GetCurrOdlAsync(string idxMacchina, bool forceDb) { string answ = ""; // se ho forceDB leggo dai dati prod... if (forceDb) { var datiProd = await StatoProdMacchinaAsync(idxMacchina, DateTime.Now, true); if (datiProd != null) { answ = datiProd.IdxOdl.ToString(); } } else { answ = await GetCurrOdlAsync(idxMacchina); } return answ; } /// /// Restituisce il valOut dell'ultimo ODL /// /// /// public async Task GetLastOdlAsync(string idxMacchina) { ODLExpModel result = new ODLExpModel(); string currKey = $"{Utils.redisOdlLastByMac}:{idxMacchina}"; // cerco in redis dato valOut sel macchina... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject($"{rawData}") ?? new(); } else { result = await IocDbController.OdlLastByMaccAsync(idxMacchina); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); } return result; } /// /// Effettua calcolo data-ora di riferimento per il server a partire da /// /// /// /// public DateTime GetSrvDtEvent(string dtEve, string dtCurr) { DateTime dataOraEvento = DateTime.Now; // 2017.09.14 trimmo eventualmente lo zero finale dalle date SE supera i millisecondi... dtEve = dtEve.Length > 17 ? dtEve.Substring(0, 17) : dtEve; dtCurr = dtCurr.Length > 17 ? dtCurr.Substring(0, 17) : dtCurr; DateTime dtEvento, dtCorrente; // controllo: se ho valori dt x evento e orario DIVERSI per acquisitore IOB calcolo // dataOraEvento corretto if (dtEve != dtCurr) { Int64 delta = 0; try { // se ho meno decimali x evento rispetto dtCorrente... if (dtEve.Length < dtCurr.Length) { dtEve = dtEve.PadRight(dtCurr.Length, '0'); } delta = Convert.ToInt64(dtCurr) - Convert.ToInt64(dtEve); // se meno di 60'000 ms ... if (delta < 59999) { dataOraEvento = dataOraEvento.AddMilliseconds(-delta); } else { // in questo caso elimino i MS dalle stringhe e converto i datetime.... CultureInfo provider = CultureInfo.InvariantCulture; string format = "yyyyMMddHHmmssfff"; dtEvento = DateTime.ParseExact(dtEve, format, provider); dtCorrente = DateTime.ParseExact(dtCurr, format, provider); TimeSpan deltaTS = dtCorrente.Subtract(dtEvento); dataOraEvento = dataOraEvento.Add(-deltaTS); } } catch (Exception exc) { Log.Error($"getSrvDtEvent | Errore calcolo ora corrente da IOB remoto | dtEve: {dtEve} | dtCurr: {dtCurr}{Environment.NewLine}" + $"{exc}"); } } return dataOraEvento; } /// /// Restitusice elenco KVP dei TASK (da passare a IOB-WIN) per l'impianto indicato /// /// /// public async Task> GetTask2ExeMacchinaAsync(string idxMacchina) { var currHash = Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS); return await RedisGetHashDictAsync(currHash); } /// /// Init ricetta /// /// /// /// /// public RecipeModel InitRecipe(string confPath, int idxPODL, Dictionary CalcArgs) { return mongoController.InitRecipe(confPath, idxPODL, CalcArgs); } /// /// Restituisce il valOut booleano se la macchina sia abilitata all'input /// /// /// public async Task IobInsEnabAsync(string idxMacchina) { var key = Utils.RedKeyDatiMacc(idxMacchina, MpIoNS); string? val = await redisDb.HashGetAsync(key, "insEnabled"); if (val == null) { var data = await ResetDatiMacchinaAsync(idxMacchina); data.TryGetValue("insEnabled", out val); } return val != null && (val == "1" || val.ToLower() == "true"); } /// /// Restituisce il valOut booleano se la macchina sia master /// /// /// public async Task IobIsMasterAsync(string idxMacchina) { var key = Utils.RedKeyDatiMacc(idxMacchina, MpIoNS); // 1. Tentativo ottimizzato: leggiamo solo il campo che ci serve // Supponendo che tu usi StackExchange.Redis direttamente o un wrapper string? val = await redisDb.HashGetAsync(key, "Master"); // 2. Se non c'è in cache, carichiamo/resettiamo tutto if (val == null) { //var data = ResetDatiMacchina(idxMacchina); var data = await ResetDatiMacchinaAsync(idxMacchina); data.TryGetValue("Master", out val); } // 3. Parsing sicuro return val != null && (val == "1" || val.ToLower() == "true"); } /// /// Restituisce il valOut booleano se la macchina sia abilitata all'inserimento COMPLETO nel /// Signal Log /// /// /// public async Task IobSLogEnabAsync(string idxMacchina) { bool answ = false; // ORA recupero da memoria redis... try { var currHash = Utils.RedKeyDatiMacc(idxMacchina, MpIoNS); RedisValue rawData = await redisDb.HashGetAsync(currHash, (RedisValue)"sLogEnabled"); // se è vuoto... leggo da DB e popolo! if (!rawData.HasValue) { await ResetDatiMacchinaAsync(idxMacchina); // riprovo rawData = await redisDb.HashGetAsync(currHash, (RedisValue)"sLogEnabled"); } // provo conversione bool.TryParse($"{rawData}", out answ); } catch (Exception exc) { Log.Error($"Errore IobSLogEnabAsync | idxMacchina {idxMacchina}:{Environment.NewLine}{exc}"); } return answ; } /// /// /// idxMacc odl da cercare /// public async Task> ListGiacenze(int IdxOdl) { List? result = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; string currKey = $"{Utils.redisGiacenzaList}:{IdxOdl}"; // cerco in redis dato valOut sel macchina... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); readType = "REDIS"; } else { result = await Task.FromResult(SpecDbController.ListGiacenze(IdxOdl)); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisShortTimeCache)); } if (result == null) { result = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"ListGiacenze | Read from {readType}: {ts.TotalMilliseconds}ms"); return result; } public async Task> ListValuesFilt(string tabName, string fieldName) { List resultList = new List(); string tag = ""; tag += string.IsNullOrEmpty(tabName) ? "" : $"{tabName}:"; tag += string.IsNullOrEmpty(fieldName) ? "" : $"{fieldName}:"; var currKey = $"{Utils.redisConfFlux}:{tag}"; RedisValue rawData = await redisDb.StringGetAsync(currKey); if (rawData.HasValue) { resultList = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); } else { resultList = await IocDbController.ListValuesFiltAsync(tabName, fieldName); // serializzo e salvo... rawData = JsonConvert.SerializeObject(resultList); await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache)); } if (resultList == null) { resultList = new(); } return resultList; } /// /// Elenco completo valori Macchine 2 Slave /// /// public async Task> Macchine2SlaveGetAllAsync() { List? result = new List(); string currKey = $"{Utils.redisBaseAddr}:M2STab"; // cerco in redis dato valOut sel macchina... RedisValue rawData = await redisDb.StringGetAsync(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); } else { if (useFactory) { await using var scope = _scopeFactory.CreateAsyncScope(); var mtcService = scope.ServiceProvider.GetRequiredService(); result = await mtcService.Macchine2SlaveAsync(); } else { result = await IocDbController.Macchine2SlaveAsync(); } // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache * 10)); } if (result == null) { result = new List(); } return result; } /// /// Elenco di tutte le macchine gestite /// /// /// public async Task> MacchineGetFilt(string codGruppo) { List? result = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; string keyGrp = codGruppo != "*" ? codGruppo : "ALL"; string currKey = $"{Utils.redisMacList}:{keyGrp}"; // cerco in redis dato valOut sel macchina... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); readType = "REDIS"; } else { result = await Task.FromResult(SpecDbController.MacchineGetFilt(codGruppo)); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); } if (result == null) { result = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"MacchineGetAll | Read from {readType}: {ts.TotalMilliseconds}ms"); return result; } /// /// Verifica se la macchina abbia un codice PATH ricette associato /// /// /// public async Task MacchineRecipeArchive(string idxMacchina) { string? result = ""; Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; string currKey = $"{Utils.redisMacRecipePath}:{idxMacchina}"; // cerco in redis dato valOut sel macchina... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject($"{rawData}"); readType = "REDIS"; } else { //recupero elenco macchine... var machineList = await MacchineGetFilt("*"); var currMach = machineList.Where(x => x.IdxMacchina == idxMacchina).FirstOrDefault(); result = currMach != null ? currMach.RecipeArchivePath : null; // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"MacchineRecipeArchive | Read from {readType}: {ts.TotalMilliseconds}ms"); return result ?? ""; } /// /// Verifica se la macchina abbia un codice CONF ricetta associato /// /// /// public async Task MacchineRecipeConf(string idxMacchina) { string? result = ""; Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; string currKey = $"{Utils.redisMacRecipeConf}:{idxMacchina}"; // cerco in redis dato valOut sel macchina... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject($"{rawData}"); readType = "REDIS"; } else { //recupero elenco macchine... var machineList = await MacchineGetFilt("*"); var currMach = machineList.Where(x => x.IdxMacchina == idxMacchina).FirstOrDefault(); result = currMach != null ? currMach.RecipePath : null; // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"MacchineRecipeConf | Read from {readType}: {ts.TotalMilliseconds}ms"); return result ?? ""; } /// /// Elenco idxMacc Macchine che abbiano dati FLuxLog, nel periodo indicato /// /// /// /// public async Task> MacchineWithFlux(DateTime dtStart, DateTime dtEnd) { List? result = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; string currKey = $"{Utils.redisMacByFlux}:{dtStart:yyyyMMddHHmm}:{dtEnd:yyyyMMddHHmm}"; // cerco in redis dato valOut sel macchina... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); readType = "REDIS"; } else { result = await SpecDbController.MacchineWithFlux(dtStart, dtEnd); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); } if (result == null) { result = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"MacchineWithFlux | Read from {readType}: {ts.TotalMilliseconds}ms"); return result; } /// /// Lista parametri correnti (ObjItemDTO) della macchina (ex getCurrObjItems) /// /// /// public List MachineParamList(string idxMacchina) { // setup parametri costanti string source = "NA"; Stopwatch sw = new Stopwatch(); sw.Start(); List? result = new List(); // cerco in _redisConn... var currKey = Utils.RedKeyCurrObjItems(idxMacchina, MpIoNS); RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue && rawData.Length() > 2) { var rawVal = JsonConvert.DeserializeObject>($"{rawData}"); // ordino! result = rawVal .OrderBy(x => x.displOrdinal) .ThenBy(x => x.description) .ToList(); source = "REDIS"; } if (result == null) { result = new List(); source = "NONE"; } sw.Stop(); Log.Debug($"MachineParamList | {source} | {sw.Elapsed.TotalMilliseconds}ms"); return result; } /// /// Lista parametri correnti (ObjItemDTO) della macchina (ex getCurrObjItems) /// /// /// public async Task> MachineParamListAsync(string idxMacchina) { // setup parametri costanti string source = "NA"; Stopwatch sw = new Stopwatch(); sw.Start(); List? result = new List(); // cerco in _redisConn... var currKey = Utils.RedKeyCurrObjItems(idxMacchina, MpIoNS); RedisValue rawData = await redisDb.StringGetAsync(currKey); if (rawData.HasValue && rawData.Length() > 2) { var rawVal = JsonConvert.DeserializeObject>($"{rawData}"); // ordino! result = rawVal .OrderBy(x => x.displOrdinal) .ThenBy(x => x.description) .ToList(); source = "REDIS"; } if (result == null) { result = new List(); source = "NONE"; } sw.Stop(); Log.Debug($"MachineParamListAsync | {source} | {sw.Elapsed.TotalMilliseconds}ms"); return result; } /// /// Lista parametri correnti che necessitano di write della macchina (ex getCurrObjItems) /// /// /// public async Task> MachineParamListPendingWriteAsync(string idxMacchina) { List? result = new List(); // recupero tutti i parametri var allData = await MachineParamListAsync(idxMacchina); result = allData.Where(x => x.writable && !string.IsNullOrEmpty(x.reqValue)).ToList(); return result; } /// /// Esegue aggiornamento MachineParamList (ex CurrObjItems) Async /// /// /// /// public async Task MachineParamListSetAsync(string idxMacchina, List currData) { string serVal = JsonConvert.SerializeObject(currData); var currKey = Utils.RedKeyCurrObjItems(idxMacchina, MpIoNS); bool fatto = await redisDb.StringSetAsync(currKey, serVal); return fatto; } /// /// Effettua UPSERT elenco parametri correnti x IOB (se c'è UPDATE, se manca ADD) /// /// /// /// public async Task MachineParamUpsertAsync(string idxMacchina, List innovations) { bool answ = false; if (innovations != null) { Log.Info($"upsertCurrObjItems | idxMacchina: {idxMacchina} | {innovations.Count} innovations"); // leggo i valori attuali... List actValues = await MachineParamListAsync(idxMacchina); // per ogni valOut passatomi faccio insert o update rispetto elenco valori correnti // in REDIS foreach (var item in actValues) { // cerco nelle innovazioni SE CI SIA il valOut... var trovato = innovations.Find(obj => obj.uid == item.uid); // se non trovato nelle innovazioni... if (trovato == null) { // lo ri-aggiungo x non perderlo innovations.Add(item); // in base a value (value == null) uso debug/info if (string.IsNullOrEmpty(item.value)) { Log.Debug($"idxMacchina: {idxMacchina} | innovations | add | item.uid: {item.uid} | item.value: {item.value}"); } else { Log.Info($"idxMacchina: {idxMacchina} | innovations | add | item.uid: {item.uid} | item.value: {item.value}"); } } // altrimenti aggiorno campo (non trasmesso) name e tengo il resto... else { trovato.name = item.name; Log.Debug($"idxMacchina: {idxMacchina} | innovations | update | item.uid: {item.uid} | item.value: {item.value} --> {trovato.value} "); } } // serializzo e salvo answ = await MachineParamListSetAsync(idxMacchina, innovations); } return answ; } /// /// Restitusice elenco KVP dei campi DatiMacchine + StatoMacchine per l'impianto indicato /// /// /// public async Task> mDatiMacchineAsync(string idxMacchina) { // hard coded dimensione vettore DatiMacchine Dictionary answ = new Dictionary(); // ORA recupero da memoria redis... try { var currHash = Utils.RedKeyDatiMacc(idxMacchina, MpIoNS); answ = await RedisGetHashDictAsync(currHash); // se è vuoto... leggo da DB e popolo! if (answ.Count == 0) { answ = await ResetDatiMacchinaAsync(idxMacchina); } } catch (Exception exc) { Log.Error($"Errore in compilazione dati Macchine x Redis - idxMacchina {idxMacchina}:{Environment.NewLine}{exc}"); } return answ; } /// /// Restitusice elenco KVP dei TASK (da passare a IOB-WIN) per l'impianto indicato /// /// /// public Dictionary mOptParMacchina(string idxMacchina) { // hard coded dimensione vettore DatiMacchine Dictionary answ = new Dictionary(); // ORA recupero da memoria redis... RedisKey currHash = Utils.RedKeyOptPar(idxMacchina, MpIoNS); answ = RedisGetHashDict(currHash); return answ; } /// /// Restitusice elenco KVP dei TASK SALVATI (da passare a IOB-WIN) per l'impianto indicato /// /// /// public Dictionary mSavedTaskMacchina(string idxMacchina) { // hard coded dimensione vettore DatiMacchine Dictionary answ = new Dictionary(); // ORA recupero da memoria redis... try { RedisKey currHash = Utils.RedKeySavedTask2ExeMacc(idxMacchina); answ = RedisGetHashDict(currHash); } catch (Exception exc) { Log.Info($"Errore in recupero dati SAVED TASK x Redis mSavedTaskMacchina | idxMacchina {idxMacchina}{Environment.NewLine}{exc}"); } return answ; } /// /// Elenco da tabella MappaStatoExplModel /// /// public async Task> MseGetAllAsync(bool forceDb = false) { Stopwatch sw = new Stopwatch(); string source = "DB"; sw.Start(); List? result = new List(); // cerco in _redisConn... RedisValue rawData = await redisDb.StringGetAsync(Constants.redisMseKey); if (rawData.HasValue && !forceDb) { result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); source = "REDIS"; } else { result = await IocDbController.MseGetAllAsync(maxAge); // serializzp e salvo... rawData = JsonConvert.SerializeObject(result); await redisDb.StringSetAsync(Constants.redisMseKey, rawData, getRandTOut(redisShortTimeCache / 2)); } if (result == null) { result = new List(); } sw.Stop(); Log.Debug($"MseGetAllAsync | {source} | {sw.Elapsed.TotalMilliseconds}ms"); return result; } /// /// Restitusice elenco KVP (async) per evitare blocchi quando chiamato da metodi asincroni /// /// /// public async Task[]> mTabMSMIAsync(string idxMacchina) { KeyValuePair[] answ = new KeyValuePair[1]; answ[0] = new KeyValuePair("0", "0"); try { var currHash = Utils.RedKeyMsmi(idxMacchina); answ = await RedisGetHashAsync(currHash); if (answ == null || answ.Length == 0) { answ = await resetMSMIAsync(idxMacchina); } } catch (Exception exc) { Log.Error($"Errore in compilazione Tabella Multi State Machine Ingressi x Redis:{Environment.NewLine}{exc}"); } return answ; } /// /// Restitusice elenco KVP dei TASK (da passare a IOB-WIN) per l'impianto indicato /// /// /// public Dictionary mTaskMacchina(string idxMacchina) { // hard coded dimensione vettore DatiMacchine Dictionary answ = new Dictionary(); // ORA recupero da memoria redis... try { var currHash = Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS); answ = RedisGetHashDict(currHash); } catch (Exception exc) { Log.Error(string.Format("Errore in mTaskMacchina | idxMacchina {2}:{0}{1}", Environment.NewLine, exc, idxMacchina)); } return answ; } /// /// Restitusice elenco KVP dei TASK (da passare a IOB-WIN) per l'impianto indicato /// /// /// public async Task> mTaskMacchinaAsync(string idxMacchina) { // hard coded dimensione vettore DatiMacchine Dictionary answ = new Dictionary(); // ORA recupero da memoria redis... var currHash = Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS); answ = await RedisGetHashDictAsync(currHash); return answ; } /// /// Generazione autoOdl /// /// /// /// /// /// public async Task OdlAutoDayGenAsync(string idxMacchina, DateTime dataInizio, DateTime dataFine, string codArticolo) { var result = await IocDbController.OdlAutoDayGenAsync(idxMacchina, dataInizio, dataFine, codArticolo); return result; } /// /// Generazione autoOdl /// /// /// /// /// /// /// /// /// /// /// /// /// public async Task OdlAutoDayGenFullAsync(string idxMacchina, DateTime dataInizio, DateTime dataFine, string codArticolo, int? pzPODL, int? pzPallet, string? keyRichiesta, int? tcAssegnato, string? codGruppo, bool flgCreaPODL, bool flgCheckTC) { var result = await IocDbController.OdlAutoDayGenFullAsync(idxMacchina, dataInizio, dataFine, codArticolo, pzPODL, pzPallet, keyRichiesta, tcAssegnato, codGruppo, flgCreaPODL, flgCheckTC); return result; } /// /// Elenco ODL dato batch selezionato /// /// Batch richiesto /// public async Task> OdlByBatch(string BatchSel) { List? result = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; string currKey = Utils.redisOdlByBatch; // cerco in redis dato valOut sel macchina... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); readType = "REDIS"; } else { result = await Task.FromResult(SpecDbController.OdlByBatch(BatchSel)); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); } if (result == null) { result = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"OdlByBatch | Read from {readType}: {ts.TotalMilliseconds}ms"); return result; } /// /// ODL da chiave /// /// /// public ODLExpModel OdlByKey(int IdxOdl) { ODLExpModel? result = new ODLExpModel(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; result = SpecDbController.OdlByKey(IdxOdl); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"OdlByKey | Read from {readType}: {ts.TotalMilliseconds}ms"); return result; } /// /// Effettua chiusura dell'ODL indicato, andand /// /// idx odl da chiudere /// idx macchina /// matricola operatore /// indica se confermare i pezzi priam di chiudere ODL public async Task ODLClose(int idxOdl, string idxMacchina, int matrOpr, bool confPezzi) { bool fatto = false; await Task.Delay(1); // recupero dati x conf modalità conferma var configData = await ConfigGetAllAsync(); if (configData != null) { bool confRett = false; var currRec = configData.FirstOrDefault(x => x.Chiave == "confRett"); if (currRec != null) { bool.TryParse(currRec.Valore, out confRett); } int modoConfProd = 0; currRec = configData.FirstOrDefault(x => x.Chiave == "modoConfProd"); if (currRec != null) { int.TryParse(currRec.Valore, out modoConfProd); } // chiamo metodo conferma! fatto = await SpecDbController.ODLClose(idxOdl, idxMacchina, matrOpr, confPezzi, confRett, modoConfProd); } return fatto; } public async Task OdlCurrByMaccAsync(string IdxMacchina) { ODLExpModel result = new(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; string currKey = $"{Utils.redisOdlList}:Current:{IdxMacchina}"; // cerco in redis dato valOut sel macchina... RedisValue rawData = await redisDb.StringGetAsync(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject($"{rawData}") ?? new(); readType = "REDIS"; } else { result = await IocDbController.OdlCurrByMaccAsync(IdxMacchina); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisShortTimeCache)); } if (result == null) { result = new(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"OdlCurrByMaccAsync | Read from {readType}: {ts.TotalMilliseconds}ms"); return result; } /// /// Record ODL da chaive /// /// public async Task OdlGetByKey(int IdxOdl) { await Task.Delay(1); var dbResult = await SpecDbController.OdlGetByKey(IdxOdl); return dbResult; } /// /// ODL correnti (tutti) /// /// /// public List OdlGetCurrent() { List? dbResult = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; string currKey = $"{Utils.redisOdlCurrByMac}"; // cerco in redis dato valOut sel macchina... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { try { dbResult = JsonConvert.DeserializeObject>($"{rawData}"); } catch { } readType = "REDIS"; } else { dbResult = SpecDbController.OdlGetCurrent().Select(x => x.IdxMacchina).Distinct().ToList(); rawData = JsonConvert.SerializeObject(dbResult); redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(3)); } if (dbResult == null) { dbResult = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"OdlGetCurrent | Read from {readType}: {ts.TotalMilliseconds}ms"); return dbResult; } /// /// elenco TUTTI gli ODL /// /// /// public List OdlListAll() { List? result = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; result = SpecDbController.OdlListAll(); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"OdlListAll | Read from {readType}: {ts.TotalMilliseconds}ms"); return result; } /// /// Elenco ODL filtrati x stato, articolo, KeyRich (che contiene stato) /// /// Stato ODL: true=in corso/completato /// Cod articolo /// KeyRich (parziale) da cercare (es cod stato x yacht) /// Reparto selezionato /// Macchina selezionata /// Data inizio /// Data fine /// public async Task> OdlListGetFilt(bool inCorso, string codArt, string keyRichPart, string Reparto, string IdxMacchina, DateTime startDate, DateTime endDate) { List? result = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; string currKey = $"{Utils.redisOdlList}:{inCorso}:{codArt}:{keyRichPart}:{Reparto}:{IdxMacchina}:{startDate:yyyyMMdd_HHmmss}:{endDate:yyyyMMdd_HHmmss}"; // cerco in redis dato valOut sel macchina... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); readType = "REDIS"; } else { result = await Task.FromResult(SpecDbController.ListODLFilt(inCorso, codArt, keyRichPart, Reparto, IdxMacchina, startDate, endDate)); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisShortTimeCache)); } if (result == null) { result = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"OdlListGetFilt | Read from {readType}: {ts.TotalMilliseconds}ms"); return result; } /// /// Elenco di tutti i parametri filtrati x macchina /// /// * = tutte, altrimenti solo x una data macchina /// public async Task> ParametriGetFilt(string IdxMacchina) { List? result = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; string currKey = $"{Utils.redisFluxByMac}:{IdxMacchina}"; // cerco in redis dato valOut sel macchina... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); readType = "REDIS"; } else { result = await Task.FromResult(SpecDbController.ParametriGetFilt(IdxMacchina)); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); } if (result == null) { result = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"ParametriGetFilt | Read from {readType}: {ts.TotalMilliseconds}ms"); return result; } /// /// Eliminazione record selezionato /// /// /// public async Task POdlDeleteRecord(PODLExpModel currRec) { var dbResult = await SpecDbController.PODLDeleteRecord(currRec); // elimino cache redis... await POdlFlushCache(); await Task.Delay(1); return dbResult; } /// /// Avvio fase setup per il record selezionato /// /// /// public async Task POdlDoSetup(PODLExpModel currRec) { var dbResult = await SpecDbController.PODL_startSetup(currRec, 0, 1, 1, "", DateTime.Now); // elimino cache redis... await POdlFlushCache(); await Task.Delay(1); return dbResult; } /// /// Recupero PODL da chiave /// /// /// public async Task POdlGetByKey(int idxPODL) { PODLModel result = new PODLModel(); if (idxPODL != 0) { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; string currKey = $"{Utils.redisPOdlByPOdl}:{idxPODL}"; // cerco in redis dato valOut sel macchina... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { var rawResult = JsonConvert.DeserializeObject($"{rawData}"); if (rawResult != null) { result = rawResult; readType = "REDIS"; } } else { result = await SpecDbController.PODL_getByKey(idxPODL); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); } if (result == null) { result = new PODLModel(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"POdlGetByKey | Read from {readType}: {ts.TotalMilliseconds}ms"); } else { Log.Debug("Errore IdxPODL = 0"); } return result; } public async Task> POdlGetByMaccArtAsync(string idxMacchina, string codArticolo, string codGruppo, bool onlyFree) { List result = new List(); var currKey = $"{Utils.redisPOdlByMaccArt}:{idxMacchina}"; if (!string.IsNullOrEmpty(codArticolo)) { currKey += $":A{codArticolo}"; } if (!string.IsNullOrEmpty(codGruppo)) { currKey += $":G{codGruppo}"; } currKey += onlyFree ? $":FREE" : ":ALL"; RedisValue rawData = await redisDb.StringGetAsync(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); } else { result = await IocDbController.POdlGetByMaccArtAsync(idxMacchina, codArticolo, codGruppo, onlyFree); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache)); } if (result == null) { result = new List(); } return result; } /// /// Recupero PODL da IdxODL /// /// /// public PODLModel POdlGetByOdl(int idxODL) { PODLModel result = new PODLModel(); if (idxODL != 0) { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; string currKey = $"{Utils.redisPOdlByOdl}:{idxODL}"; // cerco in redis dato valOut sel macchina... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { var rawResult = JsonConvert.DeserializeObject($"{rawData}"); if (rawResult != null) { result = rawResult; } readType = "REDIS"; } else { result = SpecDbController.PODL_getByOdl(idxODL); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); } if (result == null) { result = new PODLModel(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"POdlGetByOdl | Read from {readType}: {ts.TotalMilliseconds}ms"); } else { Log.Debug("Errore IdxODL = 0"); } return result; } /// /// Elenco PODL non avviati filtrati x articolo, KeyRich (che contiene stato) /// /// Solo lanciati (1) o ancora disponibili (0) /// KeyRich (parziale) da cercare (es cod stato x yacht) /// Macchina /// Gruppo /// Data inizio /// Data fine /// public async Task> POdlListGetFilt(bool lanciato, string keyRichPart, string idxMacchina, string codGruppo, DateTime startDate, DateTime endDate) { List? result = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; string currKey = $"{Utils.redisPOdlList}:{codGruppo}:{idxMacchina}:{keyRichPart}:{lanciato}:{startDate:yyyyMMdd_HHmmss}:{endDate:yyyyMMdd_HHmmss}"; // cerco in redis dato valOut sel macchina... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); readType = "REDIS"; } else { result = await Task.FromResult(SpecDbController.ListPODLFilt(lanciato, keyRichPart, idxMacchina, codGruppo, startDate, endDate)); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisShortTimeCache)); } if (result == null) { result = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"POdlListGetFilt | Read from {readType}: {ts.TotalMilliseconds}ms"); return result; } /// /// Aggiornamento record selezionato /// /// /// public async Task POdlUpdateRecord(PODLModel currRec) { var dbResult = await SpecDbController.PODLUpdateRecord(currRec); // elimino cache redis... await POdlFlushCache(); return dbResult; } /// /// Processa registrazione FL da IOB /// /// /// /// /// /// /// /// /// public async Task ProcessFluxLogAsync(string idxMacchina, string flux, string valore, string dtEve, string dtCurr, int contatore, bool disabKA) { // se non vietato... if (!disabKA) { // scrivo keep alive!!! (se necessario, altrimenti è in cache...) await ScriviKeepAliveAsync(idxMacchina, DateTime.Now); } string answ = ""; DateTime dataOraEvento = GetSrvDtEvent(dtEve, dtCurr); // inizio processing vero e proprio INPUT... if (string.IsNullOrEmpty(idxMacchina) || string.IsNullOrEmpty(valore)) { string errore = "processFluxLog | Errore: parametri macchina/valOut vuoti"; Log.Error(errore); answ = errore; } else { FluxLogModel newRec = new FluxLogModel() { IdxMacchina = idxMacchina, dtEvento = dataOraEvento, CodFlux = flux, Valore = valore, Cnt = contatore }; await IocDbController.FluxLogInsertAsync(newRec); // 2022.06.06 salvo su redis il valOut ULTIMO del flux x recupero rapido ultimo valOut var currKey = Utils.RedKeyLastFLog(idxMacchina, flux, MpIoNS); // 10 min cache max... await redisDb.StringSetAsync(currKey, valore, TimeSpan.FromMinutes(10)); // registro in risposta che è andato tutto bene... answ = "OK"; } return answ; } /// /// Validazione preliminare valori input /// /// /// /// /// /// private bool ValidateinputParams(string idxMacchina, string valore, string dtEve, string dtCurr) { bool isValid = false; // preparo stringa valori correnti string currVals = $"idxMacchina: {idxMacchina} | valOut: {valore} | dtEve: {dtEve} | dtCurr:{dtCurr}"; if (dtEve == null || dtCurr == null) { Log.Warn($"procInput: null found | {currVals}"); } else if (dtEve.Length < 17 || dtCurr.Length < 17) { Log.Info($"procInput: invalid data | {currVals}"); } else if (string.IsNullOrEmpty(idxMacchina)) { Log.Info($"procInput: missing IdxMacchina | {currVals}"); } else if (string.IsNullOrEmpty(valore)) { Log.Info($"procInput: missing valOut | {currVals}"); } else { isValid = true; } return isValid; } /// /// Calcola dataora evento da info dt ricevute /// /// /// /// private DateTime ParseEventTime(string dtEve, string dtCurr) { DateTime dataOraEvento = DateTime.Now; // fix formato dataora in ingresso string stdEve = dtFormStd(dtEve); string stdCurr = dtFormStd(dtCurr); // 2. Se le stringhe normalizzate coincidono, skip dei calcoli if (stdEve != stdCurr) { // 3. Parsing sicuro if (DateTime.TryParseExact(stdEve, dtFormat, ciProvider, DateTimeStyles.None, out DateTime dtEvento) && DateTime.TryParseExact(stdCurr, dtFormat, ciProvider, DateTimeStyles.None, out DateTime dtCorrente)) { TimeSpan diff = dtCorrente - dtEvento; double ms = Math.Abs(diff.TotalMilliseconds); // 4. Classificazione delta con switch expression (più leggibile e performante) string deltaClass = ms switch { <= 10 => "0-10 ms", <= 100 => "10-100 ms", <= 1000 => "100-1000 ms", <= 10000 => "1-10 sec", _ => "> 10 sec" }; Log.Debug($"Correzione delta {deltaClass}"); // 5. Correzione data/ora server: sottrao lo scarto calcolato dataOraEvento = dataOraEvento.Subtract(diff); } else { Log.Error($"Errore parsing date: {stdEve} | {stdCurr}"); } } return dataOraEvento; } /// /// Processa input da IOB eventualmente registrando i segnali inviati /// /// /// /// /// /// /// public async Task ProcessInputAsync(string idxMacchina, string valore, string dtEve, string dtCurr, string contatore) { string answ = ""; if (ValidateinputParams(idxMacchina, valore, dtEve, dtCurr)) { DateTime dataOraEvento = ParseEventTime(dtEve, dtCurr); // se abilitato registro evento sul DB if (await IobSLogEnabAsync(idxMacchina)) { int cntVal = 0; int.TryParse(contatore, out cntVal); await saveSigLogAsync(idxMacchina, valore, dataOraEvento, cntVal); } // continuo col resto try { // scrivo keep alive!!! (se necessario, altrimenti è in cache...) await ScriviKeepAliveAsync(idxMacchina, DateTime.Now); // Cache dati macchina per evitare lookup ridondanti Dictionary datiMacc = await mDatiMacchineAsync(idxMacchina); // verifico se sia una macchina MULTI.... if (isMulti(idxMacchina, datiMacc)) { // inizio preprocessing string newVal = ""; // processo OGNI macchina a stati dell'impianto... (KEY:IdxMacchina / IdxMacchina#qualcosa, Val = IdxFamIn) foreach (var item in await mTabMSMIAsync(idxMacchina)) { newVal = preProcInput(item.Key, valore, datiMacc); // ora processo e salvo il valOut del microstato... // INTERNAMENTE gestisce i casi DB/REDIS secondo necessità await CheckMicroStatoAsync(item.Key, newVal, dataOraEvento, contatore, datiMacc); } } else { // ora processo e salvo il valOut del microstato... INTERNAMENTE // gestisce i casi DB/REDIS secondo necessità await CheckMicroStatoAsync(idxMacchina, valore, dataOraEvento, contatore, datiMacc); } // forzo RESET dati macchina... await ResetDatiMacchinaAsync(idxMacchina); // registro in risposta che è andato tutto bene... answ = "OK"; } catch (Exception exc) { string errore = $"Errore: {Environment.NewLine}{exc}"; Log.Error(errore); answ = errore; } } return answ; } /// /// Processa registrazione UserLog da IOB /// /// Macchina /// Flusso: DI/RC/RC /// valOut = note/valString /// data evento /// data corrente /// contatore invio /// Matricola Operatore /// label = causale scarto / tagCode /// valNum = esitoOk (0/1) / Quantità di scarto associata /// public async Task ProcessUserLogAsync(string idxMacchina, string flux, string valore, string dtEve, string dtCurr, int contatore, int matrOpr, string label, int valNum) { // scrivo keep alive!!! (se necessario, altrimenti è in cache...) await ScriviKeepAliveAsync(idxMacchina, DateTime.Now); // 2017.09.14 trimmo eventualmente lo zero finale dalle date SE supera i millisecondi... dtEve = dtEve.Length > 17 ? dtEve.Substring(0, 17) : dtEve; dtCurr = dtCurr.Length > 17 ? dtCurr.Substring(0, 17) : dtCurr; string answ = ""; DateTime dataOraEvento = GetSrvDtEvent(dtEve, dtCurr); // inizio processing vero e proprio INPUT... if (string.IsNullOrEmpty(idxMacchina) || string.IsNullOrEmpty(flux)) { string errore = "processFluxLog | Errore: parametri macchina/flux vuoti"; Log.Error(errore); answ = errore; } else { // in base al flusso decido dove e cosa scrivere... switch (flux) { case "DI": RegistroDichiarazioniModel recDich = new RegistroDichiarazioniModel() { IdxMacchina = idxMacchina, DtRec = dataOraEvento, MatrOpr = matrOpr, ValString = valore, TagCode = label }; await IocDbController.RegDichiarInsertAsync(recDich); break; case "RC": bool esitoOk = valNum != 0; await IocDbController.RegControlliInsertAsync(idxMacchina, matrOpr, esitoOk, valore, dataOraEvento); break; case "RS": RegistroScartiModel recSca = new RegistroScartiModel() { IdxMacchina = idxMacchina, DataOra = dataOraEvento, Causale = label, Qta = valNum, Note = valore, MatrOpr = matrOpr }; await IocDbController.RegScartiInsertAsync(recSca); break; default: break; } // registro in risposta che è andato tutto bene... answ = "OK"; } return answ; } /// /// Restituisce il contapezzi salvato per la macchina /// /// /// public async Task pzCounter(string idxMacchina) { int answ = -1; try { var currKey = Utils.RedKeyPzCount(idxMacchina, MpIoNS); RedisValue rawData = await redisDb.StringGetAsync(currKey); if (rawData.HasValue) { int.TryParse(rawData, out answ); } else { answ = await PzCounterTcAsync(idxMacchina); // salvo in _redisConn... await redisDb.StringSetAsync(currKey, answ.ToString(), TimeSpan.FromSeconds(1)); } } catch (Exception exc) { Log.Error($"Eccezione in pzCounter{Environment.NewLine}{exc}"); } return answ; } /// /// Restituisce il contapezzi come CONTEGGIO da TCRilevati per la macchina - ASYNC /// /// /// public async Task PzCounterTcAsync(string idxMacchina) { int answ = -1; DateTime dataRif = DateTime.Now; var datiProd = await StatoProdMacchinaAsync(idxMacchina, dataRif); if (datiProd != null) { answ = datiProd.PzTotODL; } return answ; } /// /// Ricerca ricetta su MongoDB dato PODL /// /// /// public async Task RecipeGetByPODL(int idxPODL) { RecipeModel? result = null; Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "MongoDB"; result = await mongoController.RecipeGetByPODL(idxPODL); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"RecipeGetByPODL | Read from {readType}: {ts.TotalMilliseconds}ms"); return result; } /// /// Salva ricetta su MongoDB /// /// /// public async Task RecipeSetByPODL(RecipeModel currRecord) { bool answ = false; answ = await mongoController.RecipeSetByPODL(currRecord); if (answ) { await POdlFlushCache(); } return answ; } /// /// Effettua conteggio chaivi REDIS dato pattern ricerca /// /// /// public int RedisCountKey(string keyPattern) { int num = 0; keyPattern = (string.IsNullOrEmpty(keyPattern) ? "**" : keyPattern); try { var listEndpoints = redisConnAdmin.GetEndPoints(); foreach (var endPoint in listEndpoints) { var server = redisConnAdmin.GetServer(endPoint); foreach (RedisKey item in server.Keys(pattern: keyPattern, database: redisDb.Database, pageSize: 250, cursor: 0L)) { num++; } } } catch (Exception arg) { Log.Error($"Eccezione in RedisCountKey{Environment.NewLine}{arg}"); } return num; } /// /// Esegue eliminazione memoria redis keyVal /// /// /// public bool RedisDelKey(string keyVal) { bool answ = redisDb.KeyDelete((RedisKey)keyVal); #if false var listEndpoints = redisConnAdmin.GetEndPoints(); foreach (var endPoint in listEndpoints) { var server = redisConnAdmin.GetServer(endPoint); if (server != null) { redisDb.KeyDelete((RedisKey)keyVal); answ = true; } } #endif return answ; } /// /// Esegue eliminazione memoria redis keyVal /// /// /// public async Task RedisDelKeyAsync(RedisKey keyVal) { return await redisDb.KeyDeleteAsync(keyVal); } /// /// Esegue flush memoria redis dato keyVal /// /// /// public bool RedisFlushPattern(string pattern) { bool answ = false; var listEndpoints = redisConnAdmin.GetEndPoints(); foreach (var endPoint in listEndpoints) { var server = redisConnAdmin.GetServer(endPoint); if (server != null) { var keyList = server.Keys(redisDb.Database, pattern); foreach (var item in keyList) { redisDb.KeyDelete(item); } answ = true; } } return answ; } /// /// Esegue flush memoria redis dato keyVal, async /// /// /// public async Task RedisFlushPatternAsync(RedisValue pattern) { Log.Debug($"Richiesta flush pattern: {pattern}"); // 1. Target ONLY master (le replica sono in read-only) var master = redisConnAdmin.GetEndPoints() .Where(ep => redisConnAdmin.GetServer(ep).IsConnected && !redisConnAdmin.GetServer(ep).IsReplica) .FirstOrDefault(); if (master == null) { Log.Warn($"Nessun master Redis raggiungibile per il pattern {pattern}"); return false; } // 2. Flush intero DB se richiesto if (pattern.ToString() == "*") { Log.Debug($"Full DB reset da pattern {pattern}"); if (master != null) { redisConnAdmin.GetServer(master).FlushDatabase(redisDb.Database); Log.Info($"Flush database {redisDb.Database} completato"); } return true; } // altrimenti faccio ciclo! var server = redisConnAdmin.GetServer(master); var db = redisConnAdmin.GetDatabase(redisDb.Database); const int batchSize = 500; var batch = new List(batchSize); int deletedCount = 0; try { // KeysAsync usa SCAN automaticamente quando i risultati sono grandi await foreach (var key in server.KeysAsync( database: redisDb.Database, pattern: pattern.ToString(), pageSize: batchSize)) { batch.Add(key); if (batch.Count >= batchSize) { // Esecuzione batch in parallelo controllato await Task.WhenAll(batch.Select(k => db.KeyDeleteAsync(k))); batch.Clear(); deletedCount += batchSize; } } // Restanti if (batch.Count > 0) { await Task.WhenAll(batch.Select(k => db.KeyDeleteAsync(k))); deletedCount += batch.Count; } Log.Info("Flush pattern {Pattern}: eliminate {Count} chiavi", pattern, deletedCount); return true; } catch (RedisConnectionException ex) { Log.Error(ex, "Connessione Redis persa durante il flush di {pattern}", pattern); return false; } catch (Exception ex) { Log.Error(ex, "Errore imprevisto nel flush pattern {pattern}", pattern); throw; } } public KeyValuePair[] RedisGetHash(RedisKey redKey) { HashEntry[] rawData = redisDb.HashGetAll(redKey); var result = rawData.Where(x => !x.Name.IsNull).Select(x => new KeyValuePair($"{x.Name}", $"{x.Value}")).ToArray(); return result; } public async Task[]> RedisGetHashAsync(RedisKey redKey) { HashEntry[] rawData = await redisDb.HashGetAllAsync(redKey); var result = rawData.Where(x => !x.Name.IsNull).Select(x => new KeyValuePair($"{x.Name}", $"{x.Value}")).ToArray(); return result; } public Dictionary RedisGetHashDict(RedisKey hashKey) { HashEntry[] rawData = redisDb.HashGetAll(hashKey); var result = rawData.Where(x => !x.Name.IsNull).ToDictionary(x => x.Name.ToString(), x => x.Value.ToString()); return result; } public async Task> RedisGetHashDictAsync(RedisKey hashKey) { HashEntry[] rawData = await redisDb.HashGetAllAsync(hashKey); var result = rawData .Where(x => !x.Name.IsNull) .ToDictionary(x => x.Name.ToString(), x => x.Value.ToString()); return result; } public async Task RedisGetHashFieldAsync(RedisKey key, string hashField) { return await redisDb.HashGetAsync(key, hashField); } public bool RedisKeyPresent(RedisKey key) { return redisDb.KeyExists(key); } public async Task RedisKeyPresentAsync(RedisKey key) { return await redisDb.KeyExistsAsync(key); } public void RedisSetHash(RedisKey redKey, KeyValuePair[] valori, double expireSeconds = -1.0) { HashEntry[] redHash = valori.Select(x => new HashEntry(x.Key, x.Value)).ToArray(); redisDb.HashSet(redKey, redHash); if (expireSeconds > 0.0) { redisDb.KeyExpire(redKey, DateTime.Now.AddSeconds(expireSeconds)); } } public async Task RedisSetHashAsync(RedisKey redKey, KeyValuePair[] valori, double expireSeconds = -1.0) { HashEntry[] redHash = valori.Select(x => new HashEntry(x.Key, x.Value)).ToArray(); await redisDb.HashSetAsync(redKey, redHash); if (expireSeconds > 0.0) { await redisDb.KeyExpireAsync(redKey, DateTime.Now.AddSeconds(expireSeconds)); } } public void RedisSetHashDict(RedisKey redKey, Dictionary valori, double expireSeconds = -1.0) { HashEntry[] redHash = valori.Select(x => new HashEntry(x.Key, x.Value)).ToArray(); redisDb.HashSet(redKey, redHash); if (expireSeconds > 0.0) { redisDb.KeyExpire(redKey, DateTime.Now.AddSeconds(expireSeconds)); } } public async Task RedisSetHashDictAsync(RedisKey redKey, Dictionary valori, double expireSeconds = -1.0) { HashEntry[] redHash = valori.Select(x => new HashEntry(x.Key, x.Value)).ToArray(); await redisDb.HashSetAsync(redKey, redHash); if (expireSeconds > 0.0) { await redisDb.KeyExpireAsync(redKey, DateTime.Now.AddSeconds(expireSeconds)); } } /// /// Inserisce record RRL + fa pulizia vecchi record /// /// public async Task RemRebootLogAddAsync(RemoteRebootLogModel newRec) { // verifica preliminare ultima esecuzione (max 1 ogni 60 min...) DateTime adesso = DateTime.Now; bool doClean = false; if (adesso.Subtract(lastCleanupRRL).TotalMinutes > 60) { lastCleanupRRL = adesso; doClean = true; } string confVal = await tryGetConfigAsync("IO_NumReboot2Keep"); int num2keep = int.TryParse(confVal, out int n) ? n : 5; // insert del record + pulizia bool fatto = await IocDbController.RemRebootLogAddAndCleanAsync(newRec, doClean, num2keep); if (fatto) { // svuota cache var currKey = $"{Utils.redisRemRebLog}:*"; await RedisFlushPatternAsync(currKey); } return fatto; } /// /// Recupera tutti i record di RemoteRebootLog /// /// public async Task> RemRebootLogGetAllAsync() { // setup parametri costanti string source = "DB"; Stopwatch sw = new Stopwatch(); sw.Start(); List result = new List(); // cerco in _redisConn... var currKey = $"{Utils.redisRemRebLog}:ALL"; RedisValue rawData = await redisDb.StringGetAsync(currKey); //if (!string.IsNullOrEmpty($"{rawData}")) if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); source = "REDIS"; } else { result = await IocDbController.RemRebootLogGetAllAsync(); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); await redisDb.StringSetAsync(currKey, rawData, TimeSpan.FromSeconds(30)); } if (result == null) { result = new List(); } sw.Stop(); Log.Debug($"RemRebootLogGetAll | {source} | {sw.Elapsed.TotalMilliseconds}ms"); return result; } /// /// Recupera ultimo record x ogni IdxMacchina x avere ultimo attivo /// /// public async Task> RemRebootLogGetLast() { // setup parametri costanti string source = "DB"; Stopwatch sw = new Stopwatch(); sw.Start(); List result = new List(); // cerco in _redisConn... var currKey = $"{Utils.redisRemRebLog}:LAST"; RedisValue rawData = await redisDb.StringGetAsync(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); source = "REDIS"; } else { result = await IocDbController.RemRebootLogGetLastAsync(); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); await redisDb.StringSetAsync(currKey, rawData, TimeSpan.FromSeconds(30)); } if (result == null) { result = new List(); } sw.Stop(); Log.Debug($"RemRebootLogGetLast | {source} | {sw.Elapsed.TotalMilliseconds}ms"); return result; } /// /// Elimina da elenco KVP il TASK per l'impianto indicato /// /// /// /// public async Task> RemTask2ExeMacchinaAsync(string idxMacchina, taskType tName) { // hard coded dimensione vettore DatiMacchine Dictionary answ = new Dictionary(); // ORA recupero da memoria redis... try { var currHash = Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS); answ = await RedisGetHashDictAsync(currHash); answ.Remove($"{tName}"); // riscrivo! await RedisDelKeyAsync(currHash); await RedisSetHashDictAsync(currHash, answ); Log.Info($"Task REM - idxMacchina: {idxMacchina} | taskKey: {tName.ToString()}"); } catch (Exception exc) { Log.Info(string.Format("Errore in RemTask2ExeMacchinaAsync | idxMacchina {2}:{0}{1}", Environment.NewLine, exc, idxMacchina)); } return answ; } /// /// Resetta (rileggendo) i dati della State Machine multi ingressi nel formato /// currKey: IdxMacchina /// value: IdxFamigliaIngresso /// /// /// public async Task[]> resetMSMIAsync(string idxMacchina) { var currHash = Utils.RedKeyMsmi(idxMacchina); // recupero records var tabMSMI = await IocDbController.VMSFDGetMultiByMaccAsync(idxMacchina); KeyValuePair[] answ = new KeyValuePair[tabMSMI.Count]; // salvo tutti i valori StateMachineIngressi... int i = 0; foreach (var item in tabMSMI) { answ[i] = new KeyValuePair(item.IdxMacchina, item.IdxFamigliaIngresso.ToString()); i++; } // verifico il timeout (default 60 sec...) var sTOutSmi = await tryGetConfigAsync("TmOut.MSMI"); int tOut = 60; int.TryParse(sTOutSmi, out tOut); tOut = tOut <= 60 ? 60 : tOut; // salvo in redis! await RedisSetHashAsync(currHash, answ, tOut); return answ; } /// /// Processa registrazione EVENTO CONTEGGIO PEZZI x una data macchina IOB /// /// Macchina /// Pezzi da registrare /// public async Task saveCaricoPezzi(string idxMacchina, string qty) { // default: 0, non registrato x cautela... string answ = "0"; // controllo per proseguire if (string.IsNullOrEmpty(idxMacchina) || string.IsNullOrEmpty(qty)) { string errore = $"Errore: mancano parametri macchina/incremento: idxMacchina {idxMacchina} | qty {qty}"; Log.Error(errore); answ = errore; } else { int numPzIncr = -1; int.TryParse(qty, out numPzIncr); // se il conteggio è >= 0 SALVO evento... if (numPzIncr >= 0) { // recupero info tra cui ODL corrente Dictionary datiMacc = await mDatiMacchineAsync(idxMacchina); // registro evento 120 --> contapezzi in blocco !!!HARD CODED!!! !!!FIXME!!! int idxEvento = 120; DateTime adesso = DateTime.Now; string codArticolo = "ND"; if (datiMacc.ContainsKey("CodArticolo")) { codArticolo = datiMacc["CodArticolo"]; } // creo evento EventListModel newRecEv = new EventListModel() { CodArticolo = codArticolo, IdxMacchina = idxMacchina, IdxTipo = idxEvento, InizioStato = adesso, MatrOpr = 0, pallet = "-", Value = qty }; // salva e processa var resp = await scriviRigaEventoAsync(newRecEv); // registro in risposta che è andato tutto bene... ovvero la qty richiesta... answ = qty; } } return answ; } /// /// Processa registrazione EVENTO CONTEGGIO PEZZI x una data macchina IOB /// /// Macchina /// Pezzi da registrare /// DataOra evento /// DataOra corrente /// public async Task SaveCaricoPezziAsync(string idxMacchina, string qty, string dtEve = "", string dtCurr = "") { // default: 0, non registrato x cautela... string answ = "0"; // Verifica se evento realtime oppure ho data specificata x processing @dtEve DateTime adesso = DateTime.Now; DateTime dtEvent = adesso; bool rtimeProc = string.IsNullOrEmpty(dtEve); if (!rtimeProc) { dtEvent = GetSrvDtEvent(dtEve, dtCurr); } // controllo per proseguire if (!string.IsNullOrEmpty(idxMacchina) && !string.IsNullOrEmpty(qty)) { int numPzIncr = -1; int.TryParse(qty, out numPzIncr); // se il conteggio è >= 0 SALVO evento... if (numPzIncr >= 0) { var listOdl = await IocDbController.OdlListByMaccPeriodoAsync(idxMacchina, dtEvent, dtEvent.AddSeconds(1)); if (listOdl != null && listOdl.Count > 0) { string codArticolo = listOdl.FirstOrDefault()?.CodArticolo ?? "ND"; // registro evento 120 --> contapezzi in blocco !!!HARD CODED!!! !!!FIXME!!! int idxEvento = 120; // creo evento EventListModel newRecEv = new EventListModel() { CodArticolo = codArticolo, IdxMacchina = idxMacchina, IdxTipo = idxEvento, InizioStato = dtEvent, MatrOpr = 0, pallet = "-", Value = qty }; // salva e processa var resp = await scriviRigaEventoAsync(newRecEv); // registro in risposta che è andato tutto bene... ovvero la qty richiesta... answ = qty; } } } else { string errore = $"Errore: mancano parametri macchina/incremento: idxMacchina {idxMacchina} | qty {qty}"; Log.Error(errore); answ = errore; } return answ; } /// /// Processa registrazione di un counter x una data macchina IOB /// /// /// contapezzi /// public async Task SaveCounterAsync(string idxMacchina, string counter) { string answ = "0"; // inizio processing vero e proprio INPUT... if (string.IsNullOrEmpty(idxMacchina)) { string errore = "Errore: parametro macchina vuoto"; Log.Info(errore); answ = errore; } else { if (string.IsNullOrEmpty(counter)) { string errore = "Errore: parametro counter vuoto"; Log.Error(errore); answ = errore; } else { int newCounter = -1; int.TryParse(counter, out newCounter); // se il conteggio è >= 0 SALVO come nuovo conteggio... if (newCounter >= 0) { var currKey = Utils.RedKeyPzCount(idxMacchina, MpIoNS); RedisValue rawData = await redisDb.StringGetAsync(currKey); if (rawData.HasValue) { // salvo per + tempo... await redisDb.StringSetAsync(currKey, answ.ToString()); answ = counter; } else { int currCount = await PzCounterTcAsync(idxMacchina); answ = currCount.ToString(); // salvo per meno tempo... await redisDb.StringSetAsync(currKey, answ); } } } } return answ; } public async Task SaveDataItemsAsync(string id, List dataList) { bool answ = false; if (mongoController != null) { answ = mongoController.SaveMachineDataItems(id, dataList); } else { // modalità con SqlSb (no mongo) if (useFactory) { await using var scope = _scopeFactory.CreateAsyncScope(); var mtcService = scope.ServiceProvider.GetRequiredService(); answ = await mtcService.ReplaceMachineDataAsync(id, dataList); } else { answ = await MtcService.ReplaceMachineDataAsync(id, dataList); } } return answ; } public async Task SaveMachine2Iob(string idxMacchina, string serData) { bool fatto = false; var currKey = Utils.RedKeyMach2Iob(idxMacchina); // se != null --> salvo! if (!string.IsNullOrEmpty(serData)) { fatto = await redisDb.StringSetAsync(currKey, serData); } return fatto; } public async Task SaveMachineIobConf(string idxMacchina, Dictionary currDict) { bool fatto = false; var currKey = Utils.RedKeyMachIobConf(idxMacchina); // se != null --> salvo! if (currDict != null) { await RedisSetHashDictAsync(currKey, currDict); fatto = true; } return fatto; } /// /// salva il segnale di "microstato" (segnale) ASYNC /// /// idx macchina /// valOut ingresso /// data-ora evento (server) /// contatore sequenza dati inviati /// public async Task saveSigLogAsync(string idxMacchina, string valore, DateTime dtEve, int contatore) { SignalLogModel newRec = new SignalLogModel() { IdxMacchina = idxMacchina, DtCurr = DateTime.Now, DtEve = dtEve, Contatore = contatore, Valore = valore }; return await IocDbController.SignalLogInsertAsync(newRec); } /// /// scrive un evento di keepalive sulla tabella /// /// /// /// public async Task ScriviKeepAliveAsync(string IdxMacchina, DateTime oraMacchina) { // cerco se ho keep alive in redis, var currKey = Utils.RedKeyHash($"KeepAlive:{IdxMacchina}"); bool keyPresent = await RedisKeyPresentAsync(currKey); // se NON presente salvo in REDIS con TTL 10 sec e sul DB... if (!keyPresent) { DateTime adesso = DateTime.Now; await redisDb.StringSetAsync(currKey, adesso.ToString("s"), TimeSpan.FromSeconds(30)); // effettuo scrittura sul DB await IocDbController.KeepAliveUpsertAsync(IdxMacchina, DateTime.Now, oraMacchina); } } /// /// Salvataggio YAML completo di configurazione dell'IOB /// /// /// /// public async Task SetIobConfYamlAsync(string idxMacchina, string iobConfFull) { bool answ = false; // se ho un area memoria valida... if (!string.IsNullOrEmpty(iobConfFull)) { // salvo! var currKey = Utils.RedKeyIobConfYaml(idxMacchina, MpIoNS); answ = await redisDb.StringSetAsync(currKey, iobConfFull); } return answ; } public async Task SetIobMemMap(string idxMacchina, PlcMemMapDto currMap) { bool answ = false; // se ho un area memoria valida... if (currMap != null) { // salvo! var currKey = Utils.RedKeyIobMemMap(idxMacchina, MpIoNS); string serVal = JsonConvert.SerializeObject(currMap); answ = await redisDb.StringSetAsync(currKey, serVal); } return answ; } /// /// Restitusice elenco KVP dei campi della State Machine ingressi nel formato /// currKey: cState_nVal (current MICRO-STATE + "_" + new Value) /// value: iTipoEv_nState (IdxTipoEv da trasmettere + New MICRO-STATE /// /// /// public async Task[]> StateMachInByKeyAsync(int idxFamIn) { // hard coded dimensione vettore DatiMacchine KeyValuePair[] answ = new KeyValuePair[1]; // iniziualizzo con un valOut... 0/0 answ[0] = new KeyValuePair("0", "0"); // ORA recupero da memoria redis... try { var currHash = Utils.GetHashSMI(idxFamIn); answ = await RedisGetHashAsync(currHash); // se è vuoto... leggo da DB e popolo! if (answ.Length == 0) { answ = await resetSMIAsync(idxFamIn); } } catch (Exception exc) { Log.Error($"Errore in compilazione State Machine Ingressi x Redis:{Environment.NewLine}{exc}"); } return answ; } /// /// Statistiche ODL calcolate (da stored stp_STAT_ODL) /// /// public Task> StatOdl(int IdxOdl) { return SpecDbController.OdlStart(IdxOdl); } /// /// restituisce il valOut da REDIS associato al tag richeisto /// /// Chiave in cui cercare il valOut /// public string TagConfGetKey(string redKey) { string outVal = ""; // cerco in REDIS la conf x l'IOB var rawData = redisDb.StringGet(redKey); if (!string.IsNullOrEmpty(rawData)) { outVal = $"{rawData}"; } return outVal; } /// /// Elenco setup dei tag conf correnti /// /// public Task>> TagsGetAll() { return Task.FromResult(currTagConf); } /// /// Esegue traduzione dato vocabolario da Lingua + Lemma /// /// /// /// public string Traduci(string lemma, string lingua) { string answ = $"[{lemma}]"; // verifico se ho qualcosa nell'obj vocabolario... if (ObjVocabolario == null || ObjVocabolario.Count == 0) { // inizializzo il vocabolario... ObjVocabolario = VocabolarioGetAll(); } var record = ObjVocabolario.Where(x => x.Lingua == lingua && x.Lemma == lemma).FirstOrDefault(); if (record != null) { answ = record.Traduzione; } return answ; } public async Task updateDossierValue(DossierModel currDoss, FluxLogDTO editFL) { bool answ = false; // recupero intero set valori dossier deserializzando... var fluxLogList = FluxLogDtoGetByFlux(currDoss.Valore); await Task.Delay(1); // se tutto ok if (fluxLogList != null) { // da provare...!!!! // elimino vecchio record var currRec = fluxLogList.FirstOrDefault(x => x.CodFlux == editFL.CodFlux && x.dtEvento == editFL.dtEvento); if (currRec != null) { fluxLogList.Remove(currRec); // aggiungo nuovo fluxLogList.Add(editFL); } // serializzo nuovamente valOut DossierFluxLogDTO? result = new DossierFluxLogDTO(); var ODLflux = result.ODL.ToList(); foreach (var item in fluxLogList) { ODLflux.Add(item); } DossierFluxLogDTO updatedResult = new DossierFluxLogDTO() { ODL = ODLflux }; string rawVal = JsonConvert.SerializeObject(updatedResult); currDoss.Valore = rawVal; // aggiorno record sul DB await SpecDbController.DossiersUpdateValore(currDoss); } return answ; } /// /// Effettua UPSERT elenco parametri correnti x IOB (se c'è UPDATE, se manca ADD) /// /// /// /// public async Task UpsertCurrObjItemsAsync(string idxMacchina, List innovations) { bool answ = false; if (innovations != null) { Log.Info($"upsertCurrObjItems | idxMacchina: {idxMacchina} | {innovations.Count} innovations"); // leggo i valori attuali... List actValues = await MachineParamListAsync(idxMacchina); // per ogni valOut passatomi faccio insert o update rispetto elenco valori correnti in REDIS foreach (var item in actValues) { // cerco nelle innovazioni SE CI SIA il valOut... var trovato = innovations.Find(obj => obj.uid == item.uid); // se non trovato nelle innovazioni... if (trovato == null) { // lo ri-aggiungo x non perderlo innovations.Add(item); Log.Trace($"innovations | add | item.uid: {item.uid} | item.value: {item.value}"); } else // altrimenti aggiorno campo (non trasmesso) name e tengo il resto... { trovato.name = item.name; Log.Info($"innovations | update | item.uid: {item.uid} | item.value: {item.value} --> {trovato.value} "); } } // serializzo e salvo string serVal = JsonConvert.SerializeObject(innovations); var currKey = Utils.RedKeyCurrObjItems(idxMacchina, MpIoNS); RedisValue rawData = await redisDb.StringSetAsync(currKey, serVal); } return answ; } /// /// Restituisce il valore SPECIFICATO per la state machine ingressi /// value: iTipoEv_nState (IdxTipoEv da trasmettere + New MICRO-STATE) /// /// /// /// /// public async Task ValoreSmiAsync(int idxFamIn, int idxMicroStato, int valoreIn) { string valOut = ""; var currHash = Utils.GetHashSMI(idxFamIn); string field = $"{idxMicroStato}_{valoreIn}"; var searchVal = await RedisGetHashFieldAsync(currHash, field); if (!searchVal.HasValue) { // ricarico tabella (salvando in redis x ricerca successiva)! var valori = await StateMachInByKeyAsync(idxFamIn); if (valori.Any(x => x.Key == field)) { searchVal = valori.FirstOrDefault(x => x.Key == field).Value; } } valOut = $"{searchVal}"; return valOut; } /// /// Elenco completo tabella Vocabolario /// /// public List VocabolarioGetAll() { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); List? result = new List(); string source = "REDIS"; // cerco in redis... RedisValue rawData = redisDb.StringGet(Utils.redisVocabolario); if (!string.IsNullOrEmpty($"{rawData}")) { result = JsonConvert.DeserializeObject>($"{rawData}"); } else { result = SpecDbController.VocabolarioGetAll(); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(Utils.redisVocabolario, rawData, getRandTOut(redisLongTimeCache / 5)); source = "DB"; } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"VocabolarioGetAll Read from {source}: {ts.TotalMilliseconds}ms"); if (result == null) { result = new List(); } return result; } #endregion Public Methods #region Internal Methods /// /// Recupera ArtNum dato CodArt (per impianti che accettano solo INT in scrittura) /// /// /// CodArt richiesto, se vuoto restituisce TUTTI i valori in tabella di decodifica /// /// Dizionario contenente le righe delle codifiche attive Articolo/Numero internal async Task> GetArtNumAsync(string codArt) { Dictionary answ = new Dictionary(); var currData = await DecNumArtGetFiltAsync(codArt); foreach (var item in currData) { answ.Add(item.CodArticolo, item.NumART); } return answ; } #endregion Internal Methods #region Protected Fields protected Random rand = new Random(); #endregion Protected Fields #region Protected Properties protected string canCacheParametri { get; set; } = ""; #endregion Protected Properties #region Protected Methods /// /// Restituisce un timeout dai minuti richiesti + tempo random 1..60 sec /// /// /// protected TimeSpan getRandTOut(double stdMinutes) { double rndValue = stdMinutes + (double)rand.Next(1, 60) / 60; return TimeSpan.FromMinutes(rndValue); } #endregion Protected Methods #region Private Fields private static IConfiguration _configuration = null!; private static ILogger _logger = null!; private static Logger Log = LogManager.GetCurrentClassLogger(); private static IMtcSetupService MtcService = null!; #if false /// /// Elenco completo valori Macchine 2 Slave /// /// public List Macchine2SlaveGetAll() { List? result = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); string readType = "DB"; string currKey = $"{Utils.redisBaseAddr}:M2STab"; // cerco in redis dato valore sel macchina... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}"); readType = "REDIS"; } else { result = IocDbController.Macchine2Slave(); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); } if (result == null) { result = new List(); } stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Debug($"Macchine2SlaveGetAll | Read from {readType}: {ts.TotalMilliseconds}ms"); return result; } #endif private readonly IServiceScopeFactory _scopeFactory; /// /// Provider CultureInfo x parse valori (es dataora) /// private CultureInfo ciProvider = CultureInfo.InvariantCulture; /// /// Formato dataora standard x parsing /// private string dtFormat = "yyyyMMddHHmmssfff"; /// /// Ultima esecuzione pulizia RRL x evitgare congestioni /// private DateTime lastCleanupRRL = DateTime.Now.AddHours(-1); /// /// MS max age x dato MSE /// private int maxAge = 2000; private string MpIoNS = ""; /// /// Oggetto vocabolario x uso continuo traduzione /// private List ObjVocabolario = new List(); /// /// Oggetto per connessione a REDIS /// private ConnectionMultiplexer redisConn = null!; /// /// Oggetto per connessione a REDIS modalità admin (ex flux dati) /// private ConnectionMultiplexer redisConnAdmin = null!; /// /// Oggetto DB redis da impiegare x chiamate R/W /// private IDatabase redisDb = null!; private int redisLongTimeCache = 5; private int redisShortTimeCache = 2; /// /// Generatore random classe /// private Random rnd = new Random(); private bool useFactory = false; #endregion Private Fields #region Private Methods /// /// Verifica se sia necessario inserire un cambio di stato impianto (DiarioDi Bordo) in modalità batch /// /// /// /// /// /// /// /// /// private async Task CheckCambiaStatoBatchAsync(tipoInputEvento tipoInput, string IdxMacchina, DateTime InizioStato, int IdxTipo, string CodArt, string Value, int MatrOpr, string pallet) { await IocDbController.CheckCambiaStatoBatchAsync(tipoInput, IdxMacchina, InizioStato, IdxTipo, CodArt, Value, MatrOpr, pallet); #if false List listTransit = new List(); TransizioneStatiModel? rigaTrans = null; switch (tipoInput) { case tipoInputEvento.barcode: // effettuo cambio stato INDIPENDENTEMENTE da stato precedente listTransit = await IocDbController.SMES_getUserForcedAsync(IdxMacchina, IdxTipo); if (listTransit.Count > 0) { rigaTrans = listTransit.FirstOrDefault(); // solo se cambia stato... if (rigaTrans != null && rigaTrans.IdxStato != rigaTrans.next_IdxStato) { await IocDbController.DDB_InsStatoBatchAsync(IdxMacchina, InizioStato, rigaTrans.next_IdxStato, CodArt, Value, MatrOpr, pallet); // aggiorno MSE await IocDbController.RecalcMseAsync(IdxMacchina, 0); } } else { Log.Debug($"Non trovata riga per: BARCODE | IdxMacchina: {IdxMacchina} | IdxTipo: {IdxTipo} | CodArt: {CodArt} | Value: {Value} | MatrOpr: {MatrOpr} | pallet: {pallet}"); } break; case tipoInputEvento.hw: // verifico se ci sia necessità di cambio stato listTransit = await IocDbController.SMES_getHwTransitionsAsync(IdxMacchina, IdxTipo); if (listTransit.Count > 0) { rigaTrans = listTransit.FirstOrDefault(); if (rigaTrans != null && rigaTrans.IdxStato != rigaTrans.next_IdxStato) { await IocDbController.DDB_InsStatoBatchAsync(IdxMacchina, InizioStato, rigaTrans.next_IdxStato, CodArt, Value, MatrOpr, pallet); // aggiorno MSE await IocDbController.RecalcMseAsync(IdxMacchina, 0); } } else { Log.Debug($"Non trovata riga per: HW | IdxMacchina: {IdxMacchina} | IdxTipo: {IdxTipo} | CodArt: {CodArt} | Value: {Value} | MatrOpr: {MatrOpr} | pallet: {pallet}"); } break; default: break; } #endif } /// /// Restituisce l'elenco codici flusso (da confFlux) x una macchina (se presenti) Impiegata /// anche cache redis /// /// /// private async Task> ConfFluxMach(string idxMacchina) { List resultList = new List(); string tag = string.IsNullOrEmpty(idxMacchina) ? "ALL" : idxMacchina; var currKey = $"{Utils.redisConfFlux}:{tag}"; RedisValue rawData = await redisDb.StringGetAsync(currKey); if (rawData.HasValue) { resultList = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); } else { var dbData = await IocDbController.ConfFluxFiltAsync(idxMacchina); resultList = dbData .Select(x => x.CodFlux) .ToList(); // serializzo e salvo... rawData = JsonConvert.SerializeObject(resultList); await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache)); } if (resultList == null) { resultList = new(); } return resultList; } /// /// Svuota la cache redis x l'elenco delle righe di confFlux x una macchina (se presenti) /// /// /// private async Task DossierLastByMachResetAsync(string idxMacchina) { bool answ = false; var currKey = $"{Utils.redisDossByMacLast}:{idxMacchina}"; await redisDb.KeyDeleteAsync(currKey); return answ; } /// /// Helper standardizzazione valOut dataora ricevuto da IOB remoti /// /// /// private string dtFormStd(string? s) { return s?.Length > 17 ? s[..17] : s?.PadRight(17, '0') ?? string.Empty; } /// /// Restituisce l'elenco delle data-ora di confFlux x una macchina (se presenti) Impiegata /// anche cache redis /// /// /// num record da recuperare /// private async Task> FluxLogFirstByMachAsync(string idxMacchina, int numMax = 10) { List resultList = new List(); var currKey = $"{Utils.redisFluxByMacFirst}:{idxMacchina}"; RedisValue rawData = await redisDb.StringGetAsync(currKey); if (rawData.HasValue) { resultList = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); } else { var dbData = await IocDbController.FluxLogFirstByMaccAsync(idxMacchina, numMax); resultList = dbData .Select(x => x.dtEvento) .ToList(); // serializzo e salvo... rawData = JsonConvert.SerializeObject(resultList); await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache)); } if (resultList == null) { resultList = new List(); } return resultList; } /// /// Effettua vera chiamata x salvataggio snapshot dati FluxLog /// /// /// /// /// private async Task FluxLogSaveSnapshotAsync(string id, DateTime dtStart, DateTime dtEnd, string caller) { string answ = ""; DateTime dataOraEvento = DateTime.Now; Log.Debug($"{caller} | Richiesta snapshot dati FluxLog macchina: id: {id} | periodo: {dtStart} - {dtEnd}"); try { bool fatto = await IocDbController.FluxLogTakeSnapshotLastAsync(id, dtStart, dtEnd); answ = fatto ? "OK" : "KO"; } catch (Exception exc) { Log.Error($"Errore in {caller}{Environment.NewLine}{exc}"); answ = "NO"; } return answ; } /// /// Recupero info ODL corrente da dati prod macchina /// /// /// private async Task GetCurrOdlByProdAsync(string idxMacchina) { string answ = ""; // recupero stato... var datiProd = await StatoProdMacchinaAsync(idxMacchina, DateTime.Now); if (datiProd != null) { answ = datiProd.IdxOdl.ToString(); } // ultimo controllo su idxOdl... answ = answ == "" ? "0" : answ; // restituisco! return answ; } /// /// Restituisce il valOut booleano se la macchina sia di tipo MULTI (con più state machine x INGRESSI) /// usando dati macchina in cache per evitare lookup ridondanti /// /// /// /// private bool isMulti(string idxMacchina, Dictionary datiMacc) { bool answ = false; try { answ = Convert.ToBoolean(datiMacc.ContainsKey("Multi") && datiMacc["Multi"] == "1"); } catch (Exception exc) { Log.Error($"Eccezione in isMulti{Environment.NewLine}{exc}"); } return answ; } private async Task> ListMasterAsync() { HashSet result = new(); string currKey = $"{Utils.redisBaseAddr}:ListMaster"; RedisValue rawData = await redisDb.StringGetAsync(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); } else { var fullList = await Macchine2SlaveGetAllAsync(); result = fullList.Select(x => x.IdxMacchina).ToHashSet(); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache * 10)); } return result; } private async Task> ListSlaveAsync() { HashSet result = new(); string currKey = $"{Utils.redisBaseAddr}:ListSlave"; RedisValue rawData = await redisDb.StringGetAsync(currKey); if (rawData.HasValue) { result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); } else { var fullList = await Macchine2SlaveGetAllAsync(); result = fullList.Select(x => x.IdxMacchinaSlave).Distinct().ToHashSet(); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache * 10)); } return result; } private async Task POdlFlushCache() { bool answ = false; RedisValue pattern = new RedisValue($"{Utils.redisXdlData}:*"); answ = await RedisFlushPatternAsync(pattern); pattern = new RedisValue($"{Utils.redisPOdlByOdl}:*"); answ = await RedisFlushPatternAsync(pattern); pattern = new RedisValue($"{Utils.redisPOdlByPOdl}:*"); answ = await RedisFlushPatternAsync(pattern); pattern = new RedisValue($"{Utils.redisPOdlList}:*"); answ = await RedisFlushPatternAsync(pattern); return answ; } /// /// Calcola l'effettivo valOut da passare alla macchina a stati INGRESSI usando dati macchina in cache /// /// /// /// /// private string preProcInput(string idxMacchina, string valore, Dictionary datiMacc) { string newVal = ""; try { // variabili int valINT = 0; int BitFilt = 0; int BSR = 0; bool ExplodeBit = false; int NumBit = 0; int newValInt = 0; // recupero parametri dalla cache... int.TryParse(datiMacc.ContainsKey("BitFilt") ? datiMacc["BitFilt"] : "0", out BitFilt); int.TryParse(datiMacc.ContainsKey("BSR") ? datiMacc["BSR"] : "0", out BSR); Boolean.TryParse(datiMacc.ContainsKey("ExplodeBit") ? datiMacc["ExplodeBit"] : "false", out ExplodeBit); // non usato (x ora) int.TryParse(datiMacc.ContainsKey("NumBit") ? datiMacc["NumBit"] : "0", out NumBit); // recupero valOut valINT = int.Parse(valore, NumberStyles.HexNumber); // filtro newValInt = MP.Core.Utils.bMaskInt(valINT, BitFilt); // effettuo eventuale BitShiftRight if (BSR > 0) { newValInt = newValInt >> BSR; } // effettuo eventuale esplosione in BIT esclusivi if (ExplodeBit) { newValInt = Convert.ToInt32(1 << newValInt); } // riconverto a STRING HEX!!! newVal = newValInt.ToString("X"); } catch { newVal = valore; } return newVal; } private async Task resetCacheArticoli() { RedisValue pattern = new RedisValue($"{Utils.redisArtByDossier}:*"); await RedisFlushPatternAsync(pattern); pattern = new RedisValue($"{Utils.redisArtList}:*"); await RedisFlushPatternAsync(pattern); } /// /// Restitusice elenco KVP dei campi DatiMacchine + StatoMacchine per l'impianto indicato /// /// /// private async Task> ResetDatiMacchinaAsync(string idxMacc) { Dictionary result = new Dictionary(); VMSFDModel? dbResult = null; if (useFactory) { await using var scope = _scopeFactory.CreateAsyncScope(); var mtcService = scope.ServiceProvider.GetRequiredService(); dbResult = await mtcService.VMSFDGetByMaccAsync(idxMacc); } else { dbResult = await IocDbController.VMSFDGetByMaccAsync(idxMacc); } if (dbResult == null) return new Dictionary(); double numSecCache = redisLongTimeCache; // converto in formato dizionario... if (dbResult != null) { // salvo 1:1 i valori... STATO result.Add("IdxMicroStato", $"{dbResult.IdxMicroStato}"); result.Add("IdxStato", $"{dbResult.IdxStato}"); result.Add("CodArticolo", $"{dbResult.CodArticolo}"); result.Add("insEnabled", $"{dbResult.InsEnabled}"); result.Add("sLogEnabled", $"{dbResult.SLogEnabled}"); result.Add("pallet", $"{dbResult.Pallet}"); result.Add("CodArticolo_A", $"{dbResult.CodArticoloA}"); result.Add("CodArticolo_B", $"{dbResult.CodArticoloB}"); result.Add("TempoCicloBase", $"{dbResult.TempoCicloBase}"); result.Add("PzPalletProd", $"{dbResult.PzPalletProd}"); result.Add("MatrOpr", $"{dbResult.MatrOpr}"); result.Add("lastVal", $"{dbResult.LastVal}"); result.Add("TCBase", $"{dbResult.TempoCicloBase}"); //...e SETUP result.Add("CodMacc", $"{dbResult.Codmacchina}"); result.Add("IdxFamIn", $"{dbResult.IdxFamigliaIngresso}"); result.Add("Multi", $"{dbResult.Multi}"); result.Add("BitFilt", $"{dbResult.BitFilt}"); result.Add("MaxVal", $"{dbResult.MaxVal}"); result.Add("BSR", $"{dbResult.Bsr}"); result.Add("ExplodeBit", $"{dbResult.ExplodeBit}"); result.Add("NumBit", $"{dbResult.NumBit}"); result.Add("IdxFamMacc", $"{dbResult.IdxFamiglia}"); result.Add("simplePallet", $"{dbResult.SimplePallet}"); result.Add("palletChange", $"{dbResult.PalletChange}"); // durata cache in secondi dal valOut insEnabled... //double numSecCache = ((result["insEnabled"].ToLower() == "true") ? redisShortTimeCache : redisLongTimeCache); numSecCache = dbResult.InsEnabled ? redisShortTimeCache : redisLongTimeCache; } else { } // dati master/slave string isMaster = (await ListMasterAsync()).Contains(idxMacc) ? "1" : "0"; string isSlave = (await ListSlaveAsync()).Contains(idxMacc) ? "1" : "0"; result.Add("Master", isMaster); result.Add("Slave", isSlave); // Processing redis in transazoine... var redKey = Utils.RedKeyDatiMacc(idxMacc, MpIoNS); // variazione con redis transaction... var transaction = redisDb.CreateTransaction(); // 1. Eliminiamo la chiave (rimuove i vecchi campi) _ = transaction.KeyDeleteAsync(redKey); // 2. Prepariamo gli HashEntry per il batch (più efficiente di un Dictionary) HashEntry[] entries = result.Select(kvp => new HashEntry(kvp.Key, kvp.Value)).ToArray(); // 3. Inseriamo i nuovi valori _ = transaction.HashSetAsync(redKey, entries); // 4. Impostiamo l'expiration in un unico colpo _ = transaction.KeyExpireAsync(redKey, TimeSpan.FromSeconds(numSecCache)); // Eseguiamo tutto in un unico viaggio verso Redis bool success = await transaction.ExecuteAsync(); return result; } /// /// Resetta (rileggendo) i dati della State Machine ingressi nel formato /// currKey: cState_nVal (current MICRO-STATE + "_" + new Value) /// value: iTipoEv_nState (IdxTipoEv da trasmettere + New MICRO-STATE) /// /// /// private async Task[]> resetSMIAsync(int idxFamIn) { var currHash = Utils.GetHashSMI(idxFamIn); // leggo da DB... var tabSMI = await IocDbController.StateMachineIngressiAsync(idxFamIn); KeyValuePair[] answ = new KeyValuePair[tabSMI.Count]; // salvo tutti i valori StateMachineIngressi... int i = 0; string key = ""; string val = ""; foreach (var item in tabSMI) { key = string.Format("{0}_{1}", item.IdxMicroStato, item.ValoreIngresso); val = string.Format("{0}_{1}", item.IdxTipoEvento, item.NextIdxMicroStato); answ[i] = new KeyValuePair(key, val); i++; } // verifico il timeout (default 60 sec...) int tOut = 300; var sTOutSmi = await tryGetConfigAsync("TmOut.SMI"); if (!string.IsNullOrEmpty(sTOutSmi)) { int.TryParse(sTOutSmi, out tOut); } // salvo in redis! await RedisSetHashAsync(currHash, answ, tOut); return answ; } #if false /// /// Scrive una riga di evento nel db + check cambio stato DiarioDiBordo /// /// codice macchina /// private inputComandoMapo scriviRigaEvento(EventListModel newRec) { bool inserito = false; try { // inserisco evento inserito = IocDbController.EvListInsert(newRec); // faccio controllo per eventuale cambio stato da tab transizioni... CheckCambiaStatoBatchAsync(tipoInputEvento.hw, newRec.IdxMacchina, newRec.InizioStato ?? DateTime.Now, newRec.IdxTipo, newRec.CodArticolo, newRec.Value, newRec.MatrOpr, newRec.pallet); } catch (Exception exc) { Log.Error($"Errore in scriviRigaEvento | IdxMacchina {newRec.IdxMacchina} | IdxTipo {newRec.IdxTipo} | codArticolo {newRec.CodArticolo} | Value {newRec.Value} | MatrOpr {newRec.MatrOpr} | Pallet {newRec.pallet} | dTime {newRec.InizioStato}{Environment.NewLine}{exc}"); } // formatto output inputComandoMapo answ = new inputComandoMapo(); answ.outValue = inserito.ToString(); answ.needStatusRefresh = true; return answ; } #endif /// /// Scrive una riga di evento nel db + check cambio stato DiarioDiBordo /// /// codice macchina /// private async Task scriviRigaEventoAsync(EventListModel newRec) { bool inserito = false; try { // inserisco evento inserito = await IocDbController.EvListInsertAsync(newRec); // faccio controllo per eventuale cambio stato da tab transizioni... await CheckCambiaStatoBatchAsync(tipoInputEvento.hw, newRec.IdxMacchina, newRec.InizioStato ?? DateTime.Now, newRec.IdxTipo, newRec.CodArticolo, newRec.Value, newRec.MatrOpr, newRec.pallet); } catch (Exception exc) { Log.Error($"Errore in scriviRigaEvento | IdxMacchina {newRec.IdxMacchina} | IdxTipo {newRec.IdxTipo} | codArticolo {newRec.CodArticolo} | Value {newRec.Value} | MatrOpr {newRec.MatrOpr} | Pallet {newRec.pallet} | dTime {newRec.InizioStato}{Environment.NewLine}{exc}"); } // formatto output inputComandoMapo answ = new inputComandoMapo(); answ.outValue = inserito.ToString(); answ.needStatusRefresh = true; return answ; } /// /// Scrive una riga evento + una riga microstato insieme, ed effettua verifica necessità cambio stato /// /// Record MicroStatoMacchina /// record EventList /// private async Task scriviRigaEventoAsync(MicroStatoMacchinaModel newRecMsm, EventListModel newRecEv) { bool inserito = false; if (useFactory) { await using var scope = _scopeFactory.CreateAsyncScope(); var iocService = scope.ServiceProvider.GetRequiredService(); // inserisco evento inserito = await iocService.EvListMicroStatoInsertAsync(newRecMsm, newRecEv); // faccio controllo per eventuale cambio stato da tab transizioni... await iocService.CheckCambiaStatoBatchAsync(tipoInputEvento.hw, newRecEv.IdxMacchina, newRecEv.InizioStato ?? DateTime.Now, newRecEv.IdxTipo, newRecEv.CodArticolo, newRecEv.Value, newRecEv.MatrOpr, newRecEv.pallet); } else { try { // inserisco evento inserito = await IocDbController.EvListMicroStatoInsertAsync(newRecMsm, newRecEv); // faccio controllo per eventuale cambio stato da tab transizioni... await CheckCambiaStatoBatchAsync(tipoInputEvento.hw, newRecEv.IdxMacchina, newRecEv.InizioStato ?? DateTime.Now, newRecEv.IdxTipo, newRecEv.CodArticolo, newRecEv.Value, newRecEv.MatrOpr, newRecEv.pallet); } catch (Exception exc) { Log.Error($"Errore in scriviRigaEvento | IdxMacchina {newRecEv.IdxMacchina} | IdxTipo {newRecEv.IdxTipo} | codArticolo {newRecEv.CodArticolo} | Value {newRecEv.Value} | MatrOpr {newRecEv.MatrOpr} | Pallet {newRecEv.pallet} | dTime {newRecEv.InizioStato}{Environment.NewLine}{exc}"); } } // formatto output inputComandoMapo answ = new inputComandoMapo(); answ.outValue = inserito.ToString(); answ.needStatusRefresh = true; return answ; } /// /// Scrive una riga di evento manuale (barcode) nel db + check cambio stato DiarioDiBordo /// /// codice macchina /// private async Task scriviRigaEventoBarcodeAsync(EventListModel newRec) { bool inserito = false; try { // inserisco evento inserito = await IocDbController.EvListInsertAsync(newRec); // faccio controllo per eventuale cambio stato da tab transizioni... await CheckCambiaStatoBatchAsync(tipoInputEvento.barcode, newRec.IdxMacchina, newRec.InizioStato ?? DateTime.Now, newRec.IdxTipo, newRec.CodArticolo, newRec.Value, newRec.MatrOpr, newRec.pallet); } catch (Exception exc) { Log.Error($"Errore in scriviRigaEvento | IdxMacchina {newRec.IdxMacchina} | IdxTipo {newRec.IdxTipo} | codArticolo {newRec.CodArticolo} | Value {newRec.Value} | MatrOpr {newRec.MatrOpr} | Pallet {newRec.pallet} | dTime {newRec.InizioStato}{Environment.NewLine}{exc}"); } // formatto output inputComandoMapo answ = new inputComandoMapo(); answ.outValue = inserito.ToString(); answ.needStatusRefresh = true; return answ; } /// /// Stato prod macchina (completo) /// /// /// /// private async Task StatoProdMacchinaAsync(string idxMacchina, DateTime dtReq, bool forceDb = false) { // setup parametri costanti string source = "DB"; StatoProdModel? result = new StatoProdModel(); // cerco in _redisConn... string currKey = $"{Utils.redisStatoProd}:{idxMacchina}:{dtReq:HHmm}"; RedisValue rawData = await redisDb.StringGetAsync(currKey); if (rawData.HasValue && !forceDb) { result = JsonConvert.DeserializeObject($"{rawData}"); source = "REDIS"; } else { result = await IocDbController.StatoProdMacchinaAsync(idxMacchina, dtReq); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); await redisDb.StringSetAsync(currKey, rawData, TimeSpan.FromSeconds(60)); } if (result == null) { result = new StatoProdModel(); } return result; } /// /// Restituisce valOut della stringa (SE disponibile) /// /// /// private async Task tryGetConfigAsync(string keyName) { string answ = ""; // preselezione valori var configData = await ConfigGetAllAsync(); var currRec = configData.FirstOrDefault(x => x.Chiave == keyName); if (currRec != null) { answ = currRec.Valore; } return answ; } #if false /// /// Restituisce il valOut SPECIFICATO per la state machine ingressi /// value: iTipoEv_nState (IdxTipoEv da trasmettere + New MICRO-STATE) /// /// /// /// /// private string valoreSMI(int idxFamIn, int idxMicroStato, int valoreIn) { var currHash = Utils.GetHashSMI(idxFamIn); string field = string.Format("{0}_{1}", idxMicroStato, valoreIn); return RedisGetHashFieldAsync(currHash, field); } #endif /// /// cerca codice in anagrafica macchine ed eventualmente inserisce nuova macchina /// /// private async Task verificaIdxMacchinaAsync(string IdxMacchina) { bool needDB = false; try { // esecuzione in REDIS...cerco status macchina... if ((await mDatiMacchineAsync(IdxMacchina)).Count == 0) { needDB = true; } } catch { } if (needDB) { // verifico se esiste su DB var dbRec = await IocDbController.MacchineGetByIdxAsync(IdxMacchina); if (dbRec == null) { MacchineModel newRec = new MacchineModel() { IdxMacchina = IdxMacchina, CodMacchina = "0000", Nome = IdxMacchina, Descrizione = "Macchina non codificata", Note = "-", locazione = "", RecipeArchivePath = "", RecipePath = "" }; await IocDbController.MacchineUpsertAsync(newRec); // verifico ci sia un microstato macchina... var recMSM = await IocDbController.MicroStatoMacchinaGetByIdxMaccAsync(IdxMacchina); if (recMSM.Count == 0) { // inserisco nuovo stato... MicroStatoMacchinaModel msRec = new MicroStatoMacchinaModel() { IdxMacchina = IdxMacchina, IdxMicroStato = 0, InizioStato = DateTime.Now, Value = "00" }; await IocDbController.MicroStatoMacchinaUpsertAsync(msRec); } } } } #endregion Private Methods } }