diff --git a/MP.Core/DTO/EvDataDto.cs b/MP.Core/DTO/EvDataDto.cs new file mode 100644 index 00000000..3b175187 --- /dev/null +++ b/MP.Core/DTO/EvDataDto.cs @@ -0,0 +1,33 @@ +namespace MP.Core.DTO +{ + /// + /// Tracciato InputEvents in formato JSON valido + /// Derivato da input realtime valore=3&dtEve=20181206180600000&dtCurr=20181206180600000&cnt=999 + /// + public class EvDataDto + { + #region Public Properties + + /// + /// Contatore incrementale x riordino invio (opzionale) + /// + public int cnt { get; set; } = 0; + + /// + /// DataOra corrente della trasmissione + /// + public DateTime dtCurr { get; set; } = DateTime.Now; + + /// + /// DataOra evento + /// + public DateTime dtEve { get; set; } = DateTime.Now; + + /// + /// Valore del dato di flusso registrato + /// + public string valore { get; set; } = "-"; + + #endregion Public Properties + } +} diff --git a/MP.Core/DTO/FLogDataDto.cs b/MP.Core/DTO/FLogDataDto.cs new file mode 100644 index 00000000..2be657a8 --- /dev/null +++ b/MP.Core/DTO/FLogDataDto.cs @@ -0,0 +1,13 @@ +namespace MP.Core.DTO +{ + /// + /// Tracciato FluxLog in formato JSON valido + /// + public class FLogDataDto : EvDataDto + { + /// + /// nome del flusso + /// + public string flux { get; set; } = "ND"; + } +} diff --git a/MP.Core/DTO/FLogJsonPayloadDto.cs b/MP.Core/DTO/FLogJsonPayloadDto.cs new file mode 100644 index 00000000..30792672 --- /dev/null +++ b/MP.Core/DTO/FLogJsonPayloadDto.cs @@ -0,0 +1,14 @@ +namespace MP.Core.DTO +{ + /// + /// Array valori tipo flogData inviati come JSon + /// + public class FLogJsonPayloadDto + { + #region Public Properties + + public List fluxData { get; set; } = new(); + + #endregion Public Properties + } +} diff --git a/MP.Core/Utils.cs b/MP.Core/Utils.cs index 9f7c6efa..e4dc6965 100644 --- a/MP.Core/Utils.cs +++ b/MP.Core/Utils.cs @@ -257,7 +257,19 @@ namespace MP.Core } /// - /// Hash dati Task2Exe x la macchina specificata + /// Hash dati SAVED (EXE) TASK x la macchina specificata x poter ripristinare in caso di + /// perdita valore WRITE (ex savedTaskHash) + /// + /// + /// + public static RedisKey RedKeySavedTask2ExeMacc(string idxMacchina, string baseAddr = null) + { + var prefix = (baseAddr ?? redisBaseAddr).TrimEnd(':'); + return (RedisKey)$"{prefix}:SavedTask:{idxMacchina}"; + } + + /// + /// Hash dati Task2Exe x la macchina specificata (ex exeTaskHash) /// /// /// Chiave override per i valori in caso di dati che accedono al dominio dati di un altra app (es: baseAddr x IO legacy) @@ -268,6 +280,19 @@ namespace MP.Core return (RedisKey)$"{prefix}:ExeTask:{idxMacchina}"; } + /// + /// Hash dati ultimo FluxLog ricevuto (ex LiveFLogKeyHash) + /// + /// + /// + /// Chiave override per i valori in caso di dati che accedono al dominio dati di un altra app (es: baseAddr x IO legacy) + /// + public static RedisKey RedKeyLastFLog(string idxMacchina, string flog, string baseAddr = null) + { + var prefix = (baseAddr ?? redisBaseAddr).TrimEnd(':'); + return (RedisKey)$"{prefix}:FLOG:{idxMacchina}:{flog}"; + } + /// /// Formato RedisValue delal chaive richeista (completa) /// diff --git a/MP.Data/Controllers/MpIocController.cs b/MP.Data/Controllers/MpIocController.cs index 2557ef3a..9885d3a6 100644 --- a/MP.Data/Controllers/MpIocController.cs +++ b/MP.Data/Controllers/MpIocController.cs @@ -199,6 +199,56 @@ namespace MP.Data.Controllers return dbResult; } + /// + /// Aggiunta record FluxLog + /// + /// + /// + public bool FluxLogInsert(FluxLogModel newRec) + { + bool fatto = false; + using (var dbCtx = new MoonPro_FluxContext(_configuration)) + { + try + { + var currRec = dbCtx + .DbSetFluxLog + .Add(newRec); + dbCtx.SaveChanges(); + } + catch (Exception exc) + { + Log.Error($"Eccezione durante FluxLogInsert{Environment.NewLine}{exc}"); + } + } + return fatto; + } + + /// + /// Aggiunta record FluxLog Async + /// + /// + /// + public async Task FluxLogInsertAsync(FluxLogModel newRec) + { + bool fatto = false; + using (var dbCtx = new MoonPro_FluxContext(_configuration)) + { + try + { + var currRec = dbCtx + .DbSetFluxLog + .Add(newRec); + await dbCtx.SaveChangesAsync(); + } + catch (Exception exc) + { + Log.Error($"Eccezione durante FluxLogInsertAsync{Environment.NewLine}{exc}"); + } + } + return fatto; + } + public bool KeepAliveUpsert(string IdxMacc, DateTime OraServer, DateTime OraMacc) { bool fatto = false; diff --git a/MP.IOC/Controllers/IOBController.cs b/MP.IOC/Controllers/IOBController.cs index 82200bd5..3071e706 100644 --- a/MP.IOC/Controllers/IOBController.cs +++ b/MP.IOC/Controllers/IOBController.cs @@ -74,6 +74,78 @@ namespace MP.IOC.Controllers } } + /// + /// Invio record flog direttamente via URL GET + /// GET: IOB/flog/SIMUL_03?flux=PROG&valore=P0001&dtEve=20161223180600000&dtCurr=20161223180600000&cnt=999&disabKA=false + /// + /// IdxMacchina + /// CodFlux + /// Valore da registrare + /// DataOra evento (IOB time) + /// DataOra corrente (IOB time) + /// Contatore eventi (IOB) + /// Disabilitazione scrittura record KeepAlive + /// + [HttpGet("flog/{id}")] + public async Task Flog(string id, string flux, string valore, string dtEve, string dtCurr, string cnt = "0", string disabKA = "false") + { + if (string.IsNullOrEmpty(id)) return BadRequest("Missing ID"); + + // Multi: gestione carattere "|" trasformato in "#" + id = id.Replace("|", "#"); + + string answ = ""; + // formato yyyymmddHHMMSSnnn ovvero da anno a millisecondi + + DateTime dataOraEvento = DateTime.Now; + Log.Trace($"Valori letti: id: {id} | flux: {flux} valore: {valore}"); + try + { + int count = 0; + Int32.TryParse(cnt, out count); + bool disKa = false; + bool.TryParse(disabKA, out disKa); + answ = await DService.ProcessFluxLogAsync(id, flux, valore, dtEve, dtCurr, count, disKa); + return Ok(answ); + } + catch (Exception exc) + { + Log.Error($"Errore in flog{Environment.NewLine}{exc}"); + return StatusCode(StatusCodes.Status500InternalServerError, "NO"); + } + } + + /// + /// Processa una chiamata POST per l'invio di un array Json di oggetti fluxLog + /// PUT: IOB/flogJson/SIMUL_03 + /// + /// ID dell'IOB + /// Disabilitazione della scrittura del record KeepAlive + /// + [HttpPost("flogJson/{id}")] + public async Task FlogJson(string id, string disabKA = "false", [FromBody] string content = "") + { + if (string.IsNullOrEmpty(id)) return BadRequest("Missing ID"); + + // Multi: gestione carattere "|" trasformato in "#" + id = id.Replace("|", "#"); + + bool disableKa = false; + bool.TryParse(disabKA, out disableKa); + string answ = "-"; + // se ho dati... + if (content != "") + { + answ = await processFLogJsonAsync(id, disableKa, content); + return Ok(answ); + } + else + { + Log.Error($"Errore in FlogJson - no content"); + return StatusCode(StatusCodes.Status500InternalServerError, "NO"); + } + } + /// /// Recupera COUNTER x macchina: /// GET: IOB/getCounter/SIMUL_03 @@ -326,7 +398,6 @@ namespace MP.IOC.Controllers /// /// ID dell'IOB /// - [HttpPost] [HttpPost("saveMachineIobConf/{id}")] public async Task SaveMachineIobConf(string id, [FromBody] Dictionary currDict) { @@ -334,8 +405,8 @@ namespace MP.IOC.Controllers // Multi: gestione carattere "|" trasformato in "#" id = id.Replace("|", "#"); - string answ = "NA"; + string answ = "NA"; try { bool fatto = await DService.SaveMachineIobConf(id, currDict); @@ -530,5 +601,121 @@ namespace MP.IOC.Controllers private MpDataService DService { get; set; } #endregion Private Properties + + #region Private Methods + + /// + /// Effettivo processing FLogJson + /// + /// + /// + /// + /// + /// + private async Task processFLogJsonAsync(string idxMacc, bool disabKA, string content) + { + string answ = ""; + int insDone = 0; + // procedo a deserializzare in blocco l'oggetto... + FLogJsonPayloadDto receivedData = new FLogJsonPayloadDto(); + try + { + // deserializzo. + receivedData = JsonConvert.DeserializeObject(content) ?? new(); + } + catch (Exception exc) + { + Log.Error($"Errore in fase deserializzazione flogJson{Environment.NewLine}{exc}"); + answ = "NO"; + } + // se ho qualcosa da processare... + if (receivedData != null) + { + // per ogni valore --> salvo! + try + { + foreach (var item in receivedData.fluxData) + { + // formato datetime come yyyyMMddHHmmssfff -->es: 20181223180600000 + answ = await DService.ProcessFluxLogAsync(idxMacc, item.flux, item.valore, item.dtEve.ToString("yyyyMMddHHmmssfff"), item.dtCurr.ToString("yyyyMMddHHmmssfff"), item.cnt, disabKA); + } + // se vuoto --> OK! + if (string.IsNullOrEmpty(answ)) + { + answ = $"OK {insDone} processed"; + } + } + catch (Exception exc) + { + Log.Error($"Errore in fase invio valori flogJson{Environment.NewLine}{exc}"); + answ = "NO"; + } + // leggo parametri correnti... + DateTime adesso = DateTime.Now; + try + { + List currParams = DService.MachineParamList(idxMacc); + // ora per ogni valore RICEVUTO costruisco un oggetto in innovazioni da + // inviare...x salvare in stato parametri... + List innovazioni = new List(); + foreach (var item in receivedData.fluxData) + { + // flux = uuid del parametro + ObjItemDTO? trovato = currParams.Find(obj => obj.uid == item.flux); + // se lo trovo aggiorno... + if (trovato != null) + { + // aggiorno valore e data + trovato.value = item.valore; + trovato.lastRead = adesso; + trovato.lastMessage = item.valore; + // se fosse un valore WRITE + if (trovato.writable) + { + // ...e mi ha dato un valore vuoto --> mando un fix x riscrittura + if (string.IsNullOrEmpty(item.valore)) + { + Log.Debug($"flogJson | verifica parametri | {trovato.uid} | reqVal: {trovato.reqValue}"); + taskType currTask = (taskType)Enum.Parse(typeof(taskType), trovato.uid); + DService.AddCheckTask4Machine(idxMacc, currTask, item.valore); + } + } + else + { + // richiesto 10 sec prima... + trovato.lastRequest = adesso.AddSeconds(-10); + } + } + // altrimenti AGGIUNGO (READ ONLY)... + else + { + trovato = new ObjItemDTO + { + uid = item.flux, + name = item.flux, + value = item.valore, + lastRead = adesso, + lastRequest = adesso.AddSeconds(-10), + writable = false + }; + } + // lo carico in innovation + innovazioni.Add(trovato); + } + // faccio upsert innovations! + DService.UpsertCurrObjItems(idxMacc, innovazioni); + Log.Info($"processFLogJsonAsync | {idxMacc} | Updated {innovazioni.Count} par"); + } + catch (Exception exc) + { + Log.Error($"Errore in fase salvataggio innovazioni parametri correnti da flogJson{Environment.NewLine}{exc}"); + answ = "NO"; + } + } + + return answ; + } + + #endregion Private Methods } } \ No newline at end of file diff --git a/MP.IOC/Data/MpDataService.cs b/MP.IOC/Data/MpDataService.cs index 2c267361..3790574b 100644 --- a/MP.IOC/Data/MpDataService.cs +++ b/MP.IOC/Data/MpDataService.cs @@ -127,6 +127,43 @@ namespace MP.IOC.Data return fatto; } + /// + /// Verifica se sia da reinviare un task 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 valore saved + string savedVal = savedTask[taskKey.ToString()]; + // se ho un valore != "" --> rimetto in coda di invio... + if (!string.IsNullOrEmpty(savedVal) && (savedVal != taskVal)) + { + // leggo task attuali... + Dictionary currTask = mTaskMacchina(idxMacchina); + // rimetto in task da eseguire... + currTask[taskKey.ToString()] = savedVal; + answ = RedisSetHashDict(currHash, currTask); + 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) /// @@ -152,6 +189,61 @@ namespace MP.IOC.Data return answ; } + /// + /// Aggiunge un task 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 task attuali... + currTask = mTaskMacchina(idxMacchina); + if (currTask.ContainsKey($"{taskKey}")) + { + currTask[$"{taskKey}"] = taskVal; + } + else + { + currTask.Add($"{taskKey}", taskVal); + } + answ = RedisSetHashDict(currHash, currTask); + Log.Info($"Task ADD | hash: {currHash} | idxMacchina: {idxMacchina} | taskKey: {taskKey} | taskVal: {taskVal}"); + } + catch { } + // verifico in base al tipo di task se fare backup... + switch (taskKey) + { + case Enums.taskType.setArt: + case Enums.taskType.setComm: + case Enums.taskType.setPzComm: + case Enums.taskType.setProg: + // leggo task SALVATI attuali... + savedTask = mSavedTaskMacchina(idxMacchina); + savedTask[taskKey.ToString()] = taskVal; + answ = RedisSetHashDict(currSavedParHash, savedTask); + break; + + case Enums.taskType.endProd: + // salvo un DICT vuoto x resettare + savedTask = new Dictionary(); + answ = RedisSetHashDict(currSavedParHash, savedTask); + break; + + default: + break; + } + return answ; + } + public async Task> AnagStatiComm() { Stopwatch stopWatch = new Stopwatch(); @@ -1240,7 +1332,7 @@ namespace MP.IOC.Data } /// - /// Lista parametri correnti (ObjItemDTO) della macchina + /// Lista parametri correnti (ObjItemDTO) della macchina (ex getCurrObjItems) /// /// /// @@ -1275,7 +1367,7 @@ namespace MP.IOC.Data } /// - /// Lista parametri correnti (ObjItemDTO) della macchina + /// Lista parametri correnti (ObjItemDTO) della macchina (ex getCurrObjItems) /// /// /// @@ -1309,6 +1401,34 @@ namespace MP.IOC.Data return result; } + /// + /// Esegue aggiornamento MachineParamList (ex CurrObjItems) + /// + /// + /// + /// + public bool MachineParamListSet(string idxMacchina, List currData) + { + string serVal = JsonConvert.SerializeObject(currData); + var currKey = Utils.RedKeyCurrObjItems(idxMacchina, MpIoNS); + bool fatto = redisDb.StringSet(currKey, serVal); + return fatto; + } + + /// + /// 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) /// @@ -1344,9 +1464,7 @@ namespace MP.IOC.Data } } // serializzo e salvo - string serVal = JsonConvert.SerializeObject(innovations); - var currKey = Utils.RedKeyCurrObjItems(idxMacchina, MpIoNS); - RedisValue rawData = redisDb.StringSet(currKey, serVal); + answ = MachineParamListSet(idxMacchina, innovations); } return answ; } @@ -1386,9 +1504,7 @@ namespace MP.IOC.Data } } // serializzo e salvo - string serVal = JsonConvert.SerializeObject(innovations); - var currKey = Utils.RedKeyCurrObjItems(idxMacchina, MpIoNS); - RedisValue rawData = await redisDb.StringSetAsync(currKey, serVal); + answ = MachineParamListSet(idxMacchina, innovations); } return answ; } @@ -1459,9 +1575,31 @@ namespace MP.IOC.Data 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; + } + /// /// Restitusice elenco KVP - /// key: IdxMacchina + /// currKey: IdxMacchina /// value: IdxFamigliaIngresso /// /// @@ -1955,6 +2093,65 @@ namespace MP.IOC.Data 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...) + ScriviKeepAlive(idxMacchina, DateTime.Now); + } + + string answ = ""; + DateTime dataOraEvento = GetSrvDtEvent(dtEve, dtCurr); + // inizio processing vero e proprio INPUT... + if (idxMacchina != null && valore != null) + { + if (idxMacchina != "" && valore != "") + { + 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 valore ULTIMO del flux x recupero rapido ultimo valore + 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"; + } + else + { + string errore = "processFluxLog | Errore: parametri macchina/valore vuoti"; + Log.Error(errore); + answ = errore; + } + } + else + { + string errore = "processFluxLog | Errore: mancano parametri macchina/valore"; + Log.Error(errore); + answ = errore; + } + return answ; + } + /// /// Processa input da IOB eventualmente registrando i segnali inviati /// @@ -2361,7 +2558,7 @@ namespace MP.IOC.Data } catch (Exception arg) { - Log.Error($"Errore in redHashPresent per la key {key}{Environment.NewLine}{arg}"); + Log.Error($"Errore in redHashPresent per la currKey {key}{Environment.NewLine}{arg}"); } return result; @@ -2381,7 +2578,7 @@ namespace MP.IOC.Data } catch (Exception arg) { - Log.Error($"Errore in redKeyPresent per la key {key}:{Environment.NewLine}{arg}"); + Log.Error($"Errore in redKeyPresent per la currKey {key}:{Environment.NewLine}{arg}"); } return result; @@ -2453,7 +2650,7 @@ namespace MP.IOC.Data /// /// Resetta (rileggendo) i dati della State Machine multi ingressi nel formato - /// key: IdxMacchina + /// currKey: IdxMacchina /// value: IdxFamigliaIngresso /// /// @@ -2787,7 +2984,7 @@ namespace MP.IOC.Data /// /// Restitusice elenco KVP dei campi della State Machine ingressi nel formato - /// key: cState_nVal (current MICRO-STATE + "_" + new Value) + /// currKey: cState_nVal (current MICRO-STATE + "_" + new Value) /// value: iTipoEv_nState (IdxTipoEv da trasmettere + New MICRO-STATE /// /// @@ -2914,6 +3111,48 @@ namespace MP.IOC.Data return answ; } + /// + /// Effettua UPSERT elenco parametri correnti x IOB (se c'è UPDATE, se manca ADD) + /// + /// + /// + /// + public bool UpsertCurrObjItems(string idxMacchina, List innovations) + { + bool answ = false; + if (innovations != null) + { + Log.Info($"upsertCurrObjItems | idxMacchina: {idxMacchina} | {innovations.Count} innovations"); + // leggo i valori attuali... + List actValues = MachineParamList(idxMacchina); + // per ogni valore passatomi faccio insert o update rispetto elenco valori correnti in REDIS + foreach (var item in actValues) + { + // cerco nelle innovazioni SE CI SIA il valore... + 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 = redisDb.StringSet(currKey, serVal); + } + return answ; + } + /// /// Restituisce il valore SPECIFICATO per la state machine ingressi /// value: iTipoEv_nState (IdxTipoEv da trasmettere + New MICRO-STATE) @@ -3278,7 +3517,7 @@ namespace MP.IOC.Data /// /// Resetta (rileggendo) i dati della State Machine ingressi nel formato - /// key: cState_nVal (current MICRO-STATE + "_" + new Value) + /// currKey: cState_nVal (current MICRO-STATE + "_" + new Value) /// value: iTipoEv_nState (IdxTipoEv da trasmettere + New MICRO-STATE) /// ///