// See https://aka.ms/new-console-template for more information using MachineSim; using Microsoft.Extensions.Configuration; using MP.MONO.Core; using MP.MONO.Core.CONF; using MP.MONO.Core.DTO; using Newtonsoft.Json; using NLog; using StackExchange.Redis; using System.Reflection; using static MP.MONO.Core.Enums; // 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(); IConfigurationRoot? config = builder.Build(); // imposto variabili di base string lineSep = "---------------------------------------------"; string redisConf = config.GetConnectionString("Redis"); string confPath = Path.Combine(Directory.GetCurrentDirectory(), "conf"); Logger Log = LogManager.GetCurrentClassLogger(); Random rand = new Random(); List? statusList = new List(); List? modeList = new List(); // fix numero minimo dei thread pool x evitare collasso chiamate redis ThreadPool.SetMinThreads(10, 10); Dictionary LogSimulator = new Dictionary(); Dictionary LastKeySave = new Dictionary(); DateTime lastLog = DateTime.Now.AddMinutes(-1); bool verboseLog = false; bool logWriting = false; int MinSaveIntSec = 15; logInfo(lineSep, true, true); logInfo($"Starting Machine SIM", true, true); logInfo($"vers.{Assembly.GetExecutingAssembly().GetName().Version}", true, true); logInfo("", true, true); logInfo($"Redis server param: {redisConf.Substring(0, 20)}...", false, true); logInfo(lineSep, true, true); logInfo("", true, true); logInfo("Running - press CTRL-C to stop SIM", false, true); logInfo("", false, true); // Setup REDIS ConnectionMultiplexer.SetFeatureFlag("preventthreadtheft", true); ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(redisConf); ISubscriber sub = redis.GetSubscriber(); IDatabase? redisDb = redis.GetDatabase(); // preparo oggetti da configurare AlarmReportingMode alarmMode = AlarmReportingMode.ND; List? alarmsBankBitConf = new List(); List? alarmsRawConf = new List(); List? machineModeConf = new List(); List? machineStatusConf = new List(); List? palletStatusConf = new List(); // salvo configurazioni in redis setupConf(); var currSimGen = new Simulator(confPath, modeList.Count, statusList.Count, redisDb, config); // preparo la lista dei contatori invio... LogSimulator.Add(Constants.ACT_LOG_M_QUEUE, 0); LogSimulator.Add(Constants.ALARM_M_QUEUE, 0); LogSimulator.Add(Constants.EVENT_LOG_M_QUEUE, 0); LogSimulator.Add(Constants.PARAMS_M_QUEUE, 0); LogSimulator.Add(Constants.PROD_M_QUEUE, 0); LogSimulator.Add(Constants.MACH_STATS_M_QUEUE, 0); LogSimulator.Add(Constants.MAINT_STATS_M_QUEUE, 0); LogSimulator.Add(Constants.TOOLS_M_QUEUE, 0); // avvio tutti i thread... Thread threadStatus = new Thread(simMPStatus); Thread threadCount = new Thread(simCountersRaw); Thread threadAlarms = new Thread(simAlarms); Thread threadParams = new Thread(simParameters); #if false Thread threadProd = new Thread(simProd); #endif Thread threadMaint = new Thread(simMaint); Thread threadTools = new Thread(simTools); Thread threadEvHistory = new Thread(simEvents); Thread threadActLog = new Thread(simActivityLog); threadStatus.Start(); threadAlarms.Start(); threadParams.Start(); #if false threadProd.Start(); #endif threadCount.Start(); threadMaint.Start(); threadTools.Start(); threadEvHistory.Start(); threadActLog.Start(); /// /// Setup e salvataggio redis delle conf (es modi/stati) /// void setupConf() { // intervallo minimo x salvataggio in redis cache MinSaveIntSec = config.GetValue("SimPar:MinSaveIntSec"); // fix configurazioni di base MachineDTO currMach = new MachineDTO() { Manufacturer = config.GetValue("Machine:Manufacturer"), Model = config.GetValue("Machine:Model"), SerNumber = config.GetValue("Machine:SerialNumber"), Name = config.GetValue("Machine:Name"), ModeId = 0, StatusId = 0 }; redisDb.StringSet(Constants.MACHINE_CONF_PLATE, JsonConvert.SerializeObject(currMach)); // allarmi alarmMode = config.GetValue("OptPar:AlarmMode"); redisDb.StringSetAsync(Constants.ALARMS_MODE_KEY, JsonConvert.SerializeObject(alarmMode)); ConfigManager configManager = new ConfigManager(redisConf, confPath); if (alarmMode == AlarmReportingMode.RawList) { alarmsRawConf = configManager.getAlarmsRawConf("SimAlarmRawList.json"); } else if (alarmMode == AlarmReportingMode.RawListBlink) { alarmsRawConf = configManager.getAlarmsRawConf("SimAlarmRawList.json"); } else { alarmsBankBitConf = configManager.getAlarmsBankConf(); } machineModeConf = configManager.getMachineModeConf(); machineStatusConf = configManager.getMachineStatusConf(); palletStatusConf = configManager.getPalletStatusConf(); // altre conf x cui fare setup var ActLogStatus = configManager.getActLog(); var ToolStatus = configManager.getTools(); var ParamStatus = configManager.getParamsConf(); var AlarmsStatus = configManager.getAlarmsConf(); var MPStatus = configManager.getMPStatusConf(); var CountStatus = configManager.getCountersConf(); } /// /// Effettua log INFO su file e se richiesto su console /// void logInfo(string msg, bool log2file = true, bool log2console = false) { if (log2console) { Console.WriteLine(msg); } if (log2file) { Log.Info(msg); } } /// /// Effettua log ERROR su file e se richiesto su console /// void logError(string msg, bool log2file = true, bool log2console = false) { if (log2console) { Console.WriteLine(msg); } if (log2file) { Log.Error(msg); } } void saveAndSendMessage(string memKey, string notifyChannel, string message) { // effettuo la scrittura nell'area di memoria indicata SE passato intervallo minimo bool doSave = true; if (LastKeySave.ContainsKey(memKey)) { if (DateTime.Now.Subtract(LastKeySave[memKey]).TotalSeconds < MinSaveIntSec) { doSave = false; } } else { LastKeySave.Add(memKey, DateTime.Now); } if (doSave) { if (redisDb != null) { redisDb.StringSetAsync(memKey, message); LastKeySave[memKey] = DateTime.Now; logInfo($"Redis Cache Key: {memKey}"); } } // invio notifica tramite il canale richiesto sub.Publish(notifyChannel, message); if (verboseLog) { logInfo($"[{notifyChannel}] key: {memKey} | val/message: {message}"); } else { try { if (!logWriting) { if (LogSimulator.ContainsKey(notifyChannel)) { LogSimulator[notifyChannel]++; } else { LogSimulator.Add(notifyChannel, 1); } logWriting = true; // vedo se loggare... DateTime adesso = DateTime.Now; if (adesso.Subtract(lastLog).TotalSeconds > 15) { lastLog = adesso; logInfo(lineSep); // lavoro su copia... var LogSimulatorCopy = new Dictionary(LogSimulator); foreach (var item in LogSimulatorCopy) { logInfo($"Redis mQueue {item.Key,-20}{item.Value,12}"); } logInfo(lineSep); } logWriting = false; } } catch (Exception ex) { logError($"ERROR{Environment.NewLine}{ex}"); } } } // Effettua salvataggio allarmi attivi void sendActiveAlarm(Dictionary CurrActiveAlarm) { // serializzo ed invio l'elenco dei soli allarmi attivi List actAlarmList = CurrActiveAlarm.Select(x => x.Key).OrderBy(x => x).ToList(); string rawDataVal = JsonConvert.SerializeObject(actAlarmList); saveAndSendMessage(Constants.ALARM_ACT_KEY, Constants.ALARM_RAW_QUEUE, rawDataVal); } void simAlarms() { // periodo minimo tra check allarmi int minPeriod = 1000; // periodo massimo tra check allarmi int maxPeriod = 5000; int percAllarmi = config.GetValue("SimPar:PercAllarmi"); int MaxAddAllarmi = config.GetValue("SimPar:MaxAddAllarmi"); double MaxDurationAllarmi = config.GetValue("SimPar:MaxDurationAllarmi"); //-------------------------------- // Parametri x SIM BLINK //-------------------------------- // indica se l'ultimo allarme simulato fosse breve bool lastAlarmWasShort = true; // soglia (sec) tra interruzioni/allarmi int sogliaSecAllarmi = config.GetValue("SimPar:SogliaAllarmi"); // periodo (sec) ogni cui fare refresh allarmi (toglie 1:1 e rimette 1:1) int perRefresh = config.GetValue("SimPar:PeriodRefresh"); // periodo in secondi dopo cui è si generano allarmi o interruzioni (alternando) int minOkPeriod = config.GetValue("SimPar:PeriodOk"); // in base alla modalità configurata effettuo la simulazione... if (alarmMode == AlarmReportingMode.RawListBlink) { // controllo se devo fare (se ho valori da simulare...) if (alarmsRawConf.Count > 0 && alarmsRawConf[0].messages.Count > 0) { DateTime lastOk = DateTime.Now; DateTime lastAlarm = DateTime.Now.AddMilliseconds(-1); /* Modalità simulazione blink... * - verifico da quanto non ho allarmi * - se supero soglia minima ok --> simulo allarme o interruzione (alternati) * - vado in simulazione inizio/fine per le interruzioni (brevi) * - vado in simulazione inizio/fine per gli allarmi (che dovranno sparire e tornare ogni 10 sec) */ // Dict allarmi attivi (e da quando) Dictionary CurrActiveAlarm = new Dictionary(); do { DateTime adesso = DateTime.Now; Dictionary NewActiveAlarm = new Dictionary(); bool newAlarm = false; // verifico SE ho superato la soglia minima x "tirare i dadi" e simulare allarmi if (adesso.Subtract(lastOk).TotalSeconds > minOkPeriod) { // simulo allarmi insorti foreach (var alarmGroup in alarmsRawConf) { // simulo e limito insorgenza NUOVI allarmi al percAllarmi/100 dei casi newAlarm = rand.Next(0, 100) <= percAllarmi; if (newAlarm) { int numNew = rand.Next(2, MaxAddAllarmi); // ciclo x aggiungere il numero di allarmi indicato for (int i = 0; i < numNew; i++) { // seleziono uno degli allarmi del banco.. dando + peso agli // "allarmi bassi" int alarmIndex = 0; int msgBlock = rand.Next(1, 5); switch (msgBlock) { case 1: alarmIndex = rand.Next(0, alarmGroup.messages.Count / 4); break; case 2: alarmIndex = rand.Next(0, alarmGroup.messages.Count / 2); break; case 3: alarmIndex = rand.Next(0, alarmGroup.messages.Count * 3 / 4); break; case 4: default: alarmIndex = rand.Next(0, alarmGroup.messages.Count); break; } string rndNewAlarm = alarmGroup.messages[alarmIndex]; if (!NewActiveAlarm.ContainsKey(rndNewAlarm)) { NewActiveAlarm.Add(rndNewAlarm, DateTime.Now); } } } } // se ho allarmi... if (NewActiveAlarm.Count > 0) { // verifico ultimo tipo allarmi breve/veloce if (lastAlarmWasShort) { Dictionary currAlarm = new Dictionary(); // aggiungo 1:1 ed invio foreach (var item in NewActiveAlarm) { Thread.Sleep(rand.Next(0, 10)); currAlarm.Add(item.Key, item.Value); sendActiveAlarm(currAlarm); } // CICLO 01: aspetto periodo blink... tra 80 e 100% periodo Thread.Sleep(rand.Next(800 * perRefresh, 1000 * perRefresh)); // tolgo tutti ed invio currAlarm = new Dictionary(); sendActiveAlarm(currAlarm); // riaggiungo 1:1 ed invio foreach (var item in NewActiveAlarm) { Thread.Sleep(rand.Next(100, 300)); currAlarm.Add(item.Key, item.Value); sendActiveAlarm(currAlarm); } // CICLO 02: aspetto periodo blink... Thread.Sleep(rand.Next(800 * perRefresh, 1000 * perRefresh)); // tolgo tutti ed invio currAlarm = new Dictionary(); sendActiveAlarm(currAlarm); // riaggiungo 1:1 ed invio foreach (var item in NewActiveAlarm) { Thread.Sleep(rand.Next(100, 300)); currAlarm.Add(item.Key, item.Value); sendActiveAlarm(currAlarm); } // CICLO 03: aspetto periodo blink... Thread.Sleep(rand.Next(800 * perRefresh, 1000 * perRefresh)); // tolgo tutti ed invio currAlarm = new Dictionary(); sendActiveAlarm(currAlarm); // riaggiungo 1:1 ed invio foreach (var item in NewActiveAlarm) { Thread.Sleep(rand.Next(100, 300)); currAlarm.Add(item.Key, item.Value); sendActiveAlarm(currAlarm); } Thread.Sleep(rand.Next(800 * perRefresh, 1000 * perRefresh)); // svuoto NewActiveAlarm = new Dictionary(); // invio update sendActiveAlarm(NewActiveAlarm); } else { // invio allarmi sendActiveAlarm(NewActiveAlarm); // aspetto poco Thread.Sleep(rand.Next(1000, 1000 * sogliaSecAllarmi)); // svuoto NewActiveAlarm = new Dictionary(); // invio update sendActiveAlarm(NewActiveAlarm); } // resetto/svuoto allarmi correnti... NewActiveAlarm = new Dictionary(); // inverto tipo simulato... lastAlarmWasShort = !lastAlarmWasShort; // indico che ora è INIZIATO periodo OK... lastOk = DateTime.Now; } } // copio i nuovi allarmi nella memoria principale... CurrActiveAlarm = NewActiveAlarm; // invio sendActiveAlarm(CurrActiveAlarm); // attesa random Thread.Sleep(rand.Next(minPeriod, maxPeriod)); } while (true); } } else if (alarmMode == AlarmReportingMode.RawList) { // controllo se devo fare (se ho valori da simulare...) if (alarmsRawConf.Count > 0 && alarmsRawConf[0].messages.Count > 0) { DateTime lastOk = DateTime.Now; // Dict allarmi attivi (e da quando) Dictionary CurrActiveAlarm = new Dictionary(); do { bool newAlarm = false; DateTime adesso = DateTime.Now; Dictionary NewActiveAlarm = new Dictionary(); // se fosse oltre MaxDurationAllarmi * 3 sec senza all ok --> riporta ad all Ok if (lastOk.Subtract(adesso).TotalSeconds > MaxDurationAllarmi * 3 && CurrActiveAlarm.Count > 0) { lastOk = adesso; } else { // verifico situazione allarmi già presenti foreach (var singleAlarm in CurrActiveAlarm) { // 1: seleziono allarmi se ce ne fossero e la loro durata è <= limite * rand // (50-200) x rimetterli in elenco if (adesso.Subtract(singleAlarm.Value).TotalSeconds < MaxDurationAllarmi * ((double)rand.Next(50, 200) / 100)) { NewActiveAlarm.Add(singleAlarm.Key, singleAlarm.Value); } } // ciclo x numero banchi da config... foreach (var alarmGroup in alarmsRawConf) { // simulo e limito insorgenza NUOVI allarmi al percAllarmi/100 dei casi newAlarm = rand.Next(0, 100) <= percAllarmi; if (newAlarm) { int numNew = rand.Next(1, MaxAddAllarmi); // ciclo x aggiungere il numero di allarmi indicato for (int i = 0; i < numNew; i++) { // seleziono uno degli allarmi del banco.. dando + peso agli // "allarmi bassi" int alarmIndex = 0; int msgBlock = rand.Next(1, 5); switch (msgBlock) { case 1: alarmIndex = rand.Next(0, alarmGroup.messages.Count / 4); break; case 2: alarmIndex = rand.Next(0, alarmGroup.messages.Count / 2); break; case 3: alarmIndex = rand.Next(0, alarmGroup.messages.Count * 3 / 4); break; case 4: default: alarmIndex = rand.Next(0, alarmGroup.messages.Count); break; } string rndNewAlarm = alarmGroup.messages[alarmIndex]; if (!NewActiveAlarm.ContainsKey(rndNewAlarm)) { NewActiveAlarm.Add(rndNewAlarm, DateTime.Now); } } } } } // copio i nuovi allarmi nella memoria principale... CurrActiveAlarm = NewActiveAlarm; // serializzo ed invio l'elenco dei soli allarmi attivi List actAlarmList = CurrActiveAlarm.Select(x => x.Key).OrderBy(x => x).ToList(); string rawDataVal = JsonConvert.SerializeObject(actAlarmList); saveAndSendMessage(Constants.ALARM_ACT_KEY, Constants.ALARM_RAW_QUEUE, rawDataVal); // attesa random Thread.Sleep(rand.Next(minPeriod, maxPeriod)); } while (true); } } else if (alarmMode == AlarmReportingMode.BankBit) { // controllo se devo fare (se ho valori da simulare...) if (alarmsBankBitConf.Count > 0) { do { Dictionary currAlarmsVal = new Dictionary(); uint alarmCode = 0; bool hasAlarm; // ciclo x numero banchi da config... foreach (var item in alarmsBankBitConf) { // limito allarmi al percAllarmi/100 dei casi hasAlarm = rand.Next(0, 100) <= percAllarmi; // allarme libero SE in condizione allarme alarmCode = hasAlarm ? (uint)rand.Next(0, 65535) : 0; currAlarmsVal.Add(item.memAddr, alarmCode); } string rawDataVal = JsonConvert.SerializeObject(currAlarmsVal); saveAndSendMessage(Constants.ALARM_ACT_KEY, Constants.ALARM_RAW_QUEUE, rawDataVal); // attesa random Thread.Sleep(rand.Next(minPeriod, maxPeriod)); } while (true); } } } void simParameters() { int minPeriod = 150; int maxPeriod = 500; // controllo se devo fare (se ho valori da simulare...) if (currSimGen.currSimPar.Count > 0) { do { // gestione SIM dictionary var paramDict = currSimGen.getParamsVal(); string rawData = JsonConvert.SerializeObject(paramDict); saveAndSendMessage(Constants.PARAMS_ACT_KEY, Constants.PARAMS_RAW_QUEUE, rawData); // attesa random Thread.Sleep(rand.Next(minPeriod, maxPeriod)); } while (true); } } void simMPStatus() { int minPeriod = 1000; int maxPeriod = 5000; // controllo se devo fare (se ho valori da simulare...) if (currSimGen.currSimMachStat.Count > 0) { do { // recupero uno stato simulato var newStatus = currSimGen.getStatus(); string rawData = JsonConvert.SerializeObject(newStatus); saveAndSendMessage(Constants.STATUS_ACT_KEY, Constants.STATUS_RAW_QUEUE, rawData); // attesa random Thread.Sleep(rand.Next(minPeriod, maxPeriod)); } while (true); } } void simCountersRaw() { int minPeriod = 1000; int maxPeriod = 5000; // controllo se devo fare (se ho valori da simulare...) if (currSimGen.currSimMachStat.Count > 0) { do { // recupero uno stato simulato var newStatus = currSimGen.getCountersRaw(); string rawData = JsonConvert.SerializeObject(newStatus); saveAndSendMessage(Constants.COUNT_CURR_KEY, Constants.COUNT_RAW_QUEUE, rawData); // attesa random Thread.Sleep(rand.Next(minPeriod, maxPeriod)); } while (true); } } void simMaint() { int minPeriod = 10000; int maxPeriod = 20000; // controllo se devo fare (se ho valori da simulare...) if (currSimGen.currSimMaint.Count > 0) { do { // recupero uno stato simulato var newVal = currSimGen.getMaint(); string rawData = JsonConvert.SerializeObject(newVal); saveAndSendMessage(Constants.MAINT_STATS_CURR_KEY, Constants.MAINT_STATS_M_QUEUE, rawData); // attesa random Thread.Sleep(rand.Next(minPeriod, maxPeriod)); } while (true); } } void simTools() { int minPeriod = 30000; int maxPeriod = 120000; // controllo se devo fare (se ho valori da simulare...) if (currSimGen.currSimTools.Count > 0) { do { // recupero uno stato simulato var newVal = currSimGen.getToolsKVP(); string rawData = JsonConvert.SerializeObject(newVal); saveAndSendMessage(Constants.TOOLS_ACT_KEY, Constants.TOOLS_RAW_QUEUE, rawData); // attesa random Thread.Sleep(rand.Next(minPeriod, maxPeriod)); } while (true); } } void simEvents() { int minPeriod = 200; int maxPeriod = 800; // controllo se devo fare (se ho valori da simulare...) if (currSimGen.currSimEvHist.Count > 0) { do { // recupero uno stato simulato var newVal = currSimGen.getEvents(); string rawData = JsonConvert.SerializeObject(newVal); saveAndSendMessage(Constants.EVENT_LOG_CURR_KEY, Constants.EVENT_LOG_M_QUEUE, rawData); // attesa random Thread.Sleep(rand.Next(minPeriod, maxPeriod)); } while (true); } } void simActivityLog() { int minPeriod = 15000; int maxPeriod = 30000; // controllo se devo fare (se ho valori da simulare...) if (currSimGen.currSimActLog.Count > 0) { do { // recupero uno stato simulato var newVal = currSimGen.getActLog(); string rawData = JsonConvert.SerializeObject(newVal); saveAndSendMessage(Constants.ACT_LOG_CURR_KEY, Constants.ACT_LOG_M_QUEUE, rawData); // attesa random Thread.Sleep(rand.Next(minPeriod, maxPeriod)); } while (true); } }