using MathNet.Numerics.Distributions; using Microsoft.Extensions.Configuration; using MP.MONO.Core.DTO; using Newtonsoft.Json; using StackExchange.Redis; namespace MachineSim { /// /// Definizione parametri simulati /// public class Simulator { #region Public Fields public List currCountersRaw = new List(); public List currSimActLog = new List(); public List currSimEvHist = new List(); public List currSimMachStat = new List(); public List currSimMaint = new List(); public List currSimPar = new List(); public List currSimProd = new List(); public List currSimTools = new List(); #endregion Public Fields #region Public Constructors public Simulator(string _confPath, int nMode, int nStatus, IDatabase currRedisDb, IConfigurationRoot currAppConf) { confPath = _confPath; numMode = nMode; numStatus = nStatus; redisDb = currRedisDb; appConf = currAppConf; setupConfig(); } #endregion Public Constructors #region Public Methods public List getActLog() { List answ = new List(); // genero a partire dall'elenco configurato da simulare... answ = currSimActLog.Select(i => new DisplayDataDTO() { IsNumeric = i.IsNumeric, MaxVal = i.MaxVal, MinVal = i.MinVal, Order = i.Order, Title = i.Title, Type = i.Type, ValueNum = simNext(i.ValueNum, i.MinVal, i.MaxVal, i.SimMean, i.SimStd, 1), DisplFormat = i.DisplFormat }).ToList(); foreach (var item in answ) { item.Value = $"{item.ValueNum.ToString(item.DisplFormat)}"; } return answ; } public List getAlarms() { List activeAlarms = new List(); //int alarmCode = rand.Next(0, 255); int alarmCode = rand.Next(0, 160); // se >= 128 --> 0 (no alarm) alarmCode = alarmCode <= 127 ? alarmCode : 0; for (int i = 0; i < 8; i++) { if ((alarmCode & (1 << i)) != 0) { activeAlarms.Add($"Alarm {i:000}"); } } return activeAlarms; } /// /// Restituisce elenco info dati x contatori come lista da adapter /// /// public Dictionary getCountersRaw() { Dictionary answ = new Dictionary(); // simulo stato macchina ad 1 nel 99% (0 nel 1% dei casi if (rand.Next(0, 1000) < 990) { answ.Add("MACHINE STATUS", "1"); } else { answ.Add("MACHINE STATUS", "1"); } // simulo stato processo in ciclo 90% dei casi, resto HOLD if (rand.Next(0, 1000) < 900) { answ.Add("PROCESS STATUS", "2"); // simulo modo auto 90% dei casi if (rand.Next(0, 1000) < 900) { answ.Add("PROCESS MODE", "2"); // aggiungo spindle load che sono attivi 50% del tempo ogni coppia, random if (rand.Next(0, 1000) < 500) { answ.Add("SPINDLE 1 LOAD", $"{rand.Next(5, 80)}"); answ.Add("SPINDLE 2 LOAD", $"{rand.Next(5, 80)}"); answ.Add("SPINDLE 3 LOAD", $"{rand.Next(5, 80)}"); answ.Add("SPINDLE 4 LOAD", $"{rand.Next(5, 80)}"); } else { answ.Add("SPINDLE 1 LOAD", "0"); answ.Add("SPINDLE 2 LOAD", "0"); answ.Add("SPINDLE 3 LOAD", "0"); answ.Add("SPINDLE 4 LOAD", "0"); } } else { answ.Add("PROCESS MODE", "1"); answ.Add("SPINDLE 1 LOAD", "0"); answ.Add("SPINDLE 2 LOAD", "0"); answ.Add("SPINDLE 3 LOAD", "0"); answ.Add("SPINDLE 4 LOAD", "0"); } } else { answ.Add("PROCESS STATUS", "3"); answ.Add("PROCESS MODE", "7"); answ.Add("SPINDLE 1 LOAD", "0"); answ.Add("SPINDLE 2 LOAD", "0"); answ.Add("SPINDLE 3 LOAD", "0"); answ.Add("SPINDLE 4 LOAD", "0"); } return answ; } public List getEvents() { List answ = new List(); // genero a partire dall'elenco configurato da simulare... answ = currSimEvHist.Select(i => new DisplayDataDTO() { IsNumeric = i.IsNumeric, MaxVal = i.MaxVal, MinVal = i.MinVal, Order = i.Order, Title = i.Title, Type = i.Type, ValueNum = simNext(i.ValueNum, i.MinVal, i.MaxVal, i.SimMean, i.SimStd, 1), DisplFormat = i.DisplFormat }).ToList(); foreach (var item in answ) { item.Value = $"{item.ValueNum.ToString(item.DisplFormat)}"; } return answ; } public List getMacStats() { List answ = new List(); var oee = new DisplayDataDTO() { IsNumeric = true, MaxVal = 1, MinVal = 0.50, Order = 1, Title = "OEE", Type = "OEE", ValueNum = simNext(.75, .50, 1, .1, .2, 1, 0.5), DisplFormat = "P1" }; answ.Add(oee); foreach (var item in answ) { item.Value = $"{item.ValueNum.ToString(item.DisplFormat)}"; } return answ; } public List getMaint() { List answ = new List(); // genero a partire dall'elenco configurato da simulare... answ = currSimMaint.Select(i => new DisplayDataDTO() { IsNumeric = i.IsNumeric, MaxVal = i.MaxVal, MinVal = i.MinVal, Order = i.Order, Title = i.Title, Type = i.Type, ValueNum = simNext(i.ValueNum, i.MinVal, i.MaxVal, i.SimMean, i.SimStd, i.IsBoolean ? 2 : 1, i.TrueRatio), DisplFormat = i.DisplFormat }).ToList(); foreach (var item in answ) { item.Value = $"{item.ValueNum.ToString(item.DisplFormat)}"; } return answ; } public List getParameters() { List answ = new List(); // genero a partire dall'elenco configurato da simulare... answ = currSimPar.Select(i => new DisplayDataDTO() { IsNumeric = i.IsNumeric, MaxVal = i.MaxVal, MinVal = i.MinVal, Order = i.Order, Title = i.Title, Type = i.Type, ValueNum = simNext(i.ValueNum, i.MinVal, i.MaxVal, i.SimMean, i.SimStd, 1), DisplFormat = i.DisplFormat, CssIcon = i.CssIcon, EnablePlot = i.EnablePlot, ShowBar = i.ShowBar, ShowGauge = i.ShowGauge, }).ToList(); foreach (var item in answ) { item.Value = $"{item.ValueNum.ToString(item.DisplFormat)}"; } return answ; } public Dictionary getParamsVal() { Dictionary answ = new Dictionary(); // genero a partire dall'elenco configurato da simulare... answ = currSimPar.ToDictionary(i => i.Title, i => simNext(i.ValueNum, i.MinVal, i.MaxVal, i.SimMean, i.SimStd, 1)); return answ; } public ProductionDTO getProd() { int stdCycle = 5; ProductionDTO currMachDto = new ProductionDTO() { Order = "ODL Test", ItemCode = "ART.0000123", ProgName = "P000012", CurrQty = DateTime.Now.Minute + rand.Next(1, 40), OrderQty = 100, CycleTimeMin = rand.NextDouble() * stdCycle, Message = "...simulated data..." }; return currMachDto; } /// /// Restituisce elenco info stato Macchina simulato come lista da adapter /// /// public Dictionary getStatus() { DateTime adesso = DateTime.Now; Dictionary answ = new Dictionary(); // dalle 22 alle 6 --> machine OFF if (adesso.Hour < 6 || adesso.Hour >= 22) { answ.Add("MACHINE STATUS", "0"); answ.Add("PART COUNT", "0"); answ.Add("RECIPE NAME", "none"); answ.Add("CURR ORDER", "NA"); answ.Add("PROCESS STATUS", "1"); answ.Add("PROCESS MODE", "1"); answ.Add("P2 PALLET STATUS", "0"); answ.Add("P3 PALLET STATUS", "0"); answ.Add("P2 EXTERNAL PALLET STATUS", "0"); answ.Add("P3 EXTERNAL PALLET STATUS", "0"); } else { // simulo stato macchina ad 1 nel 99% (0 nel 1% dei casi) if (rand.Next(0, 1000) < 990) { answ.Add("MACHINE STATUS", "1"); } else { answ.Add("MACHINE STATUS", "0"); } // part count sono i minuti del giorno... int simCount = (int)adesso.Subtract(DateTime.Today).TotalMinutes; answ.Add("PART COUNT", $"{simCount}"); // simulo stato processo in ciclo 90% dei casi, resto HOLD if (rand.Next(0, 1000) < 900) { answ.Add("PROCESS STATUS", "2"); // simulo modo auto 85% dei casi if (rand.Next(0, 1000) < 850) { answ.Add("PROCESS MODE", "2"); } else { answ.Add("PROCESS MODE", "1"); } } else { answ.Add("PROCESS STATUS", "3"); answ.Add("PROCESS MODE", "7"); } // simulo status pallets... // Primo: 8% assente = 0, 90% presente=1, restante 2% error/hold=2 int pVal = discretizePalletVal(rand.Next(0, 1000), 80, 900); answ.Add("P2 PALLET STATUS", $"{pVal}"); // secondo: 7% assente = 0, 91% presente=1, restante 2% error/hold=2 pVal = discretizePalletVal(rand.Next(0, 1000), 70, 910); answ.Add("P3 PALLET STATUS", $"{pVal}"); // terzo: 6% assente = 0, 91% presente=1, restante 3% error/hold=2 pVal = discretizePalletVal(rand.Next(0, 1000), 60, 910); answ.Add("P2 EXTERNAL PALLET STATUS", $"{pVal}"); // quarto: 8% assente = 0, 89% presente=1, restante 3% error/hold=2 pVal = discretizePalletVal(rand.Next(0, 1000), 80, 890); answ.Add("P3 EXTERNAL PALLET STATUS", $"{pVal}"); // aggiunta part program che cambia ogni 10 minuti... int minRound = (adesso.Minute / 10) * 10; answ.Add("RECIPE NAME", $"{adesso:yyMMdd-HH}{minRound:00}.prg"); // aggiunta ordine... orario answ.Add("CURR ORDER", $"ODL000{adesso:MMddHH}"); } return answ; } /// /// Simulo dati in formato DTO (già finale) /// /// public List getToolsDTO() { List answ = new List(); // genero a partire dall'elenco configurato da simulare... answ = currSimTools.Select(i => new DisplayDataDTO() { IsNumeric = i.IsNumeric, MaxVal = i.MaxVal, MinVal = i.MinVal, Order = i.Order, Title = i.Title, Type = i.Type, ValueNum = simNext(i.ValueNum, i.MinVal, i.MaxVal, i.SimMean, i.SimStd, 3), DisplFormat = i.DisplFormat, EnablePlot = i.EnablePlot, ShowBar = i.ShowBar, ShowGauge = i.ShowGauge, CssIcon = i.CssIcon, HLShow = i.HLShow }).ToList(); foreach (var item in answ) { item.Value = $"{item.ValueNum.ToString(item.DisplFormat)}"; // aggiorno con ultimo valore simulato... var simItem = currSimTools.FirstOrDefault(x => x.Title == item.Title); if (simItem != null) { simItem.ValueNum = item.ValueNum; } } return answ; } /// /// Mostro un valore in formato chiave/valore (stringa/double) /// /// public Dictionary getToolsKVP() { Dictionary answ = new Dictionary(); // calcolo come DTO (e così si aggiorna trend) var randDto = getToolsDTO(); // genero a partire dall'elenco configurato da simulare... answ = randDto.ToDictionary(i => i.Title, i => i.ValueNum); return answ; } /// Calcola prox valore simulato dati i parametri rand configurati, con /// innovazione stocastica + deterministica: /// - ciclo ogni 4 minuti - con operazione modulo (4) /// * 0 fermo /// * 1 --> sale /// * 2 fermo /// * 3 --> scende Modalità simulazione (1-3) 1 = std, /// RandomWalk + trend periodico con memoria 2 = boolean 3 = trend secondo valore mean /// (crescente se >0, decrescente se <0) public double simNext(double CurrVal, double MinVal, double MaxVal, double sMean, double sStd, int simMode = 1, double trueRatio = 0.5) { double innov = 0; double trend = 0; // se è boolean --> simulazione 0/1 rapida con % ratio indicata switch (simMode) { case 2: CurrVal = rand.Next(10000) <= trueRatio * 10000 ? 1 : 0; break; case 3: innov = Normal.Sample(sMean, sStd); trend = rand.NextDouble() * 2 * sMean; double delta = innov + trend; // il delta deve essere dello stesso segno della media... if (delta * sMean < 0) { delta = delta * -1; } CurrVal += delta; // verifico estremi... CurrVal = CurrVal > MaxVal ? MinVal : CurrVal; CurrVal = CurrVal < MinVal ? MaxVal : CurrVal; break; case 1: default: // innovazione stocastica di base innov = Normal.Sample(sMean, sStd); int multFact = rand.Next(1, 6); // innovazione deterministica: int resto = (DateTime.Now.Second % 10); trend = 0; switch (resto) { case 0: case 1: case 7: trend = sMean + multFact * sStd; break; case 2: case 5: case 6: trend = -(sMean + multFact * sStd); break; default: break; } CurrVal += innov + trend; // verifico estremi... CurrVal = CurrVal > MaxVal ? MaxVal : CurrVal; CurrVal = CurrVal < MinVal ? MinVal : CurrVal; break; } return CurrVal; } #endregion Public Methods #region Protected Fields protected string confPath = ""; protected Random rand = new Random(); #endregion Protected Fields #region Protected Properties protected IConfigurationRoot appConf { get; set; } = null!; protected int numMode { get; set; } = 1; protected int numStatus { get; set; } = 1; protected IDatabase redisDb { get; set; } = null!; #endregion Protected Properties #region Protected Methods /// /// Discretizza valore sim pallet tra 0..1..2 dati valori limite /// /// /// /// /// protected int discretizePalletVal(int simVal, int lim_1, int lim_2) { int answ = 0; if (simVal <= lim_1) { answ = 0; } else if (simVal <= (lim_1 + lim_2)) { answ = 1; } else { answ = 2; } return answ; } #endregion Protected Methods #region Private Methods private void setupConfDTO(string paramFileName, ref List localObj) { string fullPath = Path.Combine(confPath, paramFileName); if (File.Exists(fullPath)) { var rawData = File.ReadAllText(fullPath); if (!string.IsNullOrEmpty(rawData)) { var deserObj = JsonConvert.DeserializeObject>(rawData); if (deserObj != null) { localObj = deserObj; } } } } private void setupConfig() { // setup conf Parametri setupConfDTO("SimParams.json", ref currSimPar); // setup conf Maintenance setupConfDTO("SimMaint.json", ref currSimMaint); // setup conf Tools setupConfDTO("SimTools.json", ref currSimTools); // setup conf Event History setupConfDTO("SimEvHist.json", ref currSimEvHist); // setup conf Activity Log setupConfDTO("SimActLog.json", ref currSimActLog); // setup conf Prod setupConfDTO("SimProd.json", ref currSimProd); // setup conf MachStat setupConfList("SimMachStat.json", ref currSimMachStat); // setup conf Prod setupConfList("SimStatus.json", ref currCountersRaw); } private void setupConfList(string paramFileName, ref List localObj) { string fullPath = Path.Combine(confPath, paramFileName); if (File.Exists(fullPath)) { var rawData = File.ReadAllText(fullPath); if (!string.IsNullOrEmpty(rawData)) { var deserObj = JsonConvert.DeserializeObject>(rawData); if (deserObj != null) { localObj = deserObj; } } } } #endregion Private Methods } }