Files
2026-05-12 07:19:24 +02:00

1163 lines
47 KiB
C#

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MP.Core.Objects;
using MP.Data.DbModels;
using MP.Data.Repository.IOC;
using Newtonsoft.Json;
using NLog;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using ZiggyCreatures.Caching.Fusion;
using static MP.Core.Objects.Enums;
namespace MP.Data.Services.IOC
{
public class IocService : BaseServ, IIocService
{
#region Public Constructors
public IocService(
IConfiguration config,
IConnectionMultiplexer redis,
IIocRepository repo,
IServiceScopeFactory scopeFactory,
//Microsoft.Extensions.Caching.Memory.IMemoryCache cache
IFusionCache cache) : base(config, redis)
{
_className = "IocServ";
int.TryParse(config.GetValue<string>("ServerConf:redisLongTimeCache"), out redisLongTimeCache);
int.TryParse(config.GetValue<string>("ServerConf:redisShortTimeCache"), out redisShortTimeCache);
_repo = repo;
_scopeFactory = scopeFactory;
_cache = cache;
}
#endregion Public Constructors
#region Public Methods
/// <inheritdoc />
public async Task<bool> CheckCambiaStatoBatchAsync(tipoInputEvento tipoInput, string IdxMacchina, DateTime InizioStato, int IdxTipo, string CodArt, string Value, int MatrOpr, string pallet)
{
bool success = await _repo.CheckCambiaStatoBatchAsync(tipoInput, IdxMacchina, InizioStato, IdxTipo, CodArt, Value, MatrOpr, pallet);
return success;
}
/// <inheritdoc />
public async Task<bool> ClearFusionCache()
{
bool fatto = false;
await _cache.ClearAsync();
fatto = true;
return fatto;
}
/// <inheritdoc />
public async Task<bool> EvListMicroStatoInsertAsync(MicroStatoMacchinaModel newRecMsm, EventListModel newRecEv)
{
bool success = await _repo.EvListMicroStatoInsertAsync(newRecMsm, newRecEv);
return success;
}
/// <inheritdoc />
public async Task<string> GetCurrOdlAsync(string idxMacchina)
{
string result = "";
string currKey = $"{MP.Data.Utils.redisOdlCurrByMac}:{idxMacchina}";
// cerco in redis dato valOut sel macchina...
RedisValue rawData = _redisDb.StringGet(currKey);
if (rawData.HasValue)
{
result = $"{rawData}";
}
else
{
result = await GetCurrOdlByProdAsync(idxMacchina);
// serializzo e salvo...
rawData = JsonConvert.SerializeObject(result);
_redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache));
}
return result;
}
/// <inheritdoc />
public async Task<Dictionary<string, string>> GetTask2ExeMacchinaAsync(string idxMacchina)
{
var currHash = MP.Data.Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS);
return await RedisGetHashDictAsync(currHash);
}
/// <inheritdoc />
public async Task<bool> IobInsEnabAsync(string idxMacchina)
{
string cacheKey = $"IOC_IobInsEnab_{idxMacchina}";
return await GetOrFetchAsync(cacheKey, async () =>
{
var key = MP.Data.Utils.RedKeyDatiMacc(idxMacchina, MpIoNS);
string? val = await _redisDb.HashGetAsync(key, "insEnabled");
if (val == null)
{
var data = await ResetDatiMacchinaAsync(idxMacchina);
data.TryGetValue("insEnabled", out val);
}
return val != null && (val == "1" || val.ToLower() == "true");
}, TimeSpan.FromSeconds(5));
}
/// <inheritdoc />
public async Task<bool> MicroStatoMacchinaUpsertAsync(MicroStatoMacchinaModel newRec)
{
bool success = await _repo.MicroStatoMacchinaUpsertAsync(newRec);
return success;
}
/// <inheritdoc />
public async Task<string> ProcessInputAsync(string idxMacchina, string valore, string dtEve, string dtCurr, string contatore)
{
string answ = "";
if (ValidateInputParams(idxMacchina, valore, dtEve, dtCurr))
{
DateTime dataOraEvento = ParseEventTime(dtEve, dtCurr);
// se abilitato registro evento sul DB
if (await IobSLogEnabAsync(idxMacchina))
{
int cntVal = 0;
int.TryParse(contatore, out cntVal);
await saveSigLogAsync(idxMacchina, valore, dataOraEvento, cntVal);
}
// continuo col resto
try
{
// scrivo keep alive!!! (se necessario, altrimenti è in cache...)
await ScriviKeepAliveAsync(idxMacchina, DateTime.Now);
// Cache dati macchina per evitare lookup ridondanti
Dictionary<string, string> datiMacc = await mDatiMacchineAsync(idxMacchina);
// verifico se sia una macchina MULTI....
if (isMulti(idxMacchina, datiMacc))
{
// inizio preprocessing
string newVal = "";
// processo OGNI macchina a stati dell'impianto... (KEY:IdxMacchina / IdxMacchina#qualcosa, Val = IdxFamIn)
foreach (var item in await mTabMSMIAsync(idxMacchina))
{
newVal = preProcInput(item.Key, valore, datiMacc);
// ora processo e salvo il valOut del microstato...
// INTERNAMENTE gestisce i casi DB/REDIS secondo necessità
await CheckMicroStatoAsync(item.Key, newVal, dataOraEvento, contatore, datiMacc);
}
}
else
{
// ora processo e salvo il valOut del microstato... INTERNAMENTE
// gestisce i casi DB/REDIS secondo necessità
await CheckMicroStatoAsync(idxMacchina, valore, dataOraEvento, contatore, datiMacc);
}
// forzo RESET dati macchina...
await ResetDatiMacchinaAsync(idxMacchina);
// registro in risposta che è andato tutto bene...
answ = "OK";
}
catch (Exception exc)
{
string errore = $"Errore: {Environment.NewLine}{exc}";
Log.Error(errore);
answ = errore;
}
}
return answ;
}
/// <inheritdoc />
public async Task<int> PzCounterTcAsync(string idxMacchina)
{
int answ = -1;
DateTime dataRif = DateTime.Now;
var datiProd = await StatoProdMacchinaAsync(idxMacchina, dataRif);
if (datiProd != null)
{
answ = datiProd.PzTotODL;
}
return answ;
}
/// <inheritdoc />
public async Task<string> SaveCounterAsync(string idxMacchina, string counter)
{
string answ = "0";
// inizio processing vero e proprio INPUT...
if (string.IsNullOrEmpty(idxMacchina))
{
string errore = "Errore: parametro macchina vuoto";
Log.Warn(errore);
answ = errore;
}
else
{
if (string.IsNullOrEmpty(counter))
{
string errore = "Errore: parametro counter vuoto";
Log.Warn(errore);
answ = errore;
}
else
{
int newCounter = -1;
int.TryParse(counter, out newCounter);
// se il conteggio è >= 0 SALVO come nuovo conteggio...
if (newCounter >= 0)
{
var currKey = MP.Data.Utils.RedKeyPzCount(idxMacchina, MpIoNS);
RedisValue rawData = await _redisDb.StringGetAsync(currKey);
if (rawData.HasValue)
{
// salvo per + tempo...
await _redisDb.StringSetAsync(currKey, answ.ToString());
answ = counter;
}
else
{
int currCount = await PzCounterTcAsync(idxMacchina);
answ = currCount.ToString();
// salvo per meno tempo...
await _redisDb.StringSetAsync(currKey, answ);
}
}
}
}
return answ;
}
/// <inheritdoc />
public async Task ScriviKeepAliveAsync(string IdxMacchina, DateTime oraMacchina)
{
// cerco se ho keep alive in redis,
var currKey = MP.Data.Utils.RedKeyHash($"KeepAlive:{IdxMacchina}");
bool keyPresent = await _redisDb.KeyExistsAsync(currKey);
// se NON presente salvo in REDIS con TTL 10 sec e sul DB...
if (!keyPresent)
{
DateTime adesso = DateTime.Now;
await _redisDb.StringSetAsync(currKey, adesso.ToString("s"), TimeSpan.FromSeconds(30));
// effettuo scrittura sul DB
await _repo.KeepAliveUpsertAsync(IdxMacchina, DateTime.Now, oraMacchina);
}
}
#endregion Public Methods
#region Protected Fields
protected Random rand = new Random();
#endregion Protected Fields
#region Protected Methods
/// <summary>
/// Restituisce un timeout dai minuti richiesti + tempo random 1..60 sec
/// </summary>
/// <param name="stdMinutes"></param>
/// <returns></returns>
protected TimeSpan getRandTOut(double stdMinutes)
{
double rndValue = stdMinutes + (double)rand.Next(1, 60) / 60;
return TimeSpan.FromMinutes(rndValue);
}
#endregion Protected Methods
#region Private Fields
private static Logger Log = LogManager.GetCurrentClassLogger();
private readonly IFusionCache _cache;
private readonly string _className;
private readonly IIocRepository _repo;
private readonly IServiceScopeFactory _scopeFactory;
/// <summary>
/// Provider CultureInfo x parse valori(es dataora)
/// </summary>
private CultureInfo ciProvider = CultureInfo.InvariantCulture;
/// <summary>
/// Formato dataora standard x parsing
/// </summary>
private string dtFormat = "yyyyMMddHHmmssfff";
private int redisLongTimeCache = 5;
private int redisShortTimeCache = 2;
#endregion Private Fields
#region Private Methods
/// <summary>
/// controlla se da il segnale di "microstato" deriva un evento da generare - modalità OFFLINE
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="valore">valOut ingresso</param>
/// <param name="dtEve">data-ora evento (server)</param>
/// <param name="contatore">sequenza dati inviati</param>
/// <param name="datiMaccCache">dati macchina in cache (opzionale, se null fa lookup)</param>
/// <returns></returns>
private async Task<inputComandoMapo> CheckMicroStatoAsync(string idxMacchina, string valore, DateTime dtEve, string contatore, Dictionary<string, string>? datiMaccCache = null)
{
// recupero SE IMPIEGATO REDIS i valori del Dictionary della macchina...
Dictionary<string, string> datiMacc = datiMaccCache ?? await mDatiMacchineAsync(idxMacchina);
// processing
inputComandoMapo answ = new inputComandoMapo();
// SE no trovassi verifico se esista la macchina altrimenti la creo... REDIS compliant
if (!datiMacc.ContainsKey(idxMacchina))
{
await verificaIdxMacchinaAsync(idxMacchina);
}
// continuo processing...
string CodArticolo = datiMacc["CodArticolo"];
if (string.IsNullOrEmpty(CodArticolo))
{
var allDatiMacch = await _repo.DatiMacchineGetAllAsync();
var recMacc = allDatiMacch.FirstOrDefault(x => x.IdxMacchina == idxMacchina);
if (recMacc != null)
{
CodArticolo = recMacc.CodArticoloA;
}
}
// preparo gestione val ingresso
int? valINT = 0;
int idxTipoEv = 0;
int next_idxMS = 0;
try
{
valINT = int.Parse(valore, NumberStyles.HexNumber);
int idxFamIn = Convert.ToInt32(datiMacc["IdxFamIn"]);
int idxMicroStato = Convert.ToInt32(datiMacc["IdxMicroStato"]);
int valIOB = Convert.ToInt32(valINT);
next_idxMS = idxMicroStato;
// recupero singolo valOut (stringa) x chiave
string todoSMI = await ValoreSmiAsync(idxFamIn, idxMicroStato, valIOB);
// solo se ho trovato un risultato nella tab SMI della famiglia macchina...
if (!string.IsNullOrEmpty(todoSMI) && todoSMI.Contains("_"))
{
// splitto e salvo valori OUT...
string[] valori = todoSMI.Split('_');
idxTipoEv = Convert.ToInt32(valori[0]);
next_idxMS = Convert.ToInt32(valori[1]);
}
}
catch (Exception exc)
{
Log.Error($"[ChkMiSt_5a] - - Eccezione in recupero riga Trans ingressi | idxMacchina {idxMacchina} | valINT {valINT}:{Environment.NewLine}{exc}", Environment.NewLine, exc, valINT, idxMacchina);
}
// effettuo update vari SU DB!!!
MicroStatoMacchinaModel newRecMsm = new MicroStatoMacchinaModel()
{
IdxMacchina = idxMacchina,
IdxMicroStato = next_idxMS,
InizioStato = dtEve,
Value = valore
};
if (idxTipoEv > 0)
{
// preparo record
string valEsteso = string.Format("[{0}] {1}", contatore.PadLeft(3, '0'), valore);
// creo evento
EventListModel newRecEv = new EventListModel()
{
CodArticolo = CodArticolo,
IdxMacchina = idxMacchina,
IdxTipo = idxTipoEv,
InizioStato = dtEve,
MatrOpr = 0,
pallet = "-",
Value = valEsteso
};
// salva e processa evento + microstato
answ = await scriviRigaEventoAsync(newRecMsm, newRecEv);
}
else
{
await _repo.MicroStatoMacchinaUpsertAsync(newRecMsm);
}
return answ;
}
/// <summary>
/// Elenco completo config da DB
/// </summary>
private async Task<List<ConfigModel>> ConfigGetAllAsync()
{
return await GetOrFetchAsync("IOC_ConfigAll", async () =>
{
List<ConfigModel>? result = new List<ConfigModel>();
// cerco in redis...
RedisValue rawData = await _redisDb.StringGetAsync(MP.Data.Utils.redisConfKey);
string source = "DB";
if (!string.IsNullOrEmpty($"{rawData}"))
{
result = JsonConvert.DeserializeObject<List<ConfigModel>>($"{rawData}");
source = "REDIS";
}
else
{
result = await _repo.ConfigGetAllAsync();
rawData = JsonConvert.SerializeObject(result);
await _redisDb.StringSetAsync(MP.Data.Utils.redisConfKey, rawData, getRandTOut(redisLongTimeCache));
}
Log.Debug($"ConfigGetAllAsync Read from {source}");
if (result == null)
{
result = new List<ConfigModel>();
}
return result;
}, TimeSpan.FromMinutes(1));
}
/// <summary>
/// Helper standardizzazione valOut dataora ricevuto da IOB remoti
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
private string dtFormStd(string? s)
{
return s?.Length > 17 ? s[..17] : s?.PadRight(17, '0') ?? string.Empty;
}
/// <summary>
/// Recupero info ODL corrente da dati prod macchina
/// </summary>
/// <param name="idxMacchina"></param>
/// <returns></returns>
private async Task<string> GetCurrOdlByProdAsync(string idxMacchina)
{
string answ = "";
// recupero stato...
var datiProd = await StatoProdMacchinaAsync(idxMacchina, DateTime.Now);
if (datiProd != null)
{
answ = datiProd.IdxOdl.ToString();
}
// ultimo controllo su idxOdl...
answ = answ == "" ? "0" : answ;
// restituisco!
return answ;
}
/// <summary>
/// Implementa gestione recupero cache da memoria o da obj esterno + cache memoria
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="cacheKey"></param>
/// <param name="fetchFunc"></param>
/// <param name="expiration"></param>
/// <returns></returns>
private async Task<T> GetOrFetchAsync<T>(string cacheKey, Func<Task<T>> fetchFunc, TimeSpan expiration)
{
// GetOrSetAsync di Fusion cache:
// - TryGetValue, se mancasse crea un lock solo per QUELLA key
// - Esegue fetchFunc (la logica Redis + DB)
// - Salva in memoria e rilascia il lock
return await _cache.GetOrSetAsync<T>(
cacheKey,
async ct => await fetchFunc(),
options => options.SetDuration(expiration)
);
}
/// <summary>
/// Restituisce il valOut booleano se la macchina sia abilitata all'inserimento COMPLETO nel
/// Signal Log
/// </summary>
/// <param name="idxMacchina"></param>
/// <returns></returns>
private async Task<bool> IobSLogEnabAsync(string idxMacchina)
{
bool answ = false;
// ORA recupero da memoria redis...
try
{
var currHash = MP.Data.Utils.RedKeyDatiMacc(idxMacchina, MpIoNS);
RedisValue rawData = await _redisDb.HashGetAsync(currHash, (RedisValue)"sLogEnabled");
// se è vuoto... leggo da DB e popolo!
if (!rawData.HasValue)
{
await ResetDatiMacchinaAsync(idxMacchina);
// riprovo
rawData = await _redisDb.HashGetAsync(currHash, (RedisValue)"sLogEnabled");
}
// provo conversione
bool.TryParse($"{rawData}", out answ);
}
catch (Exception exc)
{
Log.Error($"Errore IobSLogEnabAsync | idxMacchina {idxMacchina}:{Environment.NewLine}{exc}");
}
return answ;
}
/// <summary>
/// Restituisce il valOut booleano se la macchina sia di tipo MULTI (con più state machine x INGRESSI)
/// usando dati macchina in cache per evitare lookup ridondanti
/// </summary>
/// <param name="idxMacchina"></param>
/// <param name="datiMacc"></param>
/// <returns></returns>
private bool isMulti(string idxMacchina, Dictionary<string, string> datiMacc)
{
bool answ = false;
try
{
answ = Convert.ToBoolean(datiMacc.ContainsKey("Multi") && datiMacc["Multi"] == "1");
}
catch (Exception exc)
{
Log.Error($"Eccezione in isMulti{Environment.NewLine}{exc}");
}
return answ;
}
private async Task<HashSet<string>> ListMasterAsync()
{
return await GetOrFetchAsync("IOC_ListMaster", async () =>
{
HashSet<string> result = new();
string currKey = $"{MP.Data.Utils.redisBaseAddr}:ListMaster";
RedisValue rawData = await _redisDb.StringGetAsync(currKey);
if (rawData.HasValue)
{
result = JsonConvert.DeserializeObject<HashSet<string>>($"{rawData}") ?? new();
}
else
{
var fullList = await Macchine2SlaveGetAllAsync();
result = fullList.Select(x => x.IdxMacchina).ToHashSet<string>();
rawData = JsonConvert.SerializeObject(result);
await _redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache * 10));
}
return result;
}, TimeSpan.FromMinutes(15));
}
private async Task<HashSet<string>> ListSlaveAsync()
{
return await GetOrFetchAsync("IOC_ListSlave", async () =>
{
HashSet<string> result = new();
string currKey = $"{MP.Data.Utils.redisBaseAddr}:ListSlave";
RedisValue rawData = await _redisDb.StringGetAsync(currKey);
if (rawData.HasValue)
{
result = JsonConvert.DeserializeObject<HashSet<string>>($"{rawData}") ?? new();
}
else
{
var fullList = await Macchine2SlaveGetAllAsync();
result = fullList.Select(x => x.IdxMacchinaSlave).Distinct().ToHashSet<string>();
rawData = JsonConvert.SerializeObject(result);
await _redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache * 10));
}
return result;
}, TimeSpan.FromMinutes(15));
}
/// <summary>
/// Elenco completo valori Macchine 2 Slave
/// </summary>
/// <returns></returns>
private async Task<List<Macchine2SlaveModel>> Macchine2SlaveGetAllAsync()
{
return await GetOrFetchAsync("IOC_M2SlaveGetAll", async () =>
{
List<Macchine2SlaveModel>? result = new List<Macchine2SlaveModel>();
string currKey = $"{MP.Data.Utils.redisBaseAddr}:M2STab";
// cerco in redis dato valOut sel macchina...
RedisValue rawData = await _redisDb.StringGetAsync(currKey);
if (rawData.HasValue)
{
result = JsonConvert.DeserializeObject<List<Macchine2SlaveModel>>($"{rawData}");
}
else
{
result = await _repo.Macchine2SlaveAsync();
// serializzo e salvo...
rawData = JsonConvert.SerializeObject(result);
await _redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache * 10));
}
if (result == null)
{
result = new List<Macchine2SlaveModel>();
}
return result;
}, TimeSpan.FromMinutes(30));
}
/// <summary>
/// Restitusice elenco KVP dei campi DatiMacchine + StatoMacchine per l'impianto indicato
/// </summary>
/// <param name="idxMacchina"></param>
/// <returns></returns>
private async Task<Dictionary<string, string>> mDatiMacchineAsync(string idxMacchina)
{
// hard coded dimensione vettore DatiMacchine
Dictionary<string, string> answ = new Dictionary<string, string>();
// ORA recupero da memoria redis...
try
{
var currHash = MP.Data.Utils.RedKeyDatiMacc(idxMacchina, MpIoNS);
answ = await RedisGetHashDictAsync(currHash);
// se è vuoto... leggo da DB e popolo!
if (answ.Count == 0)
{
answ = await ResetDatiMacchinaAsync(idxMacchina);
}
}
catch (Exception exc)
{
Log.Error($"Errore in compilazione dati Macchine x Redis - idxMacchina {idxMacchina}:{Environment.NewLine}{exc}");
}
return answ;
}
/// <summary>
/// Restitusice elenco KVP (async) per evitare blocchi quando chiamato da metodi asincroni
/// </summary>
/// <param name="idxMacchina"></param>
/// <returns></returns>
private async Task<KeyValuePair<string, string>[]> mTabMSMIAsync(string idxMacchina)
{
KeyValuePair<string, string>[] answ = new KeyValuePair<string, string>[1];
answ[0] = new KeyValuePair<string, string>("0", "0");
try
{
var currHash = MP.Data.Utils.RedKeyMsmi(idxMacchina);
answ = await RedisGetHashAsync(currHash);
if (answ == null || answ.Length == 0)
{
answ = await resetMSMIAsync(idxMacchina);
}
}
catch (Exception exc)
{
Log.Error($"Errore in compilazione Tabella Multi State Machine Ingressi x Redis:{Environment.NewLine}{exc}");
}
return answ;
}
/// <summary>
/// Calcola dataora evento da info dt ricevute
/// </summary>
/// <param name="dtEve"></param>
/// <param name="dtCurr"></param>
/// <returns></returns>
private DateTime ParseEventTime(string dtEve, string dtCurr)
{
DateTime dataOraEvento = DateTime.Now;
// fix formato dataora in ingresso
string stdEve = dtFormStd(dtEve);
string stdCurr = dtFormStd(dtCurr);
// 2. Se le stringhe normalizzate coincidono, skip dei calcoli
if (stdEve != stdCurr)
{
// 3. Parsing sicuro
if (DateTime.TryParseExact(stdEve, dtFormat, ciProvider, DateTimeStyles.None, out DateTime dtEvento) &&
DateTime.TryParseExact(stdCurr, dtFormat, ciProvider, DateTimeStyles.None, out DateTime dtCorrente))
{
TimeSpan diff = dtCorrente - dtEvento;
double ms = Math.Abs(diff.TotalMilliseconds);
// 4. Classificazione delta con switch expression (più leggibile e performante)
string deltaClass = ms switch
{
<= 10 => "0-10 ms",
<= 100 => "10-100 ms",
<= 1000 => "100-1000 ms",
<= 10000 => "1-10 sec",
_ => "> 10 sec"
};
Log.Debug($"Correzione delta {deltaClass}");
// 5. Correzione data/ora server: sottrao lo scarto calcolato
dataOraEvento = dataOraEvento.Subtract(diff);
}
else
{
Log.Error($"Errore parsing date: {stdEve} | {stdCurr}");
}
}
return dataOraEvento;
}
/// <summary>
/// Calcola l'effettivo valOut da passare alla macchina a stati INGRESSI usando dati macchina in cache
/// </summary>
/// <param name="idxMacchina"></param>
/// <param name="valore"></param>
/// <param name="datiMacc"></param>
/// <returns></returns>
private string preProcInput(string idxMacchina, string valore, Dictionary<string, string> datiMacc)
{
string newVal = "";
try
{
// variabili
int valINT = 0;
int BitFilt = 0;
int BSR = 0;
bool ExplodeBit = false;
int NumBit = 0;
int newValInt = 0;
// recupero parametri dalla cache...
int.TryParse(datiMacc.ContainsKey("BitFilt") ? datiMacc["BitFilt"] : "0", out BitFilt);
int.TryParse(datiMacc.ContainsKey("BSR") ? datiMacc["BSR"] : "0", out BSR);
Boolean.TryParse(datiMacc.ContainsKey("ExplodeBit") ? datiMacc["ExplodeBit"] : "false", out ExplodeBit);
// non usato (x ora)
int.TryParse(datiMacc.ContainsKey("NumBit") ? datiMacc["NumBit"] : "0", out NumBit);
// recupero valOut
valINT = int.Parse(valore, NumberStyles.HexNumber);
// filtro
newValInt = MP.Core.Utils.bMaskInt(valINT, BitFilt);
// effettuo eventuale BitShiftRight
if (BSR > 0)
{
newValInt = newValInt >> BSR;
}
// effettuo eventuale esplosione in BIT esclusivi
if (ExplodeBit)
{
newValInt = Convert.ToInt32(1 << newValInt);
}
// riconverto a STRING HEX!!!
newVal = newValInt.ToString("X");
}
catch
{
newVal = valore;
}
return newVal;
}
private async Task<KeyValuePair<string, string>[]> RedisGetHashAsync(RedisKey redKey)
{
HashEntry[] rawData = await _redisDb.HashGetAllAsync(redKey);
var result = rawData.Where(x => !x.Name.IsNull).Select(x => new KeyValuePair<string, string>($"{x.Name}", $"{x.Value}")).ToArray();
return result;
}
private async Task<Dictionary<string, string>> RedisGetHashDictAsync(RedisKey hashKey)
{
HashEntry[] rawData = await _redisDb.HashGetAllAsync(hashKey);
var result = rawData
.Where(x => !x.Name.IsNull)
.ToDictionary(x => x.Name.ToString(), x => x.Value.ToString());
return result;
}
private async Task RedisSetHashAsync(RedisKey redKey, KeyValuePair<string, string>[] valori, double expireSeconds = -1.0)
{
HashEntry[] redHash = valori.Select(x => new HashEntry(x.Key, x.Value)).ToArray();
await _redisDb.HashSetAsync(redKey, redHash);
if (expireSeconds > 0.0)
{
await _redisDb.KeyExpireAsync(redKey, DateTime.Now.AddSeconds(expireSeconds));
}
}
/// <summary>
/// Restitusice elenco KVP dei campi DatiMacchine + StatoMacchine per l'impianto indicato
/// </summary>
/// <param name="idxMacc"></param>
/// <returns></returns>
private async Task<Dictionary<string, string>> ResetDatiMacchinaAsync(string idxMacc)
{
Dictionary<string, string> result = new Dictionary<string, string>();
VMSFDModel? dbResult = null;
dbResult = await _repo.VMSFDGetByMaccAsync(idxMacc);
if (dbResult == null) return new Dictionary<string, string>();
double numSecCache = redisLongTimeCache;
// converto in formato dizionario...
if (dbResult != null)
{
// salvo 1:1 i valori... STATO
result.Add("IdxMicroStato", $"{dbResult.IdxMicroStato}");
result.Add("IdxStato", $"{dbResult.IdxStato}");
result.Add("CodArticolo", $"{dbResult.CodArticolo}");
result.Add("insEnabled", $"{dbResult.InsEnabled}");
result.Add("sLogEnabled", $"{dbResult.SLogEnabled}");
result.Add("pallet", $"{dbResult.Pallet}");
result.Add("CodArticolo_A", $"{dbResult.CodArticoloA}");
result.Add("CodArticolo_B", $"{dbResult.CodArticoloB}");
result.Add("TempoCicloBase", $"{dbResult.TempoCicloBase}");
result.Add("PzPalletProd", $"{dbResult.PzPalletProd}");
result.Add("MatrOpr", $"{dbResult.MatrOpr}");
result.Add("lastVal", $"{dbResult.LastVal}");
result.Add("TCBase", $"{dbResult.TempoCicloBase}");
//...e SETUP
result.Add("CodMacc", $"{dbResult.Codmacchina}");
result.Add("IdxFamIn", $"{dbResult.IdxFamigliaIngresso}");
result.Add("Multi", $"{dbResult.Multi}");
result.Add("BitFilt", $"{dbResult.BitFilt}");
result.Add("MaxVal", $"{dbResult.MaxVal}");
result.Add("BSR", $"{dbResult.Bsr}");
result.Add("ExplodeBit", $"{dbResult.ExplodeBit}");
result.Add("NumBit", $"{dbResult.NumBit}");
result.Add("IdxFamMacc", $"{dbResult.IdxFamiglia}");
result.Add("simplePallet", $"{dbResult.SimplePallet}");
result.Add("palletChange", $"{dbResult.PalletChange}");
// durata cache in secondi dal valOut insEnabled...
//double numSecCache = ((result["insEnabled"].ToLower() == "true") ? redisShortTimeCache : redisLongTimeCache);
numSecCache = dbResult.InsEnabled ? redisShortTimeCache : redisLongTimeCache;
}
// dati master/slave
string isMaster = (await ListMasterAsync()).Contains(idxMacc) ? "1" : "0";
string isSlave = (await ListSlaveAsync()).Contains(idxMacc) ? "1" : "0";
result.Add("Master", isMaster);
result.Add("Slave", isSlave);
// Processing redis in transazoine...
var redKey = MP.Data.Utils.RedKeyDatiMacc(idxMacc, MpIoNS);
// variazione con redis transaction...
var transaction = _redisDb.CreateTransaction();
// 1. Eliminiamo la chiave (rimuove i vecchi campi)
_ = transaction.KeyDeleteAsync(redKey);
// 2. Prepariamo gli HashEntry per il batch (più efficiente di un Dictionary)
HashEntry[] entries = result.Select(kvp => new HashEntry(kvp.Key, kvp.Value)).ToArray();
// 3. Inseriamo i nuovi valori
_ = transaction.HashSetAsync(redKey, entries);
// 4. Impostiamo l'expiration in un unico colpo
_ = transaction.KeyExpireAsync(redKey, TimeSpan.FromSeconds(numSecCache));
// Eseguiamo tutto in un unico viaggio verso Redis
bool success = await transaction.ExecuteAsync();
return result;
}
/// <summary>
/// Resetta (rileggendo) i dati della State Machine multi ingressi nel formato
/// currKey: IdxMacchina
/// value: IdxFamigliaIngresso
/// </summary>
/// <param name="idxMacchina"></param>
/// <returns></returns>
private async Task<KeyValuePair<string, string>[]> resetMSMIAsync(string idxMacchina)
{
var currHash = MP.Data.Utils.RedKeyMsmi(idxMacchina);
// recupero records
var tabMSMI = await _repo.VMSFDGetMultiByMaccAsync(idxMacchina);
KeyValuePair<string, string>[] answ = new KeyValuePair<string, string>[tabMSMI.Count];
// salvo tutti i valori StateMachineIngressi...
int i = 0;
foreach (var item in tabMSMI)
{
answ[i] = new KeyValuePair<string, string>(item.IdxMacchina, item.IdxFamigliaIngresso.ToString());
i++;
}
// verifico il timeout (default 60 sec...)
var sTOutSmi = await tryGetConfigAsync("TmOut.MSMI");
int tOut = 60;
int.TryParse(sTOutSmi, out tOut);
tOut = tOut <= 60 ? 60 : tOut;
// salvo in redis!
await RedisSetHashAsync(currHash, answ, tOut);
return answ;
}
/// <summary>
/// Resetta (rileggendo) i dati della State Machine ingressi nel formato
/// currKey: cState_nVal (current MICRO-STATE + "_" + new Value)
/// value: iTipoEv_nState (IdxTipoEv da trasmettere + New MICRO-STATE)
/// </summary>
/// <param name="idxFamIn"></param>
/// <returns></returns>
private async Task<KeyValuePair<string, string>[]> resetSMIAsync(int idxFamIn)
{
var currHash = MP.Data.Utils.GetHashSMI(idxFamIn);
// leggo da DB...
var tabSMI = await _repo.StateMachineIngressiAsync(idxFamIn);
KeyValuePair<string, string>[] answ = new KeyValuePair<string, string>[tabSMI.Count];
// salvo tutti i valori StateMachineIngressi...
int i = 0;
string key = "";
string val = "";
foreach (var item in tabSMI)
{
key = string.Format("{0}_{1}", item.IdxMicroStato, item.ValoreIngresso);
val = string.Format("{0}_{1}", item.IdxTipoEvento, item.NextIdxMicroStato);
answ[i] = new KeyValuePair<string, string>(key, val);
i++;
}
// verifico il timeout (default 60 sec...)
int tOut = 300;
var sTOutSmi = await tryGetConfigAsync("TmOut.SMI");
if (!string.IsNullOrEmpty(sTOutSmi))
{
int.TryParse(sTOutSmi, out tOut);
}
// salvo in redis!
await RedisSetHashAsync(currHash, answ, tOut);
return answ;
}
/// <summary>
/// salva il segnale di "microstato" (segnale) ASYNC
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="valore">valOut ingresso</param>
/// <param name="dtEve">data-ora evento (server)</param>
/// <param name="contatore">contatore sequenza dati inviati</param>
/// <returns></returns>
private async Task<bool> saveSigLogAsync(string idxMacchina, string valore, DateTime dtEve, int contatore)
{
SignalLogModel newRec = new SignalLogModel()
{
IdxMacchina = idxMacchina,
DtCurr = DateTime.Now,
DtEve = dtEve,
Contatore = contatore,
Valore = valore
};
return await _repo.SignalLogInsertAsync(newRec);
}
/// <summary>
/// Scrive una riga evento + una riga microstato insieme, ed effettua verifica necessità cambio stato
/// </summary>
/// <param name="newRecMsm">Record MicroStatoMacchina</param>
/// <param name="newRecEv">record EventList</param>
/// <returns></returns>
private async Task<inputComandoMapo> scriviRigaEventoAsync(MicroStatoMacchinaModel newRecMsm, EventListModel newRecEv)
{
bool inserito = false;
// inserisco evento
inserito = await _repo.EvListMicroStatoInsertAsync(newRecMsm, newRecEv);
// faccio controllo per eventuale cambio stato da tab transizioni...
await _repo.CheckCambiaStatoBatchAsync(tipoInputEvento.hw, newRecEv.IdxMacchina, newRecEv.InizioStato ?? DateTime.Now, newRecEv.IdxTipo, newRecEv.CodArticolo, newRecEv.Value, newRecEv.MatrOpr, newRecEv.pallet);
// formatto output
inputComandoMapo answ = new inputComandoMapo();
answ.outValue = inserito.ToString();
answ.needStatusRefresh = true;
return answ;
}
/// <summary>
/// Restitusice elenco KVP dei campi della State Machine ingressi nel formato
/// currKey: cState_nVal (current MICRO-STATE + "_" + new Value)
/// value: iTipoEv_nState (IdxTipoEv da trasmettere + New MICRO-STATE
/// </summary>
/// <param name="idxFamIn"></param>
/// <returns></returns>
private async Task<KeyValuePair<string, string>[]> StateMachInByKeyAsync(int idxFamIn)
{
// hard coded dimensione vettore DatiMacchine
KeyValuePair<string, string>[] answ = new KeyValuePair<string, string>[1];
// iniziualizzo con un valOut... 0/0
answ[0] = new KeyValuePair<string, string>("0", "0");
// ORA recupero da memoria redis...
try
{
var currHash = MP.Data.Utils.GetHashSMI(idxFamIn);
answ = await RedisGetHashAsync(currHash);
// se è vuoto... leggo da DB e popolo!
if (answ.Length == 0)
{
answ = await resetSMIAsync(idxFamIn);
}
}
catch (Exception exc)
{
Log.Error($"Errore in compilazione State Machine Ingressi x Redis:{Environment.NewLine}{exc}");
}
return answ;
}
/// <summary>
/// Stato prod macchina (completo)
/// </summary>
/// <param name="idxMacchina"></param>
/// <param name="dtReq"></param>
/// <returns></returns>
private async Task<StatoProdModel> StatoProdMacchinaAsync(string idxMacchina, DateTime dtReq, bool forceDb = false)
{
string cacheKey = $"IOC_StatoProd_{idxMacchina}";
return await GetOrFetchAsync(cacheKey, async () =>
{
StatoProdModel? result = new StatoProdModel();
// cerco in _redisConn...
string currKey = $"{MP.Data.Utils.redisStatoProd}:{idxMacchina}:{dtReq:HHmm}";
RedisValue rawData = await _redisDb.StringGetAsync(currKey);
if (rawData.HasValue && !forceDb)
{
result = JsonConvert.DeserializeObject<StatoProdModel>($"{rawData}");
}
else
{
result = await _repo.StatoProdMacchinaAsync(idxMacchina, dtReq);
// serializzo e salvo...
rawData = JsonConvert.SerializeObject(result);
await _redisDb.StringSetAsync(currKey, rawData, TimeSpan.FromSeconds(30));
}
if (result == null)
{
result = new StatoProdModel();
}
return result;
}, TimeSpan.FromSeconds(3));
}
/// <summary>
/// Restituisce valOut della stringa (SE disponibile)
/// </summary>
/// <param name="keyName"></param>
/// <returns></returns>
private async Task<string> tryGetConfigAsync(string keyName)
{
string answ = "";
// preselezione valori
var configData = await ConfigGetAllAsync();
var currRec = configData.FirstOrDefault(x => x.Chiave == keyName);
if (currRec != null)
{
answ = currRec.Valore;
}
return answ;
}
/// <summary>
/// Validazione preliminare valori input
/// </summary>
/// <param name="idxMacchina"></param>
/// <param name="valore"></param>
/// <param name="dtEve"></param>
/// <param name="dtCurr"></param>
/// <returns></returns>
private bool ValidateInputParams(string idxMacchina, string valore, string dtEve, string dtCurr)
{
bool isValid = false;
// preparo stringa valori correnti
string currVals = $"idxMacchina: {idxMacchina} | valOut: {valore} | dtEve: {dtEve} | dtCurr:{dtCurr}";
if (dtEve == null || dtCurr == null)
{
Log.Warn($"procInput: null found | {currVals}");
}
else if (dtEve.Length < 17 || dtCurr.Length < 17)
{
Log.Info($"procInput: invalid data | {currVals}");
}
else if (string.IsNullOrEmpty(idxMacchina))
{
Log.Info($"procInput: missing IdxMacchina | {currVals}");
}
else if (string.IsNullOrEmpty(valore))
{
Log.Info($"procInput: missing valOut | {currVals}");
}
else
{
isValid = true;
}
return isValid;
}
/// <summary>
/// Restituisce il valore SPECIFICATO per la state machine ingressi
/// value: iTipoEv_nState (IdxTipoEv da trasmettere + New MICRO-STATE)
/// </summary>
/// <param name="idxFamIn"></param>
/// <param name="idxMicroStato"></param>
/// <param name="valoreIn"></param>
/// <returns></returns>
private async Task<string> ValoreSmiAsync(int idxFamIn, int idxMicroStato, int valoreIn)
{
string valOut = "";
var currHash = MP.Data.Utils.GetHashSMI(idxFamIn);
string field = $"{idxMicroStato}_{valoreIn}";
var searchVal = await _redisDb.HashGetAsync(currHash, field);
if (!searchVal.HasValue)
{
// ricarico tabella (salvando in redis x ricerca successiva)!
var valori = await StateMachInByKeyAsync(idxFamIn);
if (valori.Any(x => x.Key == field))
{
searchVal = valori.FirstOrDefault(x => x.Key == field).Value;
}
}
valOut = $"{searchVal}";
return valOut;
}
/// <summary>
/// cerca codice in anagrafica macchine ed eventualmente inserisce nuova macchina
/// </summary>
/// <param name="IdxMacchina"></param>
private async Task verificaIdxMacchinaAsync(string IdxMacchina)
{
// esecuzione in REDIS...cerco status macchina...
if ((await mDatiMacchineAsync(IdxMacchina)).Count == 0)
{
// verifico se esiste su DB
var dbRec = await _repo.MacchineGetByIdxAsync(IdxMacchina);
if (dbRec == null)
{
MacchineModel newRec = new MacchineModel()
{
IdxMacchina = IdxMacchina,
CodMacchina = "0000",
Nome = IdxMacchina,
Descrizione = "Macchina non codificata",
Note = "-",
locazione = "",
RecipeArchivePath = "",
RecipePath = ""
};
await _repo.MacchineUpsertAsync(newRec);
// verifico ci sia un microstato macchina...
var recMSM = await _repo.MicroStatoMacchinaGetByIdxMaccAsync(IdxMacchina);
if (recMSM.Count == 0)
{
// inserisco nuovo stato...
MicroStatoMacchinaModel msRec = new MicroStatoMacchinaModel()
{
IdxMacchina = IdxMacchina,
IdxMicroStato = 0,
InizioStato = DateTime.Now,
Value = "00"
};
await _repo.MicroStatoMacchinaUpsertAsync(msRec);
}
}
}
}
#endregion Private Methods
}
}