using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage; using Microsoft.Extensions.Configuration; using MP.MONO.Core; using MP.MONO.Core.CONF; using MP.MONO.Core.DTO; using MP.MONO.Data; using MP.MONO.Data.Controllers; using MP.MONO.Data.DbModels; using MP.MONO.Data.DTO; using MP.MONO.DECODER; using Newtonsoft.Json; using NLog; using Org.BouncyCastle.Asn1.Pkcs; using Pomelo.EntityFrameworkCore.MySql.Query.Internal; using StackExchange.Redis; using System.Collections.Specialized; using System.Diagnostics; using System.Reflection; using static MP.MONO.Core.Enums; // See https://aka.ms/new-console-template for more information // init parte config, vedere https://blog.hildenco.com/2020/05/configuration-in-net-core-console.html var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); var builder = new ConfigurationBuilder() .AddJsonFile($"appsettings.json", true, true) .AddJsonFile($"appsettings.{env}.json", true, true) .AddEnvironmentVariables(); var config = builder.Build(); // init LOG Logger Log = LogManager.GetCurrentClassLogger(); // initi va di base string lineSep = "---------------------------------------------"; Console.WriteLine(lineSep); Console.WriteLine("Start DECODER with LUA integration!"); Console.WriteLine($"vers.{Assembly.GetExecutingAssembly().GetName().Version}"); Console.WriteLine(lineSep); Console.WriteLine(""); Console.WriteLine("Running - press CTRL-C to stop"); Console.WriteLine(lineSep); Console.WriteLine(""); Log.Info(lineSep); Log.Info("Start DECODER with LUA integration!"); Log.Info($"vers.{Assembly.GetExecutingAssembly().GetName().Version}"); Log.Info(lineSep); // init DB init info x DB string dbServerAddr = config["DbConfig:Server"]; string nKey = config["DbConfig:nKey"]; string sKey = config["DbConfig:sKey"]; DbConfig.InitDb(dbServerAddr, nKey, sKey); // inizializzo il DB e creo (se necessario) l'utente DbConfig.CheckUser(nKey, sKey); // verifico se serve applicazione migrazioni DbConfig.ExecMigrationMain(); //DbConfig.ExecMigrationIdentity(); // altri parametri per check vari string connStringDB = DbConfig.CONNECTION_STRING; MpDbController dbController = null!; if (string.IsNullOrEmpty(connStringDB)) { Log.Error("ConnString empty!"); } else { dbController = new MpDbController(); Log.Info("DbController OK"); Console.WriteLine("DbController OK"); } // init info di base int MachineId = 1; int.TryParse(config.GetValue("MachineId"), out MachineId); // init oggetti REDIS + messagePipe... ConnectionMultiplexer.SetFeatureFlag("preventthreadtheft", true); ConnectionMultiplexer redisConn = ConnectionMultiplexer.Connect(config.GetConnectionString("Redis")); IDatabase? redisDb = redisConn.GetDatabase(); // preparo oggetti da configurare AlarmReportingMode alarmMode = AlarmReportingMode.ND; List? alarmsBankBitConf = new List(); List? alarmsRListConf = new List(); List? alarmsRecorded = new List(); List? machineModeConf = new List(); List? machineStatusConf = new List(); List? countStatus = new List(); MachineDTO machinePlate = new MachineDTO(); // gestione configurazioni da redis int BlinkCount = 1; bool AlarmIgnoreEmpty = true; string AlarmCleanPre = ""; string AlarmCleanPost = ""; int DbSampleInt = 60; int BlinkPeriodMSec = 1000; bool AlarmLogActive = false; int NumDay2Keep = 180; setupConf(); // datetime dell'inizio esecuzione task... x usare un semaforo di veto doppia esecuzione entro 30 sec DateTime lastExecParams = DateTime.Now.AddHours(-1); DateTime lastLogDetail = DateTime.Now.AddHours(-1); int numSendParam = 1; int numSendTools = 1; int numSendMStatus = 1; Random rand = new Random(); /* ---------------------------------------------------------------- * Setup Thread periodici (es calcolo pareto stati) * ----------------------------------------------------------------*/ MessagePipe mpStatusParetoSendPipe = new MessagePipe(redisConn, Constants.STATUS_PAR_QUEUE); MessagePipe mpStatsSendPipe = new MessagePipe(redisConn, Constants.MACH_STATS_M_QUEUE); MessagePipe mpDayDurSendPipe = new MessagePipe(redisConn, Constants.MACH_DAY_DUR_M_QUEUE); Thread threadStatus = new Thread(calcParetoStatus); threadStatus.Start(); Thread threadDbMaint = new Thread(dbMaintenance); threadDbMaint.Start(); Thread threadPersistCount = new Thread(persistCount); threadPersistCount.Start(); /* -------------------------------- * Setup Gestione Counters * --------------------------------*/ CounterManager counterMan = new CounterManager(); // inizializzo gestione messagePipe da Redis x allarmi MessagePipe counterSendPipe = new MessagePipe(redisConn, Constants.COUNT_M_QUEUE); MessagePipe counterRecvPipe = new MessagePipe(redisConn, Constants.COUNT_RAW_QUEUE); // registro gestione eventi counterRecvPipe.EA_NewMessage += CounterRecvPipe_EA_NewMessage; /* -------------------------------- * Setup Gestione MachineStatus * --------------------------------*/ MPStatusManager mpStatusMan = new MPStatusManager(); // inizializzo gestione messagePipe da Redis x allarmi MessagePipe mpStatusSendPipe = new MessagePipe(redisConn, Constants.STATUS_M_QUEUE); MessagePipe mpProdSendPipe = new MessagePipe(redisConn, Constants.PROD_M_QUEUE); MessagePipe mpStatusRecvPipe = new MessagePipe(redisConn, Constants.STATUS_RAW_QUEUE); // datetime dell'inizio esecuzione task... x usare un semaforo di veto doppia esecuzione entro 30 sec DateTime lastExecStatus = DateTime.Now.AddHours(-1); // ultimi parametri stato x check variazione.. Dictionary parListLast = new Dictionary(); // registro gestione eventi mpStatusRecvPipe.EA_NewMessage += MpStatusRecvPipe_EA_NewMessage; /* -------------------------------- * Setup Gestione Tools * --------------------------------*/ ToolsManager toolsMan = new ToolsManager(); // inizializzo gestione messagePipe da Redis x allarmi MessagePipe toolsSendPipe = new MessagePipe(redisConn, Constants.TOOLS_M_QUEUE); MessagePipe toolsRecvPipe = new MessagePipe(redisConn, Constants.TOOLS_RAW_QUEUE); // datetime dell'inizio esecuzione task... x usare un semaforo di veto doppia esecuzione entro 30 sec DateTime lastExecTools = DateTime.Now.AddHours(-1); // registro gestione eventi toolsRecvPipe.EA_NewMessage += ToolsRecvPipe_EA_NewMessage; /* -------------------------------- * Setup Gestione Parametri * --------------------------------*/ ParamsManager paramMan = new ParamsManager(); // inizializzo gestione messagePipe da Redis x allarmi MessagePipe paramsSendPipe = new MessagePipe(redisConn, Constants.PARAMS_M_QUEUE); MessagePipe paramsRecvPipe = new MessagePipe(redisConn, Constants.PARAMS_RAW_QUEUE); // registro gestione eventi paramsRecvPipe.EA_NewMessage += ParamsValPipe_EA_NewMessage; /* -------------------------------- * Setup Gestione ALLARMI * --------------------------------*/ // init classe gestione allarmi con LUA AlarmsManager alarmsMan = new AlarmsManager(); AlarmsBlinkManager alarmsBlinkMan = new AlarmsBlinkManager(); // inizializzo gestione messagePipe da Redis x allarmi MessagePipe alarmsSendPipe = new MessagePipe(redisConn, Constants.ALARM_M_QUEUE); MessagePipe alarmsRecvPipe = new MessagePipe(redisConn, Constants.ALARM_RAW_QUEUE); // datetime dell'inizio esecuzione task... x usare un semaforo di veto doppia esecuzione entro 30 sec DateTime lastExecAlarms = DateTime.Now.AddHours(-1); // registro gestione eventi alarmsRecvPipe.EA_NewMessage += AlarmsValPipe_EA_NewMessage; /* -------------------------------- * Funzioni / metodi accessori * --------------------------------*/ AlarmListModel? getAlarmModel(string alarmFullCode) { // cerco l'allarme nell'elenco degli allarmi dal DB AlarmListModel? foundAlarm = null; if (alarmsRecorded != null) { foundAlarm = alarmsRecorded .Where(x => x.FullValue == alarmFullCode) .FirstOrDefault(); } // se non lo trovo --> chiamo procedura che verifica su DB, eventualmente inserisce, restituisce nuovo elenco if (foundAlarm == null) { // salvo sul DB foundAlarm = dbController.AlarmListInsert(alarmFullCode); // ora rileggo ed aggiorno redis e cerco di nuovo record... alarmsRecorded = dbController.AlarmListGetAll(); if (redisDb != null) { redisDb.StringSet(Constants.ALARMS_SETT_RLIST_KEY, JsonConvert.SerializeObject(alarmsRecorded)); } } return foundAlarm; } /// /// Verifica un allarme se sia x caso cessato dopo il periodo di pausa e quindi /// async Task checkCeasedAlarm(string alarmFullCode) { bool answ = false; // verifico NON SIA già in elenco check... List listClose = AlarmsBlinkManager.alarmsPendingClose; if (!listClose.Contains(alarmFullCode)) { Log.Debug($"CCA 01 | Verifica scadenza allarmi | cod: {alarmFullCode}"); listClose.Add(alarmFullCode); AlarmsBlinkManager.alarmsPendingClose = listClose; // attendo periodo pausa... await Task.Delay(BlinkPeriodMSec); // cerco allarme copiando il set degli ultimi ricevuti List alarmListRecev = AlarmsBlinkManager.alarmsLastRec; if (alarmListRecev != null) { // ...SE mancasse if (!alarmListRecev.Contains(alarmFullCode)) { // recupero allarme... var foundAlarm = alarmsRecorded .Where(x => x.FullValue == alarmFullCode) .FirstOrDefault(); int alId = foundAlarm != null ? foundAlarm.AlarmId : 0; Log.Debug($"CCA 02 | {alId} | Allarme ancora mancante | cod: {alarmFullCode}"); // --> chiudo if (foundAlarm != null) { // salvo sul DB _ = dbController.AlarmRecCloseActive(foundAlarm.AlarmId); Log.Info($"Chiusura allarme | Id: {foundAlarm.AlarmId}"); } // verifico libero canale di rimozione int numTry = 0; DateTime adesso = DateTime.Now; Random rand = new Random(); while (AlarmsBlinkManager.isRemoving) { await Task.Delay(rand.Next(10, 30)); if (numTry % 2 == 0) { TimeSpan ts = DateTime.Now.Subtract(adesso); Log.Info($"Wait for remove Alarm {alId} | total wait {ts.TotalMilliseconds:N3}ms"); } numTry++; } AlarmsBlinkManager.isRemoving = true; // --> rimuovo da elenco attivi! var alarmListSent = new List(AlarmsBlinkManager.alarmsActAck); int numPre = alarmListSent.Count(); alarmListSent.Remove(alarmFullCode); AlarmsBlinkManager.alarmsActAck = alarmListSent; int numPost = alarmListSent.Count(); // invio attivi... string serAlarms = JsonConvert.SerializeObject(alarmListSent); // invio sulla message pipeline corretta TUTTI gli allarmi serializzati alarmsSendPipe.saveAndSendMessage(Constants.ALARM_CURR_KEY, serAlarms); Log.Debug($"CCA 03 | {alId} | Salvataggio Sent alarms | num: {numPre} --> {numPost}"); AlarmsBlinkManager.isRemoving = false; } answ = true; } // rimuovo da pending close listClose = new List(AlarmsBlinkManager.alarmsPendingClose); listClose.Remove(alarmFullCode); AlarmsBlinkManager.alarmsPendingClose = listClose; } return answ; } #if false /// /// Verifica x TUTTI gli allarmi SE siano cessati topo 4 * periodo di controllo... zero ora e zero dopo... /// async Task checkCloseAllAlarm() { bool answ = false; Log.Debug($"Verifica checkCloseAllAlarm"); // cerco allarme da valore REDIS ultimi ricevuti List alarmSent = new List(AlarmsBlinkManager.alarmsActAck); if (alarmSent != null) { bool isZero = alarmSent.Count() == 0; // solo SE ora sono zero... if (isZero) { // attendo periodo pausa... await Task.Delay(BlinkPeriodMSec * 2); // rileggo stato alarmSent = new List(AlarmsBlinkManager.alarmsActAck); isZero = alarmSent.Count() == 0; // ...SE fossero sempre zero if (isZero) { Log.Debug($"Verifica zero allarmi confermata, chiamo chiusura"); // chiudo sul DB _ = dbController.AlarmRecCloseStarving(); // invio resettati... string serAlarms = JsonConvert.SerializeObject(alarmSent); // invio sulla message pipeline corretta TUTTI gli allarmi serializzati alarmsSendPipe.saveAndSendMessage(Constants.ALARM_CURR_KEY, serAlarms); } } answ = true; } return answ; } #endif /// /// Recupero da redis le conf (es modi,stati,allarmi) /// void setupConf() { // imposto blinkCount da conf... string rConf = config["OptPar:BlinkCount"]; int.TryParse(rConf, out BlinkCount); // imposto intervallo salvataggio valori su db rConf = config["OptPat:DbSampleInt"]; int.TryParse(rConf, out DbSampleInt); // imposto parametri x Alarms rConf = config["OptPar:AlarmIgnoreEmpty"]; bool.TryParse(rConf, out AlarmIgnoreEmpty); AlarmCleanPre = config["OptPar:AlarmCleanPre"]; AlarmCleanPost = config["OptPar:AlarmCleanPost"]; // gestione blink allarmi rConf = config["OptPar:BlinkPeriodMSec"]; int.TryParse(rConf, out BlinkPeriodMSec); // recupero valori e deserializzo string rawData = ""; string redisConf = config.GetConnectionString("Redis"); string confPath = Path.Combine(Directory.GetCurrentDirectory(), "conf"); ConfigManager confMan = new ConfigManager(redisConf, confPath); // salvo conf allarmi in redis rConf = config["OptPar:AlarmLogActive"]; bool.TryParse(rConf, out AlarmLogActive); confMan.AlarmLogActive = AlarmLogActive; // machine status (display) conf rawData = redisDb.StringGet(Constants.MACHINE_CONF_PLATE); if (!string.IsNullOrEmpty(rawData)) { var currData = JsonConvert.DeserializeObject(rawData); machinePlate = currData != null ? currData : new MachineDTO(); } // machine status (display) conf rawData = redisDb.StringGet(Constants.DISPLSTATUS_CONF_KEY); if (!string.IsNullOrEmpty(rawData)) { machineStatusConf = JsonConvert.DeserializeObject>(rawData); } // machine mode rawData = redisDb.StringGet(Constants.MODE_CONF_KEY); if (!string.IsNullOrEmpty(rawData)) { machineModeConf = JsonConvert.DeserializeObject>(rawData); } // allarmi.. rawData = redisDb.StringGet(Constants.ALARMS_MODE_KEY); alarmMode = JsonConvert.DeserializeObject(rawData); // elenco allarmi già registrati (cache DB) rawData = redisDb.StringGet(Constants.ALARMS_SETT_RLIST_KEY); if (!string.IsNullOrEmpty(rawData)) { alarmsRecorded = JsonConvert.DeserializeObject>(rawData); } // ora la conf specifica rawData = redisDb.StringGet(Constants.ALARMS_CONF_KEY); if (!string.IsNullOrEmpty(rawData)) { if (alarmMode == AlarmReportingMode.RawList) { alarmsRListConf = JsonConvert.DeserializeObject>(rawData); } else if (alarmMode == AlarmReportingMode.RawListBlink) { alarmsRListConf = JsonConvert.DeserializeObject>(rawData); } else { alarmsBankBitConf = JsonConvert.DeserializeObject>(rawData); } } } /// /// Test di uguaglianza tra 2 dizionari... /// bool testEqual(Dictionary dict1, Dictionary dict2) { // se nulli inizializzo... if (dict1 == null) { dict1 = new Dictionary(); } if (dict2 == null) { dict2 = new Dictionary(); } // in primis testo num elementi int num1 = dict1.Count; int num2 = dict2.Count; if (num1 != num2) { return false; } else { // comparazione sequenze ordinate... return dict1.OrderBy(x => x.Key).SequenceEqual(dict2.OrderBy(x => x.Key)); } } /// /// Ultimo Plate machine salvato /// MachineDTO getLastMachinePlate() { MachineDTO? currMPlate = new MachineDTO(); string rawData = redisDb.StringGet(Constants.STATUS_CURR_KEY); if (string.IsNullOrEmpty(rawData)) { rawData = redisDb.StringGet(Constants.MACHINE_CONF_PLATE); } if (!string.IsNullOrEmpty(rawData)) { currMPlate = JsonConvert.DeserializeObject(rawData); } if (currMPlate == null) { currMPlate = new MachineDTO(); } return currMPlate; } /// /// Ultimo ProdDTO salvato /// ProductionDTO getCurrProdData() { ProductionDTO? currProdData = new ProductionDTO(); string rawData = redisDb.StringGet(Constants.PROD_CURR_KEY); if (!string.IsNullOrEmpty(rawData)) { currProdData = JsonConvert.DeserializeObject(rawData); } if (currProdData == null) { currProdData = new ProductionDTO(); } return currProdData; } /// /// Stato precedentemente salvato su REDIS x calcolo variazioni /// Dictionary getLastStatusVal() { Dictionary answ = new Dictionary(); if (redisDb != null) { string rawData = redisDb.StringGet(Constants.STATUS_LAST_KEY); if (!string.IsNullOrEmpty(rawData)) { try { var rawDict = JsonConvert.DeserializeObject>(rawData); answ = rawDict != null ? rawDict : new Dictionary(); } catch { } } } return answ; } /// /// Salva su redis il set di valori ricevuti /// bool setLastStatusVal(Dictionary dataList) { bool answ = false; if (redisDb != null) { string rawData = JsonConvert.SerializeObject(dataList); if (!string.IsNullOrEmpty(rawData)) { answ = redisDb.StringSet(Constants.STATUS_LAST_KEY, rawData); } } return answ; } void calcParetoStatus() { // ricalcolo ogni 30" +/- 2sec int minPeriod = 28000; int maxPeriod = 32000; do { // periodo riferimento sempre ricalcolato... DateTime inizio = DateTime.Today; DateTime fine = DateTime.Today.AddDays(1); // recupero pareto da DB var newStatus = dbController.ParetoStatusMach(MachineId, inizio, fine); // riordino e aggiungo i dati di status... var statusAllDTO = machineStatusConf .Select(x => new ParetoStatusDTO() { CodStatus = $"MS_{x.MStatusID:000}", CssClass = x.Css, Description = x.Description, Group = x.Group, Prior = x.Priority }) .ToList(); // ora ciclo tra i valori trovati... foreach (var item in newStatus) { var recStatus = statusAllDTO.Where(x => x.CodStatus == item.CodStatus).FirstOrDefault(); if (recStatus != null) { recStatus.TotDuration = item.TotDuration; } } // salvo i dati insieme alla traduzione degli stati relativa... var statusActDTO = statusAllDTO .Where(x => x.TotDuration > 0) .OrderByDescending(x => x.TotDuration).ToList(); // serializzo e salvo i valori x pareto string rawData = JsonConvert.SerializeObject(statusActDTO); mpStatusParetoSendPipe.saveAndSendMessage(Constants.STATUS_PARETO_KEY, rawData); // calcolo le daily duration e invio List currMDayDurDTO = getMachDayDurDTO(statusAllDTO); if (currMDayDurDTO.Count > 0) { // serializzo e salvo rawData = JsonConvert.SerializeObject(currMDayDurDTO); mpDayDurSendPipe.saveAndSendMessage(Constants.MACH_DAY_DUR_CURR_KEY, rawData); } // calcolo Current:MachStats (pareto status macchina...) List currMStatDTO = getMachStatDTO(statusActDTO); if (currMStatDTO.Count > 0) { // serializzo e salvo rawData = JsonConvert.SerializeObject(currMStatDTO); mpStatsSendPipe.saveAndSendMessage(Constants.MACH_STATS_CURR_KEY, rawData); } // attesa random di circa 1 minuti x calcolare... Thread.Sleep(rand.Next(minPeriod, maxPeriod)); } while (true); } void dbMaintenance() { // ricalcolo ogni 120 min CIRCA +/- 2% int msecOra = 1000 * 60 * 120; int minPeriod = msecOra * 98 / 100; int maxPeriod = msecOra * 102 / 100; // leggo conf x giorni da tenere... int.TryParse(config.GetValue("OptPar:NumDay2Keep"), out NumDay2Keep); do { // effettuo su DB chiamata stored x pulizia dati vecchi var cleanupResult = dbController.RemoveOldData(NumDay2Keep); // loggo esito cleanup eseguito foreach (var item in cleanupResult) { Log.Info($"DB Cleanup: {item.CodTask} | {item.Result}"); } // attesa random da periodo... Thread.Sleep(rand.Next(minPeriod, maxPeriod)); } while (true); } void persistCount() { // eseguo ogni 1 min CIRCA +/- 2% int msecOra = 1000 * 60 * 1; int minPeriod = msecOra * 98 / 100; int maxPeriod = msecOra * 102 / 100; // per prima cosa leggo i counters attuali... countStatus = dbController.CountersGetAll(); do { // salvo su DB valori counters _ = dbController.CountersUpsertMany(countStatus); Log.Debug($"Counters: {countStatus.Count} saved"); // attesa random da periodo... Thread.Sleep(rand.Next(minPeriod, maxPeriod)); } while (true); } List getMachDayDurDTO(List currStatus) { List dayDurList = new List(); // calcolo in minuti assoluti int i = 0; dayDurList = currStatus .Select(x => new DisplayDataDTO() { Order = i++, Type = "TIME", Title = $"{x.Group} | {x.Description}", ValueNum = x.TotDuration, Value = $"{x.TotDuration:N2}", DisplFormat = "N0", MinVal = 0, MaxVal = 1, IsNumeric = true, EnablePlot = true, ShowBar = true, HLShow = x.TotDuration > 5, CssClass = x.CssClass }) .ToList(); return dayDurList; } List getMachStatDTO(List currStatus) { List machStatList = new List(); // calcolo come % tempo RUN sul totale... float globDur = currStatus.Sum(x => x.TotDuration); int i = 0; machStatList = currStatus .Select(x => new DisplayDataDTO() { Order = i++, Type = "PERC", Title = $"{x.Group} | {x.Description}", ValueNum = x.TotDuration / globDur, Value = $"{x.TotDuration / globDur:P1}", DisplFormat = "P1", MinVal = 0, MaxVal = 1, IsNumeric = true, EnablePlot = true, ShowBar = true, HLShow = x.TotDuration > (globDur * 0.01), CssClass = x.CssClass }) .ToList(); return machStatList; } /// /// Gestione evento ricezione messaggi allarmi secondo tipologia attiva... /// async void AlarmsValPipe_EA_NewMessage(object? sender, EventArgs e) { PubSubEventArgs currArgs = (PubSubEventArgs)e; // conversione on-the-fly List --> allarmi if (!string.IsNullOrEmpty(currArgs.newMessage)) { DateTime adesso = DateTime.Now; // variabili accessorie x persistenza su DB List alarmRecList = new List(); List alarmLogList = new List(); Random rand = new Random(); if (alarmMode == AlarmReportingMode.RawList) { try { // decodifico allarmi ricevuti var alarmListRaw = JsonConvert.DeserializeObject>(currArgs.newMessage); // effettuo selezione dei SOLI allarmi distinct (in caso di cambio posizione...) List alarmList = new List(); if (alarmListRaw != null && alarmListRaw.Count > 0) { alarmList = alarmListRaw .GroupBy(x => x) .Select(grp => grp.First()) .ToList(); } // gestione allarmi muted List? mutedAlarmsList = new List(); string rawData = redisDb.StringGet(Constants.ALARMS_SETT_RLIST_KEY); if (!string.IsNullOrEmpty(rawData)) { mutedAlarmsList = JsonConvert.DeserializeObject>(rawData); } if (mutedAlarmsList == null) { mutedAlarmsList = dbController.AlarmListGetAll(); } // rileggo da REDIS la conf attuale allarmi già attivi da area ALARM_CURR_KEY List lastAlarms = new List(); rawData = redisDb.StringGet(Constants.ALARM_CURR_KEY); // se trovati uso questi... if (!string.IsNullOrEmpty(rawData)) { var rawLastAlarms = JsonConvert.DeserializeObject>(rawData); lastAlarms = rawLastAlarms != null ? rawLastAlarms : new List(); } // recupero DICT dei blink allarmi (per filtrare fronte discesa in caso di allarmi // che cessano e tornano attivi) Dictionary blinkStatus = new Dictionary(); rawData = redisDb.StringGet(Constants.ALARM_BLINK_CURR_KEY); // se trovati uso questi... if (!string.IsNullOrEmpty(rawData)) { var rawBlink = JsonConvert.DeserializeObject>(rawData); blinkStatus = rawBlink != null ? rawBlink : new Dictionary(); } // ciclo x ogni allarme ricevuto x vedere se sia INIZIATO if (alarmList != null) { // ciclo x ogni allarme attivo x verificare se si sia CHIUSO foreach (var newAlarm in alarmList) { var currNewAlarm = newAlarm; // se ho valori trim pre/post li applica... if (!string.IsNullOrEmpty(AlarmCleanPre)) { currNewAlarm = currNewAlarm.Replace(AlarmCleanPre, ""); } if (!string.IsNullOrEmpty(AlarmCleanPost)) { currNewAlarm = currNewAlarm.Replace(AlarmCleanPost, ""); } // se AlarmIgnoreEmpty=true + vuoto --> salto if (AlarmIgnoreEmpty && string.IsNullOrWhiteSpace(currNewAlarm)) { alarmList.Remove(currNewAlarm); alarmList.Remove(newAlarm); } else { // in primis... se è muted --> lo escludo... bool isMuted = false; var maybeMuted = mutedAlarmsList.Where(x => x.FullValue == currNewAlarm).FirstOrDefault(); if (maybeMuted != null) { isMuted = maybeMuted.Muted; } // cerco l'allarme nell'elenco degli allarmi dal DB AlarmListModel? foundAlarm = getAlarmModel(currNewAlarm); #if false var foundAlarm = alarmsRecorded .Where(x => x.FullValue == currNewAlarm) .FirstOrDefault(); // se non lo trovo --> chiamo procedura che verifica su DB, // eventualmente inserisce, restituisce nuovo elenco if (foundAlarm == null) { // salvo sul DB foundAlarm = dbController.AlarmListInsert(currNewAlarm); // ora rileggo ed aggiorno redis e cerco di nuovo record... alarmsRecorded = dbController.AlarmListGetAll(); redisDb.StringSet(Constants.ALARMS_SETT_RLIST_KEY, JsonConvert.SerializeObject(alarmsRecorded)); } #endif if (!isMuted) { if (!lastAlarms.Contains(currNewAlarm)) { if (foundAlarm != null) { // segno come iniziato... AlarmRecModel newAlarmRec = new AlarmRecModel() { MachineId = MachineId, DtStart = adesso, DtEnd = adesso.AddMinutes(-1), AlarmId = foundAlarm.AlarmId }; alarmRecList.Add(newAlarmRec); } // gestione blink allarmi if (blinkStatus.ContainsKey(currNewAlarm)) { blinkStatus[currNewAlarm] = BlinkCount; Log.Info($"Alarm: {currNewAlarm} | BlinkCount: {BlinkCount}"); } else { blinkStatus.Add(currNewAlarm, BlinkCount); Log.Info($"NEW Alarm: {currNewAlarm} | BlinkCount: {BlinkCount}"); } } } else { // rimuovo da alarmList perché muted... alarmList.Remove(currNewAlarm); alarmList.Remove(newAlarm); } } } } // recupero elenco valori blink List chiavi = blinkStatus.Select(x => x.Key).ToList(); // decremento i blink foreach (var item in chiavi) { if (blinkStatus[item] > 0) { blinkStatus[item]--; } } // salvo blink status counter... redisDb.StringSet(Constants.ALARM_BLINK_CURR_KEY, JsonConvert.SerializeObject(blinkStatus)); // processo elenco allarmi già presenti if (lastAlarms != null) { // ciclo x ogni allarme attivo x verificare se si sia CHIUSO foreach (var oldData in lastAlarms) { bool trovato = false; if (alarmList != null) { trovato = alarmList.Contains(oldData); } bool isBlinking = false; if (blinkStatus.ContainsKey(oldData)) { isBlinking = blinkStatus[oldData] > 0; } if (!trovato && !isBlinking) { // se non lo trovo var foundAlarm = alarmsRecorded .Where(x => x.FullValue == oldData) .FirstOrDefault(); if (foundAlarm != null) { // salvo sul DB _ = dbController.AlarmRecCloseActive(foundAlarm.AlarmId); } } } } // verifico se devo scrivere if (lastAlarms != null && alarmList != null) { bool wALog = lastAlarms.Count != alarmList.Count; if (!wALog) { // verifico se ci siano differenze... wALog = alarmList.Union(lastAlarms).Count() != lastAlarms.Count; } // salvo se c'è stata variazione... if (wALog) { // preparo dati x alarmLog AlarmLogModel newAlarmLog = new AlarmLogModel() { MachineId = MachineId, DtRif = adesso, MemAddress = "ND", Index = 0, Status = (uint)alarmList.Count, ValDecoded = alarmList.Count > 0 ? string.Join(", ", alarmList) : "All OK" }; alarmLogList.Add(newAlarmLog); } } // serializzo l'elenco allarmi... string serAlarms = "[]"; serAlarms = JsonConvert.SerializeObject(alarmList); // invio sulla message pipeline corretta TUTTI gli allarmi serializzati alarmsSendPipe.saveAndSendMessage(Constants.ALARM_CURR_KEY, serAlarms); // salvo sul DB if (alarmRecList.Count > 0) { foreach (var item in alarmRecList) { _ = dbController.AlarmRecInsert(item); await Task.Delay(10); } #if false _ = dbController.AlarmRecInsertMany(alarmRecList); #endif } if (alarmLogList.Count > 0) { _ = dbController.AlarmLogInsertMany(alarmLogList).Result; } // salvo nuovo elenco allarmi if (alarmList != null) { lastAlarms = alarmList; } } catch { } } else if (alarmMode == AlarmReportingMode.RawListBlink) { int numTry = 0; int myTicket = 0; Stopwatch stopWatch = new Stopwatch(); try { myTicket = AlarmsBlinkManager.getTicket(); // attendo che NON ci siano altri processi in coda... while (!AlarmsBlinkManager.checklTicket(myTicket)) { await Task.Delay(rand.Next(10, 30)); if (numTry % 2 == 0) { TimeSpan ts = DateTime.Now.Subtract(adesso); Log.Info($"Ticket {myTicket} | total wait {ts.TotalMilliseconds:N3}ms"); } numTry++; } stopWatch.Start(); Log.Debug($"---------- INIZIO ticket {myTicket} ---------- "); // decodifico allarmi ricevuti var alarmListRaw = JsonConvert.DeserializeObject>(currArgs.newMessage); var alarmListRcvd = alarmListRaw != null ? alarmListRaw : new List(); // bonifico allarmi var alarmiListClean = AlarmsBlinkManager.cleanAlarms(AlarmCleanPre, AlarmCleanPost, alarmListRcvd); // allarmi correnti List alarmListCurrent = AlarmsBlinkManager.alarmsActAck; // init classi helper List addedAlarms = new List(); List ceasedAlarms = new List(); // chiamo procedura in blocco... bool fatto = AlarmsBlinkManager.processData(alarmiListClean, ref alarmListCurrent, ref addedAlarms, ref ceasedAlarms); // salvo ed invio subito i current... string serAlarms = JsonConvert.SerializeObject(alarmListCurrent); alarmsSendPipe.saveAndSendMessage(Constants.ALARM_CURR_KEY, serAlarms); // solo se abilitata gestione AlarmLog... if (AlarmLogActive) { // se cambiato --> ultimo ricevuto != current... string lastLogList = AlarmsBlinkManager.alarmsLogSaved != null ? AlarmsBlinkManager.alarmsLogSaved : ""; // calcolo nuova stringa... string newLogString = string.Join(", ", alarmiListClean); bool logChanged = !lastLogList.Equals(newLogString); AlarmsBlinkManager.alarmsLogSaved = newLogString; if (logChanged) { // preparo dati x alarmLog AlarmLogModel newAlarmLog = new AlarmLogModel() { MachineId = MachineId, DtRif = adesso, MemAddress = "ND", Index = 0, Status = (uint)alarmiListClean.Count, ValDecoded = alarmiListClean.Count > 0 ? string.Join(", ", alarmiListClean) : "All OK" }; alarmLogList.Add(newAlarmLog); _ = dbController.AlarmLogInsertMany(alarmLogList).Result; Log.Debug("Inserito Record AlarmLogModel"); } } if (addedAlarms.Count > 0) { // preparo lista NEW x inserimento su DB foreach (var item in addedAlarms) { // cerco l'allarme nell'elenco degli allarmi dal DB AlarmListModel? foundAlarm = getAlarmModel(item); if (foundAlarm != null) { // segno come iniziato... AlarmRecModel newAlarmRec = new AlarmRecModel() { MachineId = MachineId, DtStart = adesso, DtEnd = adesso.AddMinutes(-1), AlarmId = foundAlarm.AlarmId }; alarmRecList.Add(newAlarmRec); } } // salvo sul DB #if false foreach (var item in alarmRecList) { _ = dbController.AlarmRecInsert(item); } #endif _ = dbController.AlarmRecInsertMany(alarmRecList); Log.Debug($"Inseriti {alarmRecList.Count} Records AlarmRecModel"); } if (ceasedAlarms.Count > 0) { // chiamo task x verifica chiusura ritardata foreach (var item in ceasedAlarms) { await Task.Run(() => { checkCeasedAlarm(item); }); } // Loggo quanto fatto Log.Debug($"Chiamato controllo differito per {ceasedAlarms.Count} allarmi cessati"); } } catch (Exception exc) { Log.Error($"Eccezione:{Environment.NewLine}{exc}"); } AlarmsBlinkManager.isProcessing = false; stopWatch.Stop(); Log.Debug($"---------- Fine ticket {myTicket} | {stopWatch.ElapsedMilliseconds} ms ---------- "); } else { try { // verifico codici allarmi ricevuti var alarmList = JsonConvert.DeserializeObject>(currArgs.newMessage); if (alarmList != null) { // variabili accessorie List activeAlarmList = new List(); alarmLogList = new List(); // rileggo da REDIS la conf attuale allarmi da area ALARMS_SETT_KEY, altrimenti // uso quella letta inizialmente da conf base List? currAlarmsBankConf = new List(); string rawData = redisDb.StringGet(Constants.ALARMS_SETT_BBIT_KEY); // se trovati uso questi... if (!string.IsNullOrEmpty(rawData)) { currAlarmsBankConf = JsonConvert.DeserializeObject>(rawData); } // altrimenti uso quelli di default inizialmente letti else { currAlarmsBankConf = alarmsBankBitConf; } if (currAlarmsBankConf != null) { // ciclo x ogni bank di allarmi configurato foreach (var alarmData in currAlarmsBankConf) { // recupero valore allarme var bankVal = alarmList.FirstOrDefault(x => x.Key == alarmData.memAddr); uint actVal = !string.IsNullOrEmpty(bankVal.Key) ? bankVal.Value : 0; // valutare se dividere in 2 da 16... FIXME TODO var bankAlarmList = AlarmsManager.processData(alarmData.memAddr, alarmData.messages, alarmData.silenceMask[0], alarmData.disableMask[0], alarmData.alarmsState[0], ref actVal); // salvo nuovo valore in oggetto... if (alarmData.alarmsState[0] != actVal) { alarmData.alarmsState[0] = actVal; // aggiungo... activeAlarmList.AddRange(bankAlarmList); AlarmLogModel newAlarmLog = new AlarmLogModel() { MachineId = MachineId, DtRif = adesso, MemAddress = bankVal.Key, Index = alarmData != null ? alarmData.index : 0, Status = alarmData != null ? (uint)bankVal.Value & alarmData.silenceMask[0] & alarmData.disableMask[0] : (uint)bankVal.Value, ValDecoded = bankAlarmList.Count > 0 ? string.Join(", ", bankAlarmList) : "All OK" }; alarmLogList.Add(newAlarmLog); } } } // salvo status aggiornato allarme... redisDb.StringSet(Constants.ALARMS_SETT_BBIT_KEY, JsonConvert.SerializeObject(currAlarmsBankConf)); // serializzo l'elenco allarmi... string serAlarms = "[]"; serAlarms = JsonConvert.SerializeObject(activeAlarmList); // invio sulla message pipeline corretta TUTTI gli allarmi serializzati alarmsSendPipe.saveAndSendMessage(Constants.ALARM_CURR_KEY, serAlarms); // verifico NON ci sia veto di scrittura... DbSampleInt (tipicamente 60 sec...) if (adesso.Subtract(lastExecAlarms).TotalSeconds > DbSampleInt) { lastExecAlarms = adesso; if (dbController != null) { // salvo sul DB _ = dbController.AlarmLogInsertMany(alarmLogList).Result; } } } } catch (Exception exc) { Log.Error($"Eccezione in AlarmsValPipe_EA_NewMessage, mode {alarmMode}:{Environment.NewLine}{exc}"); } } } } /// /// Gestione evento ricezione messaggi tools /// void ToolsRecvPipe_EA_NewMessage(object? sender, EventArgs e) { PubSubEventArgs currArgs = (PubSubEventArgs)e; // conversione on-the-fly Dictionary --> parametri valorizzati (tra quelli configurati) if (!string.IsNullOrEmpty(currArgs.newMessage)) { DateTime adesso = DateTime.Now; List? redisToolsConf = new List(); try { // verifico codici ricevuti var toolsList = JsonConvert.DeserializeObject>(currArgs.newMessage); if (toolsList != null) { // recupero elenco parametri salvati in redis... string rawData = redisDb.StringGet(Constants.TOOLS_CONF_KEY); if (!string.IsNullOrEmpty(rawData)) { redisToolsConf = JsonConvert.DeserializeObject>(rawData); // se ho i parametri... if (redisToolsConf != null) { // ciclo x ogni valore ricevuto da message service... foreach (var item in toolsList) { // cerco il parametro corrispondente... var currToolVal = redisToolsConf.FirstOrDefault(x => x.Title == item.Key); if (currToolVal != null) { currToolVal.ValueNum = item.Value; currToolVal.Value = $"{item.Value.ToString(currToolVal.DisplFormat)}"; } } // verifico NON ci sia veto di scrittura... DbSampleInt (60 sec...) if (adesso.Subtract(lastExecTools).TotalSeconds > DbSampleInt) { lastExecTools = adesso; if (dbController != null) { // verifico x ogni parametro se sia completato il periodo di campionamento... var val2save = toolsMan.processData(toolsList, redisToolsConf); if (val2save.Count > 0) { // salvo sul DB _ = dbController.DataLogInsertMany(val2save).Result; Log.Info($"Tools | DB | {val2save.Count} rec inserted"); } } } } } // invio sulla message pipeline corretta TUTTI i parametri aggiornati serializzati string updRawVal = JsonConvert.SerializeObject(redisToolsConf); toolsSendPipe.saveAndSendMessage(Constants.TOOLS_CURR_KEY, updRawVal); if (redisToolsConf != null) { if (adesso.Subtract(lastLogDetail).TotalSeconds > 5 || numSendTools > 10) { Log.Info($"TOOLS | {redisToolsConf.Count} data transmitted x {numSendTools}"); lastLogDetail = adesso; numSendTools = 0; } else { numSendTools++; } } } } catch (Exception exc) { Log.Error($"Eccezione in ToolsRecvPipe_EA_NewMessage:{Environment.NewLine}{exc}{Environment.NewLine}{currArgs.newMessage}"); } } } /// /// Gestione evento ricezione messaggi parametri /// void ParamsValPipe_EA_NewMessage(object? sender, EventArgs e) { PubSubEventArgs currArgs = (PubSubEventArgs)e; // conversione on-the-fly Dictionary --> parametri valorizzati (tra quelli configurati) if (!string.IsNullOrEmpty(currArgs.newMessage)) { DateTime adesso = DateTime.Now; List? redisParamConf = new List(); try { // verifico codici ricevuti var paramsList = JsonConvert.DeserializeObject>(currArgs.newMessage); if (paramsList != null) { // recupero elenco parametri salvati in redis... string rawData = redisDb.StringGet(Constants.PARAMS_CONF_KEY); if (!string.IsNullOrEmpty(rawData)) { redisParamConf = JsonConvert.DeserializeObject>(rawData); // se ho i parametri... if (redisParamConf != null) { // ciclo x ogni valore ricevuto da message service... foreach (var item in paramsList) { // cerco il parametro corrispondente... var currParam = redisParamConf.FirstOrDefault(x => x.Title == item.Key); if (currParam != null) { currParam.ValueNum = item.Value; currParam.Value = $"{item.Value.ToString(currParam.DisplFormat)}"; } } // verifico NON ci sia veto di scrittura... DbSampleInt (60 sec...) if (adesso.Subtract(lastExecParams).TotalSeconds > DbSampleInt) { lastExecParams = adesso; if (dbController != null) { // verifico x ogni parametro se sia completato il periodo di campionamento... var params2save = paramMan.processData(paramsList, redisParamConf); if (params2save.Count > 0) { // salvo sul DB _ = dbController.DataLogInsertMany(params2save).Result; Log.Info($"Parametri | DB | {params2save.Count} rec inserted"); } } } } } // invio sulla message pipeline corretta TUTTI i parametri aggiornati serializzati string updRawVal = JsonConvert.SerializeObject(redisParamConf); paramsSendPipe.saveAndSendMessage(Constants.PARAMS_CURR_KEY, updRawVal); if (redisParamConf != null) { if (adesso.Subtract(lastLogDetail).TotalSeconds > 5 || numSendParam > 30) { Log.Info($"Parametri | {redisParamConf.Count} params trasmessi x {numSendParam}"); lastLogDetail = adesso; numSendParam = 0; } else { numSendParam++; } } } } catch (Exception exc) { Log.Error($"Eccezione in ParamsValPipe_EA_NewMessage:{Environment.NewLine}{exc}{Environment.NewLine}{currArgs.newMessage}"); } } } /// /// Gestione evento ricezione messaggi status /// void MpStatusRecvPipe_EA_NewMessage(object? sender, EventArgs e) { PubSubEventArgs currArgs = (PubSubEventArgs)e; // conversione on-the-fly Dictionary --> parametri valorizzati (tra quelli configurati) if (!string.IsNullOrEmpty(currArgs.newMessage)) { DateTime adesso = DateTime.Now; try { // verifico codici ricevuti var dataList = JsonConvert.DeserializeObject>(currArgs.newMessage); if (dataList != null) { /************************************************************************ * * * * * * * * * * * *************************************************************************/ // recupero elenco parametri salvati in redis... Dictionary parList = new Dictionary(); // recupero status precedente... MachineDTO lastMachinePlate = getLastMachinePlate(); MachineDTO nextMachinePlate = new MachineDTO(); int machStatus = 0; int procStatus = 0; int procMode = 0; string currKey = ""; // ora processo il messaggio ricevuto x stati e modi vari currKey = "MACHINE STATUS"; if (dataList.ContainsKey(currKey)) { int.TryParse(dataList[currKey], out machStatus); parList.Add(currKey, machStatus); } currKey = "PROCESS STATUS"; if (dataList.ContainsKey(currKey)) { int.TryParse(dataList[currKey], out procStatus); parList.Add(currKey, procStatus); } currKey = "PROCESS MODE"; if (dataList.ContainsKey(currKey)) { int.TryParse(dataList[currKey], out procMode); parList.Add(currKey, procMode); } // effettuo verifica valori production e relativo invio checkProduction(dataList); // fix me todo inserire gestione program name!!! Stopwatch sw = new Stopwatch(); sw.Start(); // calcolo con LUA... sw.Restart(); nextMachinePlate = mpStatusMan.processData(parList, lastMachinePlate); sw.Stop(); if (adesso.Subtract(lastLogDetail).TotalSeconds > 5 || numSendMStatus > 10) { Log.Info($"Calcolo LUA | {sw.ElapsedTicks} ticks | {sw.ElapsedMilliseconds} ms | {JsonConvert.SerializeObject(nextMachinePlate)} | x {numSendMStatus}"); lastLogDetail = adesso; numSendMStatus = 0; } else { numSendMStatus++; } #if false else { // calcolo status if (machStatus == 0) { nextMachinePlate.StatusId = 7; } else { switch (procMode) { case 1: nextMachinePlate.StatusId = 8; break; case 2: nextMachinePlate.StatusId = 2; break; case 3: nextMachinePlate.StatusId = 9; break; case 4: nextMachinePlate.StatusId = 5; break; case 5: nextMachinePlate.StatusId = 10; break; case 6: nextMachinePlate.StatusId = 11; break; case 7: nextMachinePlate.StatusId = 12; break; case 8: nextMachinePlate.StatusId = 13; break; default: break; } } // eseguo cablata la gestione x ora... rispetto a MachineStatus + MachineMode // invierò un machineDTO switch (procStatus) { case 1: lastMachinePlate.ModeId = 2; break; case 2: lastMachinePlate.ModeId = 1; break; case 3: lastMachinePlate.ModeId = 3; break; case 6: lastMachinePlate.ModeId = 8; break; case 8: lastMachinePlate.ModeId = 9; break; case 11: lastMachinePlate.ModeId = 10; break; default: break; } sw.Stop(); Log.Info($"Calcolo C# | {sw.ElapsedTicks} ticks | {sw.ElapsedMilliseconds} ms | {JsonConvert.SerializeObject(nextMachinePlate)}"); Log.Info("-------------"); } #endif // recupero elenco valori... var lastStatusParams = getLastStatusVal(); // verifico parametri state se modificati da ultimo invio --> salvo su DB bool isEqual = testEqual(dataList, lastStatusParams); if (!isEqual) { // calcolo differenze e salvo... var diffVal = dataList.Where(entry => !lastStatusParams.ContainsKey(entry.Key) || lastStatusParams[entry.Key] != entry.Value) .ToDictionary(entry => entry.Key, entry => entry.Value); // salvo ultimo blocco valori last status... setLastStatusVal(dataList); // salvo i valori modificati... List newProdRecords = diffVal .Select(x => new ProdLogModel() { DtRif = adesso, EvType = x.Key, ValStr = x.Value, MachineId = MachineId }) .ToList(); // per ogni valore cerca di effettuare conversione a double... double testVal = 0; foreach (var item in newProdRecords) { testVal = 0; double.TryParse(item.ValStr, out testVal); item.ValNum = testVal; } // salvo su DB x log produzione _ = dbController.ProdLogInsertMany(newProdRecords); } // confronto variazioni x "MACHINE STATUS" if (nextMachinePlate.StatusId != lastMachinePlate.StatusId) { //string codStatus = $"MS_{diffVal[keyStatus]:000}"; string codStatus = $"MS_{nextMachinePlate.StatusId:000}"; // chiamo registrazione sul DB dello stato... var newRecStatus = new StatusLogModel() { MachineId = MachineId, DtRif = adesso, Duration = 0, CodStatus = codStatus }; _ = dbController.StatusLogInsert(newRecStatus); } // invio sulla message pipeline corretta TUTTI i parametri aggiornati serializzati string updRawVal = JsonConvert.SerializeObject(nextMachinePlate); mpStatusSendPipe.saveAndSendMessage(Constants.STATUS_CURR_KEY, updRawVal); } } catch (Exception exc) { Log.Error($"Eccezione in MpStatusRecvPipe_EA_NewMessage:{Environment.NewLine}{exc}"); } } }; void checkProduction(Dictionary dataList) { if (dataList.Count > 0) { var currProdDTO = getCurrProdData(); string currKey = "RECIPE NAME"; if (dataList.ContainsKey(currKey)) { currProdDTO.ProgName = dataList[currKey]; } currKey = "PART COUNT"; if (dataList.ContainsKey(currKey)) { int numPz = 0; int.TryParse(dataList[currKey], out numPz); currProdDTO.CurrQty = numPz; } currKey = "CURR ORDER"; if (dataList.ContainsKey(currKey)) { currProdDTO.Order = dataList[currKey]; } string rawData = JsonConvert.SerializeObject(currProdDTO); mpProdSendPipe.saveAndSendMessage(Constants.PROD_CURR_KEY, rawData); } } /// /// Gestione evento ricezione messaggi status x conteggi... /// void CounterRecvPipe_EA_NewMessage(object? sender, EventArgs e) { PubSubEventArgs currArgs = (PubSubEventArgs)e; // conversione on-the-fly Dictionary --> parametri valorizzati (tra quelli configurati) if (!string.IsNullOrEmpty(currArgs.newMessage)) { try { // verifico codici ricevuti var dataList = JsonConvert.DeserializeObject>(currArgs.newMessage); //int valInt = 0; if (dataList != null) { // chiamo metodo CounterManager... incrementi in MINUTI!!! var incrSet = counterMan.processData(dataList); if (incrSet != null) { foreach (var item in incrSet) { // verifico i valori > soglia 5 minuti... if (item.Value > 5) { // salvo var countRec = countStatus.Where(x => x.CCode == item.Key || x.CodAlias == item.Key).FirstOrDefault(); if (countRec != null) { // attenzione valori in ore, incrementi in minuti! countRec.ActualVal = Math.Round(countRec.ActualVal + item.Value/60, 6); // resetto counterMan.resetAccum(item.Key); } } } } } } catch (Exception exc) { Log.Error($"Eccezione in CounterRecvPipe_EA_NewMessage:{Environment.NewLine}{exc}"); } } } /* -------------------------------- * MAIN * --------------------------------*/ // Ciclo infinito x attesa chiusura con CTRL-C do { Thread.Sleep(100); } while (true);