598 lines
21 KiB
C#
598 lines
21 KiB
C#
using MathNet.Numerics.Distributions;
|
|
using Microsoft.Extensions.Configuration;
|
|
using MP.MONO.Core.DTO;
|
|
using Newtonsoft.Json;
|
|
using StackExchange.Redis;
|
|
|
|
namespace MachineSim
|
|
{
|
|
/// <summary>
|
|
/// Definizione parametri simulati
|
|
/// </summary>
|
|
public class Simulator
|
|
{
|
|
#region Public Fields
|
|
|
|
public List<string> currCountersRaw = new List<string>();
|
|
public List<SimDisplayDataDTO> currSimActLog = new List<SimDisplayDataDTO>();
|
|
public List<SimDisplayDataDTO> currSimEvHist = new List<SimDisplayDataDTO>();
|
|
public List<string> currSimMachStat = new List<string>();
|
|
public List<SimDisplayDataDTO> currSimMaint = new List<SimDisplayDataDTO>();
|
|
public List<SimDisplayDataDTO> currSimPar = new List<SimDisplayDataDTO>();
|
|
public List<SimDisplayDataDTO> currSimProd = new List<SimDisplayDataDTO>();
|
|
public List<SimDisplayDataDTO> currSimTools = new List<SimDisplayDataDTO>();
|
|
|
|
#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<DisplayDataDTO> getActLog()
|
|
{
|
|
List<DisplayDataDTO> answ = new List<DisplayDataDTO>();
|
|
|
|
// 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<String> getAlarms()
|
|
{
|
|
List<string> activeAlarms = new List<string>();
|
|
//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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restituisce elenco info dati x contatori come lista da adapter
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public Dictionary<string, string> getCountersRaw()
|
|
{
|
|
Dictionary<string, string> answ = new Dictionary<string, string>();
|
|
|
|
// 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<DisplayDataDTO> getEvents()
|
|
{
|
|
List<DisplayDataDTO> answ = new List<DisplayDataDTO>();
|
|
|
|
// 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<DisplayDataDTO> getMacStats()
|
|
{
|
|
List<DisplayDataDTO> answ = new List<DisplayDataDTO>();
|
|
|
|
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<DisplayDataDTO> getMaint()
|
|
{
|
|
List<DisplayDataDTO> answ = new List<DisplayDataDTO>();
|
|
|
|
// 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<DisplayDataDTO> getParameters()
|
|
{
|
|
List<DisplayDataDTO> answ = new List<DisplayDataDTO>();
|
|
|
|
// 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<string, double> getParamsVal()
|
|
{
|
|
Dictionary<string, double> answ = new Dictionary<string, double>();
|
|
// 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restituisce elenco info stato Macchina simulato come lista da adapter
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public Dictionary<string, string> getStatus()
|
|
{
|
|
DateTime adesso = DateTime.Now;
|
|
Dictionary<string, string> answ = new Dictionary<string, string>();
|
|
// 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Simulo dati in formato DTO (già finale)
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public List<DisplayDataDTO> getToolsDTO()
|
|
{
|
|
List<DisplayDataDTO> answ = new List<DisplayDataDTO>();
|
|
|
|
// 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Mostro un valore in formato chiave/valore (stringa/double)
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public Dictionary<string, double> getToolsKVP()
|
|
{
|
|
Dictionary<string, double> answ = new Dictionary<string, double>();
|
|
// 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;
|
|
}
|
|
|
|
/// <summary> 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 </summary> <param name="CurrVal"></param> <param
|
|
/// name="MinVal"></param> <param name="MaxVal"></param> <param name="sMean"></param> <param
|
|
/// name="sStd"></param> <param name="simMode">Modalità simulazione (1-3) 1 = std,
|
|
/// RandomWalk + trend periodico con memoria 2 = boolean 3 = trend secondo valore mean
|
|
/// (crescente se >0, decrescente se <0) </param> <param name="trueRatio"></param> <returns></returns>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Discretizza valore sim pallet tra 0..1..2 dati valori limite
|
|
/// </summary>
|
|
/// <param name="simVal"></param>
|
|
/// <param name="lim_1"></param>
|
|
/// <param name="lim_2"></param>
|
|
/// <returns></returns>
|
|
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<SimDisplayDataDTO> localObj)
|
|
{
|
|
string fullPath = Path.Combine(confPath, paramFileName);
|
|
if (File.Exists(fullPath))
|
|
{
|
|
var rawData = File.ReadAllText(fullPath);
|
|
if (!string.IsNullOrEmpty(rawData))
|
|
{
|
|
var deserObj = JsonConvert.DeserializeObject<List<SimDisplayDataDTO>>(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<string> localObj)
|
|
{
|
|
string fullPath = Path.Combine(confPath, paramFileName);
|
|
if (File.Exists(fullPath))
|
|
{
|
|
var rawData = File.ReadAllText(fullPath);
|
|
if (!string.IsNullOrEmpty(rawData))
|
|
{
|
|
var deserObj = JsonConvert.DeserializeObject<List<String>>(rawData);
|
|
if (deserObj != null)
|
|
{
|
|
localObj = deserObj;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion Private Methods
|
|
}
|
|
} |