Files
Mapo-IOB-WIN/IOB-WIN-FORM/Iob/Generic.Protected.cs
T
Samuele Locatelli fa976dd2bb Update IOB BASE:
- aggiunto InitializeAsync
- gestione override x ogni IOB
2026-01-23 10:22:00 +01:00

5987 lines
232 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using EgwProxy.Ftp;
using IOB_UT_NEXT;
using MapoSDK;
using Newtonsoft.Json;
using NLog;
using NLog.Targets;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Serialization;
using static IOB_UT_NEXT.BaseAlarmConf;
using static IOB_UT_NEXT.CustomObj;
using static IOB_UT_NEXT.DataModel.Fimat;
using static MapoSDK.WharehouseData;
namespace IOB_WIN_FORM.Iob
{
public partial class Generic : BaseObj
{
#region Protected Fields
protected bool _connOk = false;
/// <summary>
/// valore booleano di check se sia in fase di COMUNICAZIONE ATTIVA con il PLC/NC
/// </summary>
protected bool adpCommAct;
/// <summary>
/// porta x adapter (x restart)
/// </summary>
protected int adpPortNum;
/// <summary>
/// DataOra ultimo avvio adapter x watchdog
/// </summary>
protected DateTime adpStartRun;
/// <summary>
/// Vettore 32 BIT valori in ingresso al filtro
/// </summary>
protected int B_input;
/// <summary>
/// Vettore 32 BIT valori in uscita dal filtro
/// </summary>
protected int B_output;
#if false
/// <summary>
/// Ultimo valore B_output inviato
/// </summary>
protected int B_output_sent = -1;
#endif
/// <summary>
/// Vettore 32 BIT valori precedenti
/// </summary>
protected int B_previous = -1;
/// <summary>
/// Cod grupo IOB x creazione PODL al volo
/// </summary>
protected string CodGruppoIob = "ND-00";
/// <summary>
/// Num errori check alive
/// </summary>
protected int currAliveErrors = 0;
/// <summary>
/// Dizionario valori impostati x produzione
/// </summary>
protected Dictionary<string, string> currProdData = new Dictionary<string, string>();
/// <summary>
/// num corrente errori read PLC
/// </summary>
protected int currReadErrors = 0;
/// <summary>
/// num errori send
/// </summary>
protected int currSendErrors = 0;
/// <summary>
/// Tempo di attesa in minuti x lettura contapezzi standard (da .ini / OptPar)
/// </summary>
protected double delayMinReadPzCount = 0;
/// <summary>
/// Fattore di demoltiplicazione dei DynData x ridurre campionamento
/// </summary>
protected int demFactDynData = 1;
/// <summary>
/// Boolean x indicare contapezzi disabilitato forzatamente da IOB
/// </summary>
protected bool disablePzCountByIob = false;
/// <summary>
/// Veto x esecuzione task AutoDossier
/// </summary>
protected DateTime dtVetoAutoDossier = DateTime.Now;
/// <summary>
/// Veto x lettura contapezzi (in caso di autoODL con attesa reset ad esempio...)
/// </summary>
protected DateTime dtVetoReadPzCount = DateTime.Now;
/// <summary>
/// Determina se sia prevista gestione PODL (creazione/avvio/chiusura) come Soitaab
/// </summary>
protected bool EnabelPodlManFull = false;
/// <summary>
/// Abilita riscrittura memoria se trova differenze lettura/richiesti
/// </summary>
protected bool ENABLE_MEM_REWRITE = true;
/// <summary>
/// Abilitazione restart (da opt par...)
/// </summary>
protected bool enableCliRestart = false;
/// <summary>
/// Boolean x indicare contapezzi abilitato a livello di conf applicazione
/// </summary>
protected bool enablePzCountByApp = true;
/// <summary>
/// Abilitazione gestione slow data
/// </summary>
protected bool enableSlowData = false;
/// <summary>
/// dizionario (opzionale) xz decodifica file da importare
/// </summary>
protected Dictionary<string, int> FileDecod = new Dictionary<string, int>();
/// <summary>
/// DeadBand x riduzione dati FluxLog (se 0 non gestita)
/// </summary>
protected double fluxLogRedDeadBand = 0;
/// <summary>
/// Determina se sia gestita riduzione dati FluxLog
/// </summary>
protected bool fluxLogReduce = false;
/// <summary>
/// Dizionario dei valori FluxLog ultimi verificati x veto
/// </summary>
protected Dictionary<string, double> fluxLogReduceLast = new Dictionary<string, double>();
/// <summary>
/// Dizionario dei valori FluxLog ultimi tipo STRING verificati x veto
/// </summary>
protected Dictionary<string, string> fluxLogReduceLastString = new Dictionary<string, string>();
/// <summary>
/// Dizionario dei veto send x ogni variabile quando non variata
/// </summary>
protected Dictionary<string, DateTime> fluxLogReduceVeto = new Dictionary<string, DateTime>();
/// <summary>
/// Finestra in minuti x invio dati FluxLog quando invariati
/// </summary>
protected int fluxLogResendPeriod = 60;
/// <summary>
/// indica che è richiesto forzatamente reset contapezzi
/// </summary>
protected bool forcePzReset = false;
/// <summary>
/// indica che è richiesto forzatamente reset contapezzi
/// </summary>
protected DateTime forcePzResetUntil = DateTime.Now.AddMinutes(-1);
/// <summary>
/// DEADBAND globale se impostata (es x gestire variazioni minime valori FluxLog da inviare...)
/// </summary>
protected float GLOBAL_DBAND = 0;
/// <summary>
/// Determina se siano gestite le ricette
/// </summary>
protected bool hasRecipe = false;
/// <summary>
/// Array dei contatori x segnali blinking
/// </summary>
protected int[] i_counters;
/// <summary>
/// Indica impianto IN SETUP (fino a quando SMETTE di esserlo...)
/// </summary>
protected bool inSetup = false;
/// <summary>
/// ultimo tentativo connessione...
/// </summary>
protected DateTime lastConnectTry;
/// <summary>
/// Dizionario ULTIMI valori impostati x produzione
/// </summary>
protected Dictionary<string, string> lastProdData = new Dictionary<string, string>();
/// <summary>
/// Ultimo invio contapezzi (x invio delayed)
/// </summary>
protected DateTime lastPzCountSend;
/// <summary>
/// Dizionario ultimi valori (string) delle TSS
/// </summary>
protected Dictionary<string, string> LastTSS = new Dictionary<string, string>();
/// <summary>
/// Dizionario ultimi valori (string) delle TSS
/// </summary>
protected Dictionary<string, DateTime> LastTSSSend = new Dictionary<string, DateTime>();
/// <summary>
/// Dizionario ultimi valori (double) delle TSVC
/// </summary>
protected Dictionary<string, double> LastTSVC = new Dictionary<string, double>();
/// <summary>
/// Ultima registrazione warning x ODL mancante (x scrivere solo ogni 15 secondi)
/// </summary>
protected DateTime lastWarnODL;
/// <summary>
/// ultima data-ora invio parametri write x ribadire status
/// </summary>
protected DateTime lastWriteParamsUpsert = DateTime.Now;
/// <summary>
/// Separatore linea (tipicamente x commenti)
/// </summary>
protected string lineSep = "--------------------------";
/// <summary>
/// Elenco parametri calcolati da inviare alla macchina (es ricetta con traduzione, RAMA/TFT)
/// </summary>
protected List<objItem> list2Write = new List<objItem>();
/// <summary>
/// Num massimo di errori in funzionalità check alive
/// </summary>
protected int maxAliveErrors = utils.CRI("maxAliveErrors");
/// <summary>
/// Soglia massima errori prima della disconnessione automatica in check
/// </summary>
protected int maxErroriCheck = utils.CRI("maxErroriCheck");
/// <summary>
/// Quantità massima per singola ricetta (per determinare num ricette da inviare)
/// </summary>
protected int maxQtyPerFile = 0;
/// <summary>
/// Num massimo di errori di rete read (dal PLC)
/// </summary>
protected int maxReadErrors = utils.CRI("maxReadErrors");
/// <summary>
/// Periodo massimo (in sec) per letture dati in mancanza di eventi di aggiornamento
/// </summary>
protected int MaxSecReload = 120;
/// <summary>
/// Num massimodi errori di rete send al server
/// </summary>
protected int maxSendErrors = utils.CRI("maxSendErrors");
/// <summary>
/// Numero massimo di tentativi x test ping preliminare
/// </summary>
protected int maxTryPing = 1;
protected string mem2trace = "";
/// <summary>
/// indica se serva refresh parametri e quindi PLC...
/// </summary>
protected bool needRefresh = true;
/// <summary>
/// Indica se usare la parte numerica di un articolo come codice INT
/// </summary>
protected bool numArtCharTrim = false;
/// <summary>
/// Variabile numero errori vari (in lettura) --&gt; se supera soglia maxErroriCheck --&gt; disconnette
/// </summary>
protected int numErroriCheck = 0;
/// <summary>
/// Timeout x ping al server
/// </summary>
protected int pingServerMsTimeout = utils.CRI("PingMsTimeout");
/// <summary>
/// DataOra avvio Programma x check avvio
/// </summary>
protected DateTime prgStarted = DateTime.Now;
/// <summary>
/// Ritardo minimo x invio contapezzi
/// </summary>
protected int pzCountDelay = 2500;
/// <summary>
/// Periodo di campionamento x refresh info
/// </summary>
protected int samplePeriod = 1000;
/// <summary>
/// MsSample Period base x campionamenti tpo OCP-UA
/// </summary>
protected int samplePeriodBase = 600;
/// <summary>
/// Dizionario di VC da trattare come TimeSeries (con conf decodificata + processing successivo...)
/// </summary>
protected Dictionary<string, VCData> TSVC_Data = new Dictionary<string, VCData>();
/// <summary>
/// Indica se usare archivio locale ricette vs scarico http/REST
/// </summary>
protected bool useLocalRecipe = true;
/// <summary>
/// Dizionario di VARIABILI da trattare come eventi (da inviare quando cambiano oppure a
/// scadenza periodo...)
/// </summary>
protected Dictionary<string, EVData> VarArray = new Dictionary<string, EVData>();
/// <summary>
/// Dizionario dei divieti di invio x ogni counter gestito
/// </summary>
protected Dictionary<string, DateTime> VetoCounterSend = new Dictionary<string, DateTime>();
/// <summary>
/// Durata in secondi del divieto accodamento segnali IN alla fase di startup
/// </summary>
protected int vetoQueueIn = 10;
/// <summary>
/// Periodo di veto prima di invio nuova conferma dei valori write correnti, default ogni 5 min
/// </summary>
protected double vetoSendWriteUpsert = 1;
/// <summary>
/// Periodo wathdog di default (2 sec se non specificato)
/// </summary>
protected int watchDogPeriod = 2;
#endregion Protected Fields
#region Protected Properties
/// <summary>
/// Valore del num max invii consecutivi da coda...
/// </summary>
protected static int nMaxSend
{
get
{
int answ = 5;
try
{
answ = utils.CRI("nMaxSend");
}
catch
{ }
return answ;
}
}
/// <summary>
/// Indica il counter della keyReq richiesta attiva
/// - gestito tramite Redis
/// - a scadenza 25h
/// - incrementato ogni invio di auto ODL
/// </summary>
protected int countKeyRichiesta
{
get
{
// calcolo keyReq odierna
int answ = redisMan.getKReqCount(dailyKey);
return answ;
}
set
{
// calcolo keyReq odierna
redisMan.setKReqCount(dailyKey, value);
}
}
protected string dailyKey
{
get
{
return DateTime.Today.ToString("yyMMdd");
}
}
/// <summary>
/// Dizionario delle deadband attive
/// </summary>
protected Dictionary<string, float> dictActDBand { get; set; } = new Dictionary<string, float>();
protected Dictionary<string, string> DictNumArt { get; set; } = new Dictionary<string, string>();
protected bool disDynDataRangeCheck { get; set; } = false;
/// <summary>
/// Folder in cui è presente il tool import excel + file json di conf
/// </summary>
protected string exclToolDirPath { get; set; } = "";
/// <summary>
/// Folder in cui salvare i file importati (es excel)
/// </summary>
protected string fileArchiveFolder { get; set; } = "";
/// <summary>
/// Folder da cui importare i file (es excel)
/// </summary>
protected string fileImportFolder { get; set; } = "";
/// <summary>
/// Tipologia di files da importare (default excel)
/// </summary>
protected string fileImportType { get; set; } = "*.xslx";
/// <summary>
/// Dati fluxLog serializzati in redis
/// </summary>
protected List<GenLogRow> fluxLogData
{
get
{
List<GenLogRow> answ = new List<GenLogRow>();
string redKeyFLog = redisMan.redHash($"IOB:CurrData:{IOBConfFull.General.FilenameIOB}:LogFile:FluxLog");
var rawData = redisMan.getRSV(redKeyFLog);
if (!string.IsNullOrEmpty(rawData))
{
answ = JsonConvert.DeserializeObject<List<GenLogRow>>(rawData);
}
return answ;
}
set
{
string redKeyFLog = redisMan.redHash($"IOB:CurrData:{IOBConfFull.General.FilenameIOB}:LogFile:FluxLog");
string fluxLogRaw = JsonConvert.SerializeObject(value);
redisMan.setRSV(redKeyFLog, fluxLogRaw);
}
}
/// <summary>
/// Variabile di appoggio GLOBALE x indicare che si può forzare reset contapezzi con
/// macchina in RUN
/// </summary>
protected bool forceResetInRun { get; set; } = false;
/// <summary>
/// Variabile di appoggio GLOBALE x indicare macchina RUN (es x gestione su reset pezzi)
/// </summary>
protected bool isRunning { get; set; } = false;
/// <summary>
/// Variabile isRunning (NON realtime)
/// </summary>
protected bool isRunningState
{
get
{
bool answ = false;
string cStatus = redisMan.getRSV(redKeyProdRunState);
bool.TryParse(cStatus, out answ);
return answ;
}
set
{
redisMan.setRSV(redKeyProdRunState, $"{value}");
}
}
protected string lastArtDescr
{
get => redisMan.getRSV(redKeyProdLastArt);
set => redisMan.setRSV(redKeyProdLastArt, value);
}
/// <summary>
/// Wrapper di log
/// </summary>
protected override Logger lg
{
get => _logger;
}
/// <summary>
/// Elenco Articoli (x invio verso macchina)
/// </summary>
protected List<ArtRow> ListaArticoli { get; set; } = new List<ArtRow>();
/// <summary>
/// Elenco Job di produzione (x invio verso macchina)
/// </summary>
protected List<JobRow> ListaJobs { get; set; } = new List<JobRow>();
/// <summary>
/// Valore limite MASSIMO di invio di dati come array Json
/// </summary>
protected int maxJsonData { get; set; } = utils.CRI("maxJsonData");
/// <summary>
/// Valore limite MASSIMO di invio di dati come array Json x EVENTI
/// </summary>
protected int maxJsonDataEv { get; set; } = utils.CRI("maxJsonDataEv");
/// <summary>
/// Max tentativi ping permessi (default: 5)
/// </summary>
protected int maxPingRetry { get; set; } = 5;
/// <summary>
/// Coda massima ammessa per FLog (se = 0 disattivata...)
/// </summary>
protected int maxQueueFLog { get; set; } = utils.CRI("maxQueueFLog");
/// <summary>
/// Coda massima ammessa per FLog (se = 0 disattivata...)
/// </summary>
protected int maxQueueRawTransf { get; set; } = utils.CRI("maxQueueRawTransf");
/// <summary>
/// Valore MINIMO limite x decidere invio di dati come array Json
/// </summary>
protected int minJsonData { get; set; } = utils.CRI("minJsonData");
protected string nextKeyRich
{
get
{
return $"{dailyKey}{countKeyRichiesta:00}";
}
}
/// <summary>
/// Numero letture IN da avvio
/// </summary>
protected int nReadFilt { get; set; }
/// <summary>
/// Numero letture IN da avvio
/// </summary>
protected int nReadIN { get; set; }
/// <summary>
/// Numero invii OUT (svuotamento coda)
/// </summary>
protected int nSendOut { get; set; }
/// <summary>
/// Numero simulazioni ammesse...
/// </summary>
protected int numSim { get; set; }
/// <summary>
/// Dizionario condizioni di check BIT da usare x valori controllo (es ModBus TCP Imax)
/// </summary>
protected Dictionary<string, BitConditionCheck> OptCheckCondBit { get; set; } = new Dictionary<string, BitConditionCheck>();
/// <summary>
/// Dizionario condizioni di check INT da usare x valori controllo bitmap (es ModBus TCP Zetapack)
/// </summary>
protected Dictionary<string, IntConditionCheck> OptCheckCondInt { get; set; } = new Dictionary<string, IntConditionCheck>();
/// <summary>
/// Elenco di valori da tradurre da valori BIT (es ModBus TCP FIMAT)
/// NB: potrebbero essere contemporaneamente attivi + valori e + traduzioni
/// </summary>
protected Dictionary<string, string> OptVar2TranslBit { get; set; } = new Dictionary<string, string>();
/// <summary>
/// Elenco di valori da tradurre da valori INT esclusivi (es ModBus TCP FIMAT)
/// </summary>
protected Dictionary<string, string> OptVar2TranslInt { get; set; } = new Dictionary<string, string>();
/// <summary>
/// Elenco dei fullPath utili x processing
/// </summary>
protected Dictionary<string, string> pathList { get; set; } = new Dictionary<string, string>();
/// <summary>
/// Gestione archivio serializzato PODL inviati (tramite file temp locale/REDIS)
/// </summary>
protected Dictionary<int, PODLModel> POdlSentFileArch
{
get
{
Dictionary<int, PODLModel> answ = new Dictionary<int, PODLModel>();
string redKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:POdlSent");
string rawData = redisMan.getRSV(redKey);
if (!string.IsNullOrEmpty(rawData))
{
try
{
answ = JsonConvert.DeserializeObject<Dictionary<int, PODLModel>>(rawData);
lgInfo($"Rilettura status POdlSentFileArch: trovati {answ.Count} record");
}
catch (Exception exc)
{
lgError($"Errore in deserializzazione POdlSentFileArch{Environment.NewLine}{exc}");
}
}
return answ;
}
set
{
string rawVal = JsonConvert.SerializeObject(value);
string redKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:POdlSent");
redisMan.setRSV(redKey, rawVal);
lgDebug($"Salvataggio status POdlSentFileArch | {value.Count} record");
}
}
/// <summary>
/// Elenco corrente (in memory) PODL inviati all'impianto
/// chiave: idxPODL
/// valore: record PODL
/// </summary>
protected Dictionary<int, PODLModel> POdlSentList { get; set; } = new Dictionary<int, PODLModel>();
/// <summary>
/// Indica se sia stato resettato un contapezzi
/// </summary>
protected bool pzCountResetted { get; set; } = false;
/// <summary>
/// Fornisce il valore letto da BITMAP in formato valido x messa in coda nel formato dtEve#valReq#cont
/// </summary>
protected string qEncodeIN
{
get
{
string answ = "";
try
{
answ = string.Format("{0:yyyyMMddHHmmssfff}#{1:X2}#{2}", DateTime.Now, B_output, counterSigIN);
}
catch
{ }
return answ;
}
}
protected bool queueInEnabCurr
{
get => qInEnabCurr;
set
{
qInEnabCurr = value;
lgInfo($"SET queueInEnabCurr: {value} | {DateTime.Now:HHmmss}");
}
}
/// <summary>
/// Definizioni x replace in file ricette
/// </summary>
protected Dictionary<string, string> RecipeReplRules { get; set; } = new Dictionary<string, string>();
protected string redKeyLogfileAct
{
get => redisMan.redHash($"IOB:CurrData:{IOBConfFull.General.FilenameIOB}:LogFile:Act");
}
protected string redKeyLogfileLast
{
get => redisMan.redHash($"IOB:CurrData:{IOBConfFull.General.FilenameIOB}:LogFile:Last");
}
protected string redKeyProdLastArt
{
get => redisMan.redHash($"IOB:CurrData:{IOBConfFull.General.FilenameIOB}:prodDecod:lastArtDesc");
}
protected string redKeyProdRunState
{
get => redisMan.redHash($"IOB:CurrData:{IOBConfFull.General.FilenameIOB}:prodDecod:isRunningState");
}
/// <summary>
/// Redis key del dizionari valori DataItemMem persistiti
/// </summary>
protected string rKeyFluxMem
{
get => redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:FluxMem");
}
protected Random rndGen { get; set; } = new Random();
/// <summary>
/// Indica se vada inviata la keyReq richiesta con lo split/autoODL
/// </summary>
protected bool sendKeyRichiesta { get; set; } = false;
/// <summary>
/// test ping all'indirizzo PLC/CNC impostato nei parametri
/// </summary>
/// <returns></returns>
protected IPStatus testPingMachine
{
get
{
IPStatus answ = IPStatus.Unknown;
// se disabilitato salto...
if (IOBConfFull.Device.DisabPing)
{
answ = IPStatus.Success;
}
else
{
IPAddress address;
PingReply reply;
using (Ping pingSender = new Ping())
{
address = IPAddress.Loopback;
int pingMsTimeout = IOBConfFull.Device.Connect.PingMsTimeout;
IPAddress.TryParse(IOBConfFull.Device.Connect.PingIpAddr, out address);
try
{
// se != null --> uso address...
if (address != null)
{
reply = pingSender.Send(address, pingMsTimeout);
}
else
{
reply = pingSender.Send(IOBConfFull.Device.Connect.IpAddr, pingMsTimeout);
}
}
catch
{
reply = pingSender.Send(IPAddress.Loopback, pingMsTimeout);
}
answ = reply.Status;
}
}
return answ;
}
}
/// <summary>
/// URL per INVIO IN BLOCCO di un INCREMENTO x contapezzi (quelli della macchina: PLC/CNC)...
/// </summary>
protected string urlAddPzCount
{
get => $@"{urlCommandIob("savePzCountInc")}?qty=";
}
/// <summary>
/// URL per INVIO IN BLOCCO di un INCREMENTO x contapezzi (quelli della macchina: PLC/CNC)
/// in una data SPECIFICA
/// </summary>
protected string urlAddPzCountAtDate
{
get => $@"{urlCommandIob("savePzCountIncAtDate")}?qty=";
}
/// <summary>
/// URL per check alive...
/// </summary>
protected string urlAlive
{
get
{
return $@"{urlCommand("alive")}";
}
}
/// <summary>
/// URL per creazione di un PODL da cod art (metodo GET)...
/// </summary>
protected string urlCreatePOdl
{
get => $@"{urlCommandIob("forceCreatePOdl")}?";
}
/// <summary>
/// URL per chiamata generazione Snapshot Dossier giornalieri alla data
/// </summary>
protected string urlFixDailyDossier
{
get => $@"{urlCommandIob("fixDailyDossier")}";
}
/// <summary>
/// URL per chiamata generazione ODL giornalieri alla data
/// </summary>
protected string urlFixDailyOdl
{
get => $@"{urlCommandIob("fixDailyOdl")}";
}
/// <summary>
/// URL per chiamata generazione ODL giornalieri alla data + conferma PzCount ad ogni step
/// </summary>
protected string urlFixDailyOdlConfPzCount
{
get => $@"{urlCommandIob("fixDailyOdlConfPzCount")}";
}
/// <summary>
/// URL per forzare split ODL...
/// </summary>
protected string urlForceSplit
{
get => $"{urlCommandIob("forceSplitOdlFull")}?doConfirm=true&qtyFromLast=true&roundStep=500";
}
/// <summary>
/// URL per recupero PODL NEXT = ATTIVABILI x macchina...
/// </summary>
protected string urlGetActPODL
{
get => $@"{urlCommandIob("getPOdlAct")}";
}
/// <summary>
/// URL per recupero ARTICOLI correnti x macchina...
/// </summary>
protected string urlGetCurrArt
{
get => $@"{urlCommandIob("getLastArtByMacc")}";
}
/// <summary>
/// URL per recupero DOSS correnti x macchina...
/// </summary>
protected string urlGetCurrDOSS
{
get => $@"{urlCommandIob("getLastDossByMacc")}";
}
/// <summary>
/// URL per recupero IDX ODL corrente...
/// </summary>
protected string urlGetCurrODL
{
get => $@"{urlCommandIob("getCurrODL")}";
}
/// <summary>
/// URL per recupero dati ODL corrente...
/// </summary>
protected string urlGetCurrOdlRow
{
get => $@"{urlCommandIob("getCurrOdlRow")}";
}
/// <summary>
/// URL per recupero PODL ATTIVABILI (quindi correnti in senso di pronti) x macchina...
/// </summary>
[Obsolete("Metodo obsoleto (dubbia interpretazione): sostituire con urlGetNextPODL se si vogliono PROSSIMI POdl attivabili (significato originale) o con urlGetActPODL x il POdl attualmente in produzione (per casi come Rama OPC-UA Siemens)")]
protected string urlGetCurrPODL
{
get => $@"{urlCommandIob("getCurrPODL")}";
}
/// <summary>
/// URL per recupero ListValue tipo fasi...
/// </summary>
protected string urlGetListValFasiPodl
{
get => $@"{urlCommand("getListValByTable")}PODL";
}
/// <summary>
/// URL per recupero PODL NEXT = ATTIVABILI x macchina...
/// </summary>
protected string urlGetNextPODL
{
get => $@"{urlCommandIob("getPOdlNext")}";
}
/// <summary>
/// URL per recupero traduzione CodArt in numerico...
/// </summary>
protected string urlGetNumArt
{
get => $@"{urlCommandIob("getArtNum")}";
}
/// <summary>
/// URL per recupero traduzione CodComm in numerico...
/// </summary>
protected string urlGetNumComm
{
get => $@"{urlCommandIob("getXdlNum")}";
}
/// <summary>
/// URL per recupero num pezzi ODL corrente...
/// </summary>
protected string urlGetNumPzCurrODL
{
get => $@"{urlCommandIob("getCurrOdlQtaReq")}";
}
/// <summary>
/// URL per richiamo parametri da scrivere...
/// </summary>
protected string urlGetParams2Write
{
get => $@"{urlCommandIob("getObjItems2Write")}";
}
/// <summary>
/// URL per recupero contapezzi...
/// </summary>
protected string urlGetPzCount
{
get => $@"{urlCommandIob("getCounter")}";
}
/// <summary>
/// URL per recupero contapezzi REGISTRATI da TC...
/// </summary>
protected string urlGetPzCountRec
{
get => $@"{urlCommandIob("getCounterTCRec")}";
}
/// <summary>
/// URL per richiamo task da eseguire...
/// </summary>
protected string urlGetTask2Exe
{
get => $@"{urlCommandIob("getTask2Exe")}";
}
/// <summary>
/// URL per recupero idle time IOB...
/// </summary>
protected string urlIdleTime
{
get => $@"{urlCommandIob("getIdlePeriod")}";
}
/// <summary>
/// URL per recupero inizio ODL...
/// </summary>
protected string urlInizioOdlIob
{
get => $@"{urlCommandIob("getCurrOdlStart")}";
}
/// <summary>
/// URL per check se abilitato...
/// </summary>
protected string urlIobEnabled
{
get => $@"{urlCommandIob("enabled")}";
}
/// <summary>
/// URL per richiesta chiusura manuale ad utente x ODL corrente (metodo GET)...
/// </summary>
protected string urlODLAskClose
{
get => $@"{urlCommandIob("askCloseODL")}?idxOdl=";
}
/// <summary>
/// URL per chiusura ODL corrente (metodo GET)...
/// </summary>
protected string urlODLClose
{
get => $@"{urlCommandIob("closeODL")}/?idxOdl=";
}
/// <summary>
/// URL per AVVIO di un ODL da PODL indicato (metodo GET)...
/// </summary>
protected string urlOdlStartFromPOdl
{
get => $@"{urlCommandIob("forceStartPOdl")}?idxPODL=";
}
/// <summary>
/// URL per chiusura PODL --&gt; ODL corrente (metodo GET)...
/// </summary>
protected string urlPODLClose
{
get => $@"{urlCommandIob("closePODL")}?idxPOdl=";
}
/// <summary>
/// URL per richiamo task da eseguire...
/// </summary>
protected string urlRemTask2Exe
{
get => $@"{urlCommandIob("remTask2Exe")}?taskName=";
}
/// <summary>
/// URL per richiamo task da eseguire...
/// </summary>
protected string urlRemTask2ExeTav(string codTav)
{
return $@"{urlCommandIob("remTask2Exe")}|{codTav}?taskName=";
}
/// <summary>
/// URL per salvataggio dati PARAMETRI IOB...
/// </summary>
protected string urlSaveAllParams
{
get => $@"{urlCommandIob("setObjItems")}";
}
/// <summary>
/// URL x salvataggio elenco dataItems OpcUa/MTC
/// </summary>
protected string urlSaveDataItems
{
get => $@"{urlCommandIob("saveDataItems")}";
}
/// <summary>
/// URL per invio MachineIobConf info
/// </summary>
protected string urlSaveMachIobConf
{
get => $@"{urlCommandIobFile("saveMachineIobConf")}";
}
/// <summary>
/// URL per salvataggio dati conf memoria IOB...
/// </summary>
protected string urlSaveMemMap
{
get => $@"{urlCommandIobFile("saveConf")}";
}
/// <summary>
/// URL per INVIO di un update dello status di un certo allarme
/// </summary>
protected string urlSendAlarm
{
get => $@"{urlCommandIob("sendAlarmBankUpdate")}";
}
/// <summary>
/// URL per invio info HASH gestione recipes
/// </summary>
protected string urlSetHashDict
{
get => $@"{urlCommandIob("setRedisHashDict")}";
}
/// <summary>
/// URL per salvataggio dati associazione Machine 2 IOB...
/// </summary>
protected string urlSetM2IOB
{
get => $@"{urlCommandIobFile("setM2IOB")}?IOB_name={Environment.MachineName}";
}
/// <summary>
/// URL per salvataggio VALORI opzionali...
/// </summary>
protected string urlSetOptVal
{
get => $@"{urlCommandIob("addOptPar")}?";
}
/// <summary>
/// URL per salvataggio contapezzi...
/// </summary>
protected string urlSetPzCount
{
get => $@"{urlCommandIob("setCounter")}?counter=";
}
/// <summary>
/// URL per salvataggio contapezzi (quelli della macchina: PLC/CNC)...
/// </summary>
protected string urlSetPzCountMAC
{
get => $@"{urlCommand("setCounter")}MAC_{IOBConfFull.General.CodIOB}?counter=";
}
/// <summary>
/// URL per effettuare salvataggio parametri (snapshot) macchina...
/// </summary>
protected string urlTakeSnapshot
{
get => $@"{urlCommandIob("takeFlogSnapshot")}";
}
/// <summary>
/// URL per salvataggio in UPSERT dei PARAMETRI IOB scritti...
/// </summary>
protected string urlUpdateWriteParams
{
get => $@"{urlCommandIob("upsertObjItems")}";
}
/// <summary>
/// Secondi standard x veto check status e log
/// </summary>
protected int vetoSeconds { get; set; } = utils.CRI("vetoSeconds");
#endregion Protected Properties
#region Protected Methods
/// <summary>
/// Verifico i dynData x validità:
/// - siano almeno 1
/// - NON siano tutti identici
/// </summary>
/// <param name="currDynData"></param>
/// <returns></returns>
protected bool checkValidDynData(Dictionary<string, string> currDynData)
{
bool answ = false;
// conto num valori
int numVal = currDynData.Count;
bool dataPresent = numVal > 0;
if (dataPresent)
{
// prendo il primo valore..
string firstVal = currDynData.FirstOrDefault().Value;
// conto val uguali al primo
int numEq = currDynData.Where(x => x.Value == firstVal).Count();
// se solo 1 o almeno 1 è diverso è ok
answ = numVal == 1 || (numVal > numEq);
}
return answ;
}
/// <summary>
/// Decodifica file MAP (caso <paramref name="ByteNum" />.bit)
/// </summary>
/// <param name="linea"></param>
/// <param name="separator"></param>
/// <param name="ByteNum">indirizzo Byte: indirizzo di partenza memoria</param>
/// <param name="memSize">dimensione singolo slot in byte</param>
/// <param name="BitNum">indirizzo bit: numero riga x calcolo indice bit</param>
/// <returns></returns>
protected static otherData decodeBitData(string linea, char separator, int ByteNum, int memSize, int BitNum)
{
if (linea != null)
{
string[] valori = linea.Split(separator);
int shift = 0;
try
{
shift = Convert.ToInt32(valori[0]) - 1;
}
catch
{ }
int resto = 0;
Math.DivRem(BitNum, 8, out resto);
string memAddr = string.Format("{0}.{1}", ByteNum + shift * memSize, resto);
return new otherData(valori[0], memAddr, valori[1].Trim(), valori[2].Trim());
}
else
{
return null;
}
}
/// <summary>
/// Decodifica file MAP generico
/// </summary>
/// <param name="linea"></param>
/// <param name="separator"></param>
/// <param name="memPre"></param>
/// <param name="baseAddr"></param>
/// <param name="memSize"></param>
/// <returns></returns>
protected static otherData decodeOtherData(string linea, char separator, string memPre, int baseAddr, int memSize)
{
if (linea != null)
{
string[] valori = linea.Split(separator);
int shift = 0;
try
{
shift = Convert.ToInt32(valori[0]) - 1;
}
catch
{ }
string memAddr = string.Format("{0}{1}", memPre, baseAddr + shift * memSize);
return new otherData(valori[0], memAddr, valori[1].Trim(), valori[2].Trim());
}
else
{
return null;
}
}
/// <summary>
/// Decodifica valore della coda IN nel formato hasVeto[0]=dtEve hasVeto[1]=valore hasVeto[2]=counter
/// </summary>
/// <param name="queueVal">dtEve + '#' + valReq + '#' + cont</param>
/// <returns></returns>
protected static string[] qDecodeIN(string queueVal)
{
string[] answ = null;
if (!string.IsNullOrEmpty(queueVal))
{
try
{
answ = queueVal.Split('#');
}
catch
{ }
}
return answ;
}
/// <summary>
/// Classe di base implementazione traduzione di una LUT da memoria come valore BIT a valore
/// esplicito x FLog
/// </summary>
protected virtual void checkTranslateBit()
{
// verifico se devo processare decodifica di qualche valore...
if (OptVar2TranslBit != null && OptVar2TranslBit.Count > 0)
{ }
}
/// <summary>
/// Classe di base implementazione traduzione di una LUT da memoria come valore INT a valore
/// esplicito x FLog
/// </summary>
protected virtual void checkTranslateInt()
{
if (OptVar2TranslInt != null && OptVar2TranslInt.Count > 0)
{ }
}
/// <summary>
/// Verifica veto coda QueueIN ed aggiorna abilitazione su variabile
/// </summary>
protected void checkVetoQueueIn()
{
queueInEnabCurr = dtVetoQueueIN < DateTime.Now;
}
/// <summary>
/// Conversione string row in log generico
/// </summary>
/// <param name="dailyLog"></param>
/// <returns></returns>
protected virtual GenLogRow convertToMachineLog(string dailyLog)
{
GenLogRow answ = new GenLogRow();
if (!string.IsNullOrEmpty(dailyLog))
{
// preventivamente: doppio ";" --> ";"
dailyLog = dailyLog.Replace(";", ";");
var sSplit = dailyLog.Split(';');
answ.dtRif = DateTime.ParseExact($"{sSplit[0]} {sSplit[1]}", "yyyy-M-d HH:mm:ss", null);
answ.valString = $"{sSplit[2]};{sSplit[3]}";
}
return answ;
}
/// <summary>
/// Restituisce stato allarmi in formato byte[]
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
protected byte[] currAlarmsState(BaseAlarmConf item)
{
byte[] answ = new byte[item.size];
int currStatus = 0;
int[] listInt = new int[2];
// banchi in array Int16 --> scompongo
for (int i = 0; i < item.size / 2; i++)
{
currStatus = getAlarmStatus(item);
// aggiornamento blink counters dato nuovo valore
item.checkBlinkCounter(i, (uint)currStatus);
// calcolo indice errori
if ((uint)(item.alarmsMask[i] & currStatus) > 0)
{
var currByte = BitConverter.GetBytes(currStatus);
// salvo nell'array di byte
Buffer.BlockCopy(currByte, 0, answ, i * 2, 2);
}
}
return answ;
}
/// <summary>
/// Recupera ODL attivo su impianto
/// </summary>
protected int CurrOdl()
{
int answ = 0;
string sCurrODL = utils.callUrl(urlGetCurrODL);
int.TryParse(sCurrODL, out answ);
return answ;
}
/// <summary>
/// Aggiunge o aggiorna nDict ad un Dict
/// </summary>
/// <param name="currDict"></param>
/// <param name="newKey"></param>
/// <param name="newVal"></param>
protected void DictUpsert(ref Dictionary<string, string> currDict, string newKey, string newVal)
{
if (currDict.ContainsKey(newKey))
{
currDict[newKey] = newVal;
}
else
{
currDict.Add(newKey, newVal);
}
}
/// <summary>
/// Imposta eventuali altri valori default
/// </summary>
protected void fixDefaultPar()
{
// parametro max tentativi PING...
maxPingRetry = IOBConfFull.General.MaxPingRetry;
maxErroriCheck = IOBConfFull.General.MaxErroriCheck;
watchDogPeriod = IOBConfFull.General.WatchDogPeriod;
// sistemo conf folder acquisizione files
if (IOBConfFull.Special.FileConf != null)
{
fileImportFolder = IOBConfFull.Special.FileConf.ImportFolder;
fileImportType = IOBConfFull.Special.FileConf.ImportType;
fileArchiveFolder = IOBConfFull.Special.FileConf.ArchiveFolder;
exclToolDirPath = IOBConfFull.Special.FileConf.ExcelToolDirPath;
}
delayMinReadPzCount = IOBConfFull.General.DelayReadPzCount;
}
/// <summary>
/// Effettua un force kill dato nome processo
/// </summary>
/// <param name="nomeProc"></param>
protected void ForceKillByName(string nomeProc)
{
Process[] stillRunningProc = Process.GetProcessesByName(nomeProc);
if (stillRunningProc != null)
{
if (stillRunningProc.Length > 0)
{
foreach (var item in stillRunningProc)
{
try
{
Process p = Process.GetProcessById(item.Id);
{
if (!p.HasExited)
{
int closeWaitTime = waitForExitMsec * 8;
// se è MAN --> aspetto + a lungo...
if (nomeProc.Contains("IOB-MAN"))
{
closeWaitTime = closeWaitTime * 4;
}
p.CloseMainWindow();
p.WaitForExit(closeWaitTime);
}
if (!p.HasExited)
{
lg.Error($"Process not Exited, 2nd try p.kill()");
p.Kill();
p.WaitForExit(waitForExitMsec);
}
if (!p.HasExited)
{
lg.Error($"Process not Killed, 3nd try p.kill()");
p.Kill();
p.WaitForExit(waitForExitMsec * 2);
}
if (!p.HasExited)
{
lg.Error($"Process not Killed, 4th try p.kill()");
p.Kill();
}
}
}
catch (Exception exc)
{
lg.Error($"Errore in fase di kill processo da nome {exc}");
}
}
}
}
}
/// <summary>
/// Imposta la memoria PLC in modo forzato (es x oggetti gestiti da FTP come setComm)
/// </summary>
protected virtual void forceMemMap()
{ }
/// <summary>
/// Implementazione di riferimento della verifica stato allarmi
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
protected virtual int getAlarmStatus(BaseAlarmConf item)
{
int answ = 0;
return answ;
}
/// <summary>
/// Implementazione di riferimento della verifica stato allarmi formato UInt
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
protected virtual uint getAlarmStatusUInt(BaseAlarmConf item)
{
uint answ = 0;
return answ;
}
/// <summary>
/// Recupera valore da dizionario CurrProdData o restituisce val default
/// </summary>
/// <param name="key">Chiave richiesta</param>
/// <param name="defVal">Valore di default</param>
/// <returns></returns>
protected string getCurrProdData(string key, string defVal)
{
string answ = "";
if (currProdData.ContainsKey(key))
{
answ = string.IsNullOrEmpty(currProdData[key]) ? defVal : currProdData[key];
}
return answ;
}
/// <summary>
/// Fornisce formato valido x messa in coda nel formato dtEve#valReq#cont
/// </summary>
protected string getEncodSigLog(DateTime DtEvent, int val2log, int counter)
{
string answ = "";
try
{
answ = $"{DtEvent:yyyyMMddHHmmssfff}#{val2log:X2}#{counter}";
}
catch
{ }
return answ;
}
/// <summary>
/// Recupero dati da FluxLog
/// </summary>
/// <param name="resultSet"></param>
/// <param name="codFlux"></param>
/// <returns></returns>
protected string getFluxVal(DossierFluxLogDTO resultSet, string codFlux)
{
string answ = "";
var searchRec = resultSet.ODL.Where(x => x.CodFlux == codFlux).FirstOrDefault();
if (searchRec != null)
{
answ = !string.IsNullOrEmpty(searchRec.ValoreEdit) ? searchRec.ValoreEdit : searchRec.Valore;
}
return answ;
}
/// <summary>
/// Recupero dati da FluxLog formato double
/// </summary>
/// <param name="resultSet"></param>
/// <param name="codFlux"></param>
/// <returns></returns>
protected double getFluxValDouble(DossierFluxLogDTO resultSet, string codFlux)
{
double answ = 0;
var style = NumberStyles.AllowDecimalPoint;
var culture = CultureInfo.InvariantCulture;
string rawVal = getFluxVal(resultSet, codFlux);
if (rawVal.Contains(","))
{
rawVal = rawVal.Replace(",", ".");
}
if (!string.IsNullOrEmpty(rawVal))
{
double.TryParse(rawVal, style, culture, out answ);
}
return answ;
}
/// <summary>
/// Recupero dati da FluxLog formato INT
/// </summary>
/// <param name="resultSet"></param>
/// <param name="codFlux"></param>
/// <returns></returns>
protected int getFluxValInt(DossierFluxLogDTO resultSet, string codFlux)
{
int answ = 0;
double dVal = 0;
var style = NumberStyles.AllowDecimalPoint;
var culture = CultureInfo.InvariantCulture;
string rawVal = getFluxVal(resultSet, codFlux);
if (rawVal.Contains(","))
{
rawVal = rawVal.Replace(",", ".");
}
if (!string.IsNullOrEmpty(rawVal))
{
double.TryParse(rawVal, style, culture, out dVal);
answ = (int)dVal;
}
return answ;
}
/// <summary>
/// Converte un file di log della macchina in un dizionario
/// </summary>
/// <param name="dailyLog"></param>
/// <returns></returns>
protected List<GenLogRow> getGenLogFromMachineLog(string dailyLog)
{
List<GenLogRow> result = new List<GenLogRow>();
//se ho valori.. faccio split!
if (!string.IsNullOrEmpty(dailyLog))
{
var rowList = dailyLog.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
if (rowList != null && rowList.Length > 0)
{
result = rowList
.Where(x => !string.IsNullOrEmpty(x))
.Select(x => convertToMachineLog(x))
.ToList();
}
}
return result;
}
/// <summary>
/// Effettua traduzione da tabella listValue per chiave
/// </summary>
/// <param name="factTable"></param>
/// <param name="key"></param>
/// <returns></returns>
protected string getLV(Dictionary<string, string> currDict, string key)
{
string answ = currDict.ContainsKey(key) ? currDict[key] : key;
return answ;
}
/// <summary>
/// Restituisce valore salvato in memMapWrite
/// </summary>
/// <param name="keyName"></param>
/// <returns></returns>
protected string getMemMapWriteVal(string keyName)
{
string answ = "";
if (memMap != null && memMap.mMapWrite.ContainsKey(keyName))
{
answ = memMap.mMapWrite[keyName].value;
}
return answ;
}
/// <summary>
/// Recupera valore numerico ARTICOLO x salvataggio valori INT
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
protected string getNumArt(string value)
{
string answ = "";
// cerco in dizionario corrente...
if (DictNumArt.Count > 0 && DictNumArt.ContainsKey(value))
{
answ = DictNumArt[value];
}
// altrimenti chiamo servizio
else
{
// chiamo MP-IO server
bool srvAlive = CheckServerAlive();
if (srvAlive)
{
string url2call = $"{urlGetNumArt}?CodArt={value}";
if (verboseLog)
{
lgInfo("chiamata URL " + url2call);
}
var rawData = utils.callUrlNow(url2call);
// deserializzo e recupero KVP...
var dictArtSrv = JsonConvert.DeserializeObject<Dictionary<string, string>>(rawData);
// se fosse una chiamata con valore vuoto --> salvo intera tabella...
if (string.IsNullOrEmpty(value))
{
DictNumArt = dictArtSrv;
}
// altrimenti aggiungo a dizionario
else
{
foreach (var item in dictArtSrv)
{
if (!DictNumArt.ContainsKey(item.Key))
{
DictNumArt.Add(item.Key, item.Value);
}
}
}
// ora cerco valore e restituisco
if (DictNumArt.ContainsKey(value))
{
answ = DictNumArt[value];
}
}
}
return answ;
}
/// <summary>
/// Recupera valore numerico COMMESSA x salvataggio valori INT
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
protected string getNumComm(string value)
{
string answ = "";
// chiamo MP-IO server
bool srvAlive = CheckServerAlive();
if (srvAlive)
{
string url2call = $"{urlGetNumComm}?CodXdl={value}";
if (verboseLog)
{
lgInfo("chiamata URL " + url2call);
}
// è direttamente il risultato!
answ = utils.callUrlNow(url2call);
}
// restituisco
return answ;
}
/// <summary>
/// Stringa raw dei parametri da scrivere...
/// </summary>
/// <returns></returns>
protected string getParams2write()
{
string answ = "";
string url2call = $"{urlGetParams2Write}";
if (verboseLog)
{
lgInfo("chiamata URL " + url2call);
}
answ = utils.callUrlNow(url2call);
// se vuoto faccio seconda prova...
if (string.IsNullOrEmpty(answ) || answ == "[]")
{
answ = utils.callUrlNow(url2call);
}
return answ;
}
/// <summary>
/// Recupera file log da analizzare
/// </summary>
/// <returns></returns>
protected virtual bool getRemoteLog()
{
bool fatto = false;
return fatto;
}
/// <summary>
/// Chiede elenco dei task da eseguire
/// - formato Json
/// - array di KVP / Dictionary
/// - formato definito da API x MP/IO/:
/// - KEY: task
/// - VALUE: array JSon KVP
/// </summary>
/// <param name="codTav">Cod DP/Tavola (opzionale)</param>
/// <returns></returns>
protected async Task<string> getTask2exe(string codTav)
{
string answ = "";
bool srvAlive = await CheckServerAliveAsync();
if (srvAlive)
{
string url2call = $"{urlGetTask2Exe}";
// se ho tavola aggiungo richiesta...
if (!string.IsNullOrEmpty(codTav))
{
url2call += $"|{codTav}";
}
if (verboseLog)
{
lgInfo("chiamata URL " + url2call);
}
answ = await utils.callUrlAsync(url2call);
#if false
answ = utils.callUrlNow(url2call);
#endif
}
return answ;
}
/// <summary>
/// Verifica presenza eventuali allarmi
/// </summary>
/// <returns></returns>
protected virtual bool hasAlarms()
{
bool answ = false;
// 2023.12.20: aggiunta gestione secondo tipo allarmi come elenco OPC-UA (BLM/Adige)
if (alarmType == AlarmBlockType.Bitmap)
{
int numRaised = 0;
int currStatus = 0;
ushort full16 = 65535;
int[] listInt = new int[2];
if (alarmMaps != null)
{
// leggo a ciclo le aree degli allarmi CONFIGURATI, se ne trovo --> segnalo allarme...
foreach (var item in alarmMaps)
{
// banchi in array Int16 --> scompongo
for (int i = 0; i < item.size / 2; i++)
{
currStatus = getAlarmStatus(item);
// ora filtro x l'i-esimo banco a 16 bit...
if (i == 0)
{
currStatus = (ushort)(currStatus & full16);
}
else
{
var temp = currStatus >> 16;
currStatus = (ushort)temp;
}
// aggiornamento blink counters dato nuovo valore
item.checkBlinkCounter(i, (uint)currStatus);
// calcolo indice errori
if ((uint)(item.alarmsMask[i] & currStatus) > 0)
{
numRaised++;
}
// verifico SE sia variato... confronto allarmi filtrato stile blink per
// bit status:
// - allarmi che iniziano per # IGNORATI
// - altri allarmi con un countdown da MAX_COUNTER_BLINK a 0 per il fronte
// di discesa
if (item.isChanged(i, (uint)currStatus))
{
lgInfo($"Alarm state change | {item.memAddr} | i: {i} | prev: {item.alarmsState[i]} | curr: {currStatus}");
// registro gli allarmi attivi e trasmetto...
if (sendAlarmVariations(item.memAddr, i, item.alarmsState[i], (uint)(item.alarmsMask[i] & currStatus), item.messages))
{
// se inviato --> salvo stato da current...
item.updStatusVal(i, (uint)(item.alarmsMask[i] & currStatus));
// salvo in redis...
string alarmHash = redisMan.redHash($"IOB:ALARM_STATUS:{IOBConfFull.General.FilenameIOB}:{item.memAddr}");
string rawAlarms = JsonConvert.SerializeObject(item.alarmsState);
redisMan.setRSV(alarmHash, rawAlarms);
}
else
{
lgError($"Errore in sendAlarmVariations | {item.memAddr} | i: {i} | prev: {item.alarmsState[i]} | curr: {currStatus}");
}
}
else
{
if (currStatus > 0)
{
lgTrace($"Alarm UNCHANGED but Active | {item.memAddr} | i: {i} | prev: {item.alarmsState[i]} | curr: {currStatus}");
}
}
}
}
}
answ = numRaised > 0;
}
// gestione allarmi come elenco
else if (alarmType == AlarmBlockType.ActiveList)
{
// verifico SE ho allarmi
if (alarmMaps != null && alarmMaps.Count > 0)
{
// ciclo x ogni blocco
foreach (var item in alarmMaps)
{
// cerco se sia superiore al livello minimo da conf
if (item.blockLevel >= alarmLevelMin)
{
// controllo variabile counter SE > 0...
int numCount = getAlarmStatus(item);
answ = numCount > 0;
}
}
}
}
return answ;
}
/// <summary>
/// Recupera da server set di dati specifici x IOB
/// </summary>
/// <returns></returns>
protected virtual bool IobGetDataFromServer()
{
return false;
}
/// <summary>
/// Effettua upload verso server FTP della macchina dei files nella folder indicata
/// </summary>
/// <param name="folderDir"></param>
/// <returns></returns>
protected virtual bool iobSendFTP(string folderDir)
{
bool answ = false;
if (IOBConfFull.Special.FtpConf != null)
{
// recupero CONF
var ftpConf = IOBConfFull.Special.FtpConf;
// procedo SE TROVO conf...
if (string.IsNullOrEmpty($"{ftpConf.Server}{ftpConf.User}{ftpConf.Passwd}"))
{
lgError($"Impossibile eseguire iobSendFTP x mancanza parametri | ftpServ: {ftpConf.Server} | ftpUser: {ftpConf.User} | ftpPass: {ftpConf.Passwd}");
}
else
{
var ftpClient = new Manager(ftpConf.Server, ftpConf.User, ftpConf.Passwd, ftpConf.CertValue, ftpConf.SkipCert);
var testServer = ftpClient.ServerOk();
if (testServer)
{
lgInfo($"FTP: server found at {ftpConf.Server}");
var srvType = ftpClient.ServerType();
lgInfo($"FTP Server type: {srvType}");
var preTest = ftpClient.DirExists(ftpConf.DirRemote);
if (!preTest)
{
var dirCreate = ftpClient.CreateDir(ftpConf.DirRemote);
lgInfo($"FTP: created remote dir {ftpConf.DirRemote}");
}
// test directory...
string basePath = Application.StartupPath;
string localPath = Path.Combine(basePath, ftpConf.DirLocal);
lgInfo($"basePath: {basePath} | localPath: {localPath}");
var fileList = Directory.GetFiles(localPath, "*.csv");
int numfile = 0;
foreach (var file in fileList)
{
string fName = file.Split('\\').Last();
string remName = $"{ftpConf.DirRemote}\\{fName}";
ftpClient.SendFile(file, remName);
numfile++;
}
var dirUploaded = (numfile == fileList.Count());
if (dirUploaded)
{
lgInfo($"FTP: uploaded dir content {ftpConf.DirLocal} --> {ftpConf.DirRemote}");
}
// se ok --> sposto invio
DateTime adesso = DateTime.Now;
string archBasePath = Path.Combine(basePath, "DATA", "HIST", $"{adesso:yyyy}");
baseUtils.checkDir(archBasePath);
string archPath = Path.Combine(archBasePath, $"{adesso:MMddHHmmss}");
baseUtils.checkDir(archPath);
lgInfo($"Richiesta Dir Move | {localPath} | {archPath}");
foreach (var item in fileList)
{
string fName = item.Split('\\').Last();
string destName = $"{archPath}\\{fName}";
File.Move(item, destName);
}
//Directory.Move(localPath, archBasePath);
lgInfo($"FTP: Archived dir content {localPath} --> {archPath}");
}
}
}
return answ;
}
/// <summary>
/// Prepara files CSV da inviare alla macchina
/// </summary>
protected virtual bool iobWriteLocalCSV()
{
bool answ = false;
// conf ftp
if (IOBConfFull.Special.FtpConf != null)
{
var ftpConf = IOBConfFull.Special.FtpConf;
// salvo articoli
string locDir = ftpConf.DirLocal;
bool addHeader = ftpConf.CsvAddHeader;
string basePath = Application.StartupPath;
string tempDir = Path.Combine(basePath, locDir);
lgInfo($"iobWriteLocalCSV | locDir: {locDir} | addHeader: {addHeader} | tempDir: {tempDir}");
baseUtils.checkDir(tempDir);
string filePath = Path.Combine(tempDir, "articoli.csv");
answ = DataExport.SaveToCsv(ListaArticoli, filePath, addHeader);
if (answ)
{
lgInfo($"CSV: saved ART file as articoli.csv at {filePath}");
// salvo PODL
string csvName = $"{DateTime.Now:dd-MM-yyyy}.csv";
filePath = Path.Combine(tempDir, $"{csvName}");
answ = DataExport.SaveToCsv(ListaJobs, filePath, addHeader);
if (answ)
{
lgInfo($"CSV: saved PODL file {csvName} at {filePath}");
}
}
}
return answ;
}
/// <summary>
/// Prepara files USTD da inviare alle macchine in formato USTD (Emmegi - taglierine)
/// </summary>
protected virtual bool iobWriteLocalUSTD()
{
bool answ = false;
#if false
// conf ftp
var ftpConf = IOBConfFull.Special.FtpConf;
// salvo articoli
string locDir = ftpConf.DirLocal;
bool addHeader = ftpConf.CsvAddHeader;
string basePath = Application.StartupPath;
string tempDir = Path.Combine(basePath, locDir);
lgInfo($"iobWriteLocalCSV | locDir: {locDir} | addHeader: {addHeader} | tempDir: {tempDir}");
baseUtils.checkDir(tempDir);
string filePath = Path.Combine(tempDir, "articoli.csv");
answ = DataExport.SaveToCsv(ListaArticoli, filePath, addHeader);
if (answ)
{
lgInfo($"CSV: saved ART file as articoli.csv at {filePath}");
// salvo PODL
string csvName = $"{DateTime.Now:dd-MM-yyyy}.csv";
filePath = Path.Combine(tempDir, $"{csvName}");
answ = DataExport.SaveToCsv(ListaJobs, filePath, addHeader);
if (answ)
{
lgInfo($"CSV: saved PODL file {csvName} at {filePath}");
}
}
#endif
return answ;
}
/// <summary>
/// Effettua traduzione ITEM da LUT parametrica (keyReq: tipo+id) del file di conf, se non
/// trovo uso keyReq
/// </summary>
/// <param name="tipo"></param>
/// <param name="id"></param>
/// <returns></returns>
protected virtual string itemTranslation(string tipo, string id)
{
string answ = "";
if (IOBConfFull.ItemTranslation != null)
{
string lemma = id;
if (!string.IsNullOrEmpty(tipo))
{
lemma = $"{tipo}_{id}";
}
// cerco nel dizionario delle traduzioni SE esiste un valore e prendo quello,
// altrimenti uso il lemma...
if (IOBConfFull.ItemTranslation.ContainsKey(lemma))
{
answ = IOBConfFull.ItemTranslation[lemma];
}
else
{
answ = lemma;
}
}
return answ;
}
/// <summary>
/// Effettua traduzione ITEM da LUT parametrica (keyReq: lemma) del file di conf, se non
/// trovo uso keyReq
/// </summary>
/// <param name="lemma"></param>
/// <returns></returns>
protected virtual string itemTranslation(string lemma)
{
string answ = "";
if (IOBConfFull.ItemTranslation != null)
{
if (IOBConfFull.ItemTranslation.ContainsKey(lemma))
{
answ = IOBConfFull.ItemTranslation[lemma];
}
else
{
answ = lemma;
}
}
return answ;
}
/// <summary>
/// Legge il file di conf di una MAP di informazioni da gestire con lettura set memoria
/// </summary>
/// <param name="vettoreConf">nome vettore memoria</param>
/// <param name="nomeFile">file origine</param>
/// <param name="memSize">dimensione (in byte) della memoria</param>
/// <param name="numVett">dimensione (in byte) della memoria</param>
protected void loadConfFile(ref otherData[] vettoreConf, string nomeFile, int memSize, ref int numVett)
{
otherData lastData = new otherData();
int totRighe = 0;
string linea;
totRighe = File.ReadLines(nomeFile).Count();
// creo un vettore della dimensione corretta... conta anche commenti tanto poi riduco...
vettoreConf = new otherData[File.ReadLines(nomeFile).Count()];
// carica da file...
StreamReader file = new StreamReader(nomeFile);
// leggo 1 linea alla volta...
int numRiga = 0;
int bitNum = 0;
int byteNum = 0;
while ((linea = file.ReadLine()) != null)
{
// SE non è un commento...
if (linea.Substring(0, 1) != "#")
{
// se finisce per BIT allora processo bit-a-bit...
if (linea.EndsWith("BOOL"))
{
try
{
string[] memIdx = linea.Split(utils.CRC("testCharSep"))[0].Split('.');
// calcolo bit e byte number...
int.TryParse(memIdx[0], out byteNum);
if (memIdx.Length > 1)
{
int.TryParse(memIdx[1], out bitNum);
}
else
{
bitNum = 0;
}
}
catch
{
byteNum = 0;
bitNum = 0;
}
lastData = decodeBitData(linea, utils.CRC("testCharSep"), byteNum, 1, bitNum);
vettoreConf[numRiga] = lastData;
}
else
{
lastData = decodeOtherData(linea, utils.CRC("testCharSep"), "", 1, memSize);
vettoreConf[numRiga] = lastData;
}
numRiga++;
}
}
// salvo lunghezza file...
try
{
numVett = Convert.ToInt32(lastData.memAddr) + 1;
}
catch
{
numVett = numRiga + 1;
}
// chiudo file
file.Close();
// ora trimmo vettore al solo numero VERO dei valori caricati...
Array.Resize<otherData>(ref vettoreConf, numRiga);
lgInfo(string.Format("Fine caricamento vettore di {0} variabili per file {1}", numRiga, nomeFile));
}
/// <summary>
/// Lettura memorie conf speciali (json) ...SE inserito in [OPTPAR] come PARAM_CONF=nome.json
/// </summary>
protected virtual void loadMemConf()
{
if (DateTime.Now <= vetoReloadConf)
{
lgTrace($"Veto on loadMemConf active until {vetoReloadConf}");
}
else
{
lgInfo("loadMemConf.01");
lgInfoStartup("BEGIN loadMemConf");
// variabili x gestione send contapezzi in blocco
enableSendPzCountBlock = IOBConfFull.Counters.EnabSendPzcBlock;
minSendPzCountBlock = IOBConfFull.Counters.SendPzcBlockMin;
maxSendPzCountBlock = IOBConfFull.Counters.SendPzcBlockMax;
lgInfo("loadMemConf.02");
minRespTimeMs = IOBConfFull.Device.MinRespTimeMs;
// gruppo macchina
CodGruppoIob = IOBConfFull.General.CodGruppoIob;
// gestione completa PODL
EnabelPodlManFull = IOBConfFull.General.EnabelPodlManFull;
// abilitazione invio WDST disabilitazione controllo range valori DynData
disableWdst = IOBConfFull.General.DisabWDST;
// disabilitazione controllo range valori DynData
disDynDataRangeCheck = IOBConfFull.FluxLog.DisDynDataRangeCheck;
lgInfo("loadMemConf.03");
// se ho info in IOBConfFull uso quello...
if (IOBConfFull.Memory != null)
{
lgInfo("loadMemConf.05a");
memMap = IOBConfFull.Memory;
}
else
{
lgInfo("loadMemConf.05b");
// inizializzo LUT decodifica PARAMETRI
string jsonParams = getOptPar("PARAM_CONF");
if (!string.IsNullOrEmpty(jsonParams))
{
string jsonFileName = $"{Application.StartupPath}\\DATA\\CONF\\{jsonParams}";
lgInfoStartup($"Apertura file {jsonFileName}");
StreamReader reader = new StreamReader(jsonFileName);
string jsonData = reader.ReadToEnd();
if (!string.IsNullOrEmpty(jsonData))
{
lgInfoStartup($"File json PARAMETRI composto da {jsonData.Length} caratteri");
lgInfo("loadMemConf.04");
try
{
memMap = JsonConvert.DeserializeObject<plcMemMapExt>(jsonData);
}
catch (Exception exc)
{
lgError($"Eccezione in decodifica conf json{Environment.NewLine}{exc}");
}
}
else
{
lgError("Errore in loadMemConf: file json vuoto!");
}
reader.Dispose();
}
else
{
lgInfoStartup("loadMemConf: non trovata opzione PARAM_CONF in file INI");
}
}
// continuo setup...
setupMemMap();
setupOptMemPar();
setupFileDecod();
setupSpecialParams();
lgInfo("loadMemConf.06");
// inizializzo LUT decodifica ALLARMI
if (IOBConfFull.Alarms != null)
{
alarmMaps = IOBConfFull.Alarms.AlarmMaps;
setupAlarmMap();
// leggo il tipo gestione allarmi..
string sAlarmType = getOptJsonKVP("alarmType");
if (!string.IsNullOrEmpty(sAlarmType))
{
alarmType = (AlarmBlockType)Enum.Parse(typeof(AlarmBlockType), sAlarmType);
}
string sAlarmLMin = getOptJsonKVP("alarmLevelMin");
if (!string.IsNullOrEmpty(sAlarmLMin))
{
alarmLevelMin = (AlarmLevel)Enum.Parse(typeof(AlarmLevel), sAlarmLMin);
}
lgInfo("loadMemConf | Fine setup allarmi");
}
vetoReloadConf = DateTime.Now.AddMinutes(15);
// loggo
lgInfo($"loadMemConf.07, veto reload until {vetoReloadConf}");
lgInfoStartup($"DONE loadMemConf, veto reload until {vetoReloadConf}");
}
}
/// <summary>
/// Recupera elenco PODL assegnabili
/// </summary>
/// <returns></returns>
protected List<PODLModel> MachineNextPodl()
{
List<PODLModel> reqPOdlList = new List<PODLModel>();
// per prima cosa recupero elenco PODL da gestire....
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
var rawListPODL = await callUrl(urlGetNextPODL, false);
if (!string.IsNullOrEmpty(rawListPODL))
{
reqPOdlList = JsonConvert.DeserializeObject<List<PODLModel>>(rawListPODL) ?? new List<PODLModel>();
}
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lg.Error($"Errore: chiamata MachineNextPodl: {ex.Message}");
}
return reqPOdlList;
}
/// <summary>
/// Indica il periodo di sampling della memoria (se presente, altrimenti 0)
/// </summary>
/// <param name="memName"></param>
/// <returns></returns>
protected int memPeriod(string memName)
{
int period = 0;
if (memMap != null)
{
if (memMap.mMapRead != null)
{
//cerco in primis modalità NUM altrimenti standard...
if (memMap.mMapRead.ContainsKey(memName))
{
period = memMap.mMapRead[memName].period;
}
}
}
return period;
}
/// <summary>
/// Metodo da overridare x scrivere DAVVERO i parametri sul PLC
/// NB: deve resettare reqValue
/// </summary>
/// <param name="updatedPar"></param>
protected virtual void plcWriteParams(ref List<objItem> updatedPar)
{
// non faccio nulla di base...
}
/// <summary>
/// Esegue eventuali step a valle dell'auto ODL (in caso di esito positivo autoODL)
/// Effettuare override x casi specifici (es SiemensRama x reset contapezzi)
/// </summary>
protected virtual void processAutoOdlExtraStep()
{
// non fa nulla di default...
}
/// <summary>
/// Effettua sync dati da e verso impianto
/// </summary>
protected virtual void ProcessDataSync()
{
}
/// <summary>
/// Ponte esecuzione processi asunc
/// </summary>
/// <returns></returns>
protected virtual bool ProcessFileImport()
{
bool fatto = false;
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
fatto = await ProcessFileImportAsync();
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in ProcessFileImportAsync{Environment.NewLine}{ex.Message}");
}
return fatto;
}
/// <summary>
/// Effettua eventuale file import, archiviando file importati
/// - es gestione file excel di Giacovelli
/// - es gestione ritorno ricette FIMAT
/// </summary>
/// <returns></returns>
protected virtual async Task<bool> ProcessFileImportAsync()
{
bool answ = false;
/* ------------------------------------------
* esecuzione cablata, si potrebbe costruire una cosa più flessibile tramite conf:
* Oggetto MultiStepFileImport
* {
* StepName: Step01
* ProcType: Excel2Json
* FolderIn: xxx
* FolderOut: xxx
* FolderErr: xxx
* },{
* StepName: Step02
* ProcType: Json2RegGiac
* FolderIn: xxx
* FolderOut: xxx
* FolderErr: xxx
* }
* ------------------------------------------*/
if (!string.IsNullOrEmpty(fileImportFolder))
{
// verifico esistenza folders...
if (Directory.Exists(fileImportFolder))
{
string errore = "";
// verifico archivio
string dirArchive = Path.Combine(fileImportFolder, "archive");
baseUtils.checkDir(dirArchive);
string dirConvert = Path.Combine(fileImportFolder, "converted");
baseUtils.checkDir(dirConvert);
Dictionary<string, TimeSpan> statsColl = new Dictionary<string, TimeSpan>();
// cerco files da convertire
var listFiles2Conv = Directory.GetFiles(fileImportFolder, fileImportType);
// se li trovo --> chiamo import!
if (listFiles2Conv != null && listFiles2Conv.Length > 0)
{
// leggo file conf di riferimento
FileProcMan fpm = new FileProcMan(dirArchive, dirConvert, exclToolDirPath, "ExcImport.exe", "RefExcConv.json", "DB Loco");
int idxODL = 0;
foreach (var fileItem in listFiles2Conv)
{
// calcolo ODL x il file...
string fileName = Path.GetFileNameWithoutExtension(fileItem);
DateTime dataDoc = DateTime.Today;
DateTime.TryParse(fileName, out dataDoc);
// cerco lotto x giornata...
string sIdxODL = utils.callUrl(urlGetOdlAtDate(dataDoc));
int.TryParse(sIdxODL, out idxODL);
// chiamo conversione
TimeSpan timeElaps = fpm.doProcess(fileItem, idxODL);
statsColl.Add($"ExcImport conversion executed for {fileItem}", timeElaps);
lgTrace(($"ExcImport conversion executed for {fileItem} | {timeElaps.Milliseconds}ms"));
}
}
// cerco i file json x invio
var listFiles2Upload = Directory.GetFiles(dirConvert, "*.json");
// se li trovo --> chiamo import!
if (listFiles2Upload != null && listFiles2Upload.Length > 0)
{
foreach (var fileItem in listFiles2Upload)
{
// leggo il file json
Dictionary<int, BatchRec> list2Send = new Dictionary<int, BatchRec>();
string rawData = File.ReadAllText(fileItem);
if (!string.IsNullOrEmpty(rawData))
{
var convData = JsonConvert.DeserializeObject<Dictionary<int, BatchRec>>(rawData);
if (convData != null)
{
list2Send = convData;
// ora accodo contenuto file json...
accodaRawData(rawTransfType.RegGiacenze, list2Send);
}
}
// processo invio: provo forzare send...
answ = await SvuotaCodaRawTransfAsync(true);
if (!answ)
{
errore = $"Errore in fase di invio dati SvuotaCodaRawTransfAsync x {fileItem}";
}
// archivio il file
else
{
// sposto file...
File.Move(fileItem, $"{dirArchive}/{Path.GetFileName(fileItem)}");
// fixme todo !!! eliminare json?!?!?
}
// log eventuali errori
if (!answ)
{
string dirError = $"{fileImportFolder}/errors";
baseUtils.checkDir(dirError);
// sposto file...
File.Move(fileItem, $"{dirError}/{Path.GetFileName(fileItem)}");
// segno errore!
string fileError = $"{fileImportFolder}/errors.log";
File.AppendAllText(fileError, errore);
}
}
}
}
}
return answ;
}
/// <summary>
/// Processa le richieste di scrittura memoria
/// </summary>
/// <returns></returns>
protected string processMemWriteRequests()
{
string answ = "";
// li salvo nei parametri in memoria locale (ogni adapter DOVREBBE salvare POI sul VERO PLC)
List<objItem> writeList = new List<objItem>();
List<objItem> updatedPar = new List<objItem>();
string currOut = "";
// recupero elenco delle cose da fare
string resp = getParams2write();
if (!string.IsNullOrEmpty(resp))
{
try
{
writeList = JsonConvert.DeserializeObject<List<objItem>>(resp);
// se ho da fare chiamo esecuzione..
if (writeList.Count > 0)
{
foreach (var item in writeList)
{
// scrivo in memoria
if (memMap.mMapWrite.ContainsKey(item.uid))
{
memMap.mMapWrite[item.uid].value = item.reqValue;
currOut = $" | Parameter {item.uid} | {item.value} --> {item.reqValue}";
// sistemo valori
item.value = item.reqValue;
// forzo variabile writeable
item.writable = true;
lgInfo($"Richiesta update parametro {currOut}");
// salvo in lista da ritrasmettere
updatedPar.Add(item);
}
else
{
currOut = $" | Error: parameter {item.uid} not found";
lgError($"Errore {currOut}");
}
// accodo in stringa taskVal...
answ += currOut;
}
// richiamo scrittura parametri su PLC
plcWriteParams(ref updatedPar);
// invio su cloud parametri!
string rawData = JsonConvert.SerializeObject(updatedPar);
utils.callUrl($"{urlUpdateWriteParams}", rawData);
lgInfo($"Notifica a server scrittura {updatedPar.Count} parametri");
}
}
catch (Exception exc)
{
lgError($"Eccezione in processMemWriteRequests:{Environment.NewLine}{exc}");
}
}
else
{
lgError("Non è stata ricevuta risposta x task da eseguire");
}
return answ;
}
/// <summary>
/// Wrapper sync process other info
/// </summary>
/// <param name="keyReq"></param>
/// <param name="valReq"></param>
/// <returns></returns>
protected virtual bool ProcessOtherInfo(string keyReq, string valReq)
{
bool fatto = false;
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
fatto = await ProcessOtherInfoAsync(keyReq, valReq);
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in ProcessOtherInfo | keyReq: {keyReq} | valReq: {valReq}{Environment.NewLine}{ex.Message}");
}
return fatto;
}
/// <summary>
/// Cerca di recuperare i file generati dall'impianto in merito al processing degli ordini
/// </summary>
/// <returns></returns>
/// <summary>
/// Processing di dati "OtherInfo" da implementare caso x caso (qui riportato caso FIMAT ricette...)
/// </summary>
/// <param name="keyReq"></param>
/// <param name="valReq"></param>
protected virtual async Task<bool> ProcessOtherInfoAsync(string keyReq, string valReq)
{
bool answ = false;
// test file import da conf... se hasRecipe=true --> modalità Tenditalia/FIMAT...
if (hasRecipe)
{
string archBasePath = Path.Combine(pathList["fullPath-locBase"], pathList["fullPath-03-Recv"]);
string reportBasePath = pathList["fullPath-outReport"];
baseUtils.checkDir(reportBasePath);
// verifico x cosa è stato richiesto attività (che esista la folder...)
string folderPath = Path.Combine(archBasePath, keyReq);
if (Directory.Exists(folderPath))
{
// calcolo il nome zip di destinazione finale
int anno = DateTime.Today.Year;
int.TryParse(keyReq.Substring(0, 4), out anno);
string zipDir = Path.Combine(archBasePath, $"{anno}");
baseUtils.checkDir(zipDir);
string zipName = Path.Combine(zipDir, $"{keyReq}.zip");
// gestione report OUT
string reportPath = Path.Combine(reportBasePath, $"{keyReq}");
// verifico il tipo di attività e procedo
switch (valReq)
{
case "Arch":
// solo archiviazione ZIP della folder...
answ = doZipArchiveFolder(folderPath, zipName);
// se sent --> elimino record da REDIS (locale e remoto)
if (answ)
{
await RecipeRemoveWeekStatus(keyReq);
}
break;
case "SendArch":
// preparazione file di resoconto contumi
answ = RecipeDoConsumeReport(folderPath, reportPath);
if (answ)
{
// infine archiviazione ZIP della folder...
bool okArchive = doZipArchiveFolder(folderPath, zipName);
// se sent --> elimino record da REDIS (locale e remoto)
if (okArchive)
{
await RecipeRemoveWeekStatus(keyReq);
}
}
break;
default:
break;
}
}
}
return answ;
}
protected virtual async Task<bool> ProcessRecipeFileRetAsync()
{
bool answ = false;
// test file import da conf... se hasRecipe=true --> modalità Tenditalia/FIMAT...
if (hasRecipe)
{
if (pathList.Count == 0)
{
lg.Debug("Attenzione: pathList vuoto!!!");
}
else
{
// recupera i NUOVI file e li sposta in folder locale temp
string remoPath = Path.Combine(pathList["fullPath-locBase"], pathList["fullPath-05-remExe"]);
string archBasePath = Path.Combine(pathList["fullPath-locBase"], pathList["fullPath-03-Recv"]);
string tempPath = Path.Combine(archBasePath, "TEMP");
baseUtils.checkDir(tempPath);
bool okRetrieve = RecipeTaskDoneRetrieve(remoPath, tempPath);
// ora cerca nella folder locale e processa i files...
bool okCheck = await RecipeDoCheckFileProc(tempPath, archBasePath);
// verifica se sia da creare/inviare il record settimanale di consumo (dopo la
// mezzanotte del lunedì inizio settimana)
bool okSent = RecipeDoProcCons();
}
}
return answ;
}
/// <summary>
/// Sync archivio ricette (Macchina -- MES)
/// </summary>
/// <returns></returns>
protected virtual bool processRecipeSyncArch()
{
bool answ = false;
// test file import da conf... se hasRecipe=true --> modalità Tenditalia/FIMAT...
if (hasRecipe)
{
DateTime adesso = DateTime.Now;
// verifico veto sync ricette (x non ripetere troppo spesso)
if (adesso > dtVetoCheckSyncRecipe)
{
// effettua sync eventuali NUOVI file ricette
bool okSync = RecipeDoSyncRecipe();
// imposto veto a 1h...
dtVetoCheckSyncRecipe = adesso.AddHours(1);
}
}
return answ;
}
/// <summary>
/// Effettua task òettura a bassa velocità
/// </summary>
/// <returns></returns>
protected virtual bool processSlowDataRead()
{
bool answ = false;
return answ;
}
protected void raiseRefresh(newDisplayData currDispData)
{
if (currDispData != null)
{
if (currDispData.hasData)
{
// segnalo refresh!
if (eh_refreshed != null)
{
eh_refreshed(this, new iobRefreshedEventArgs(currDispData));
}
}
}
}
/// <summary>
/// Registra i file in directory suddivise x anno/settimana e restitusice elenco
/// </summary>
/// <param name="recipeFile">Percorso file XML</param>
/// <param name="archBasePath">Percorso base archivi</param>
/// <returns></returns>
protected List<string> RecipeDoArchiveWeek(string recipeFile, string archBasePath)
{
List<string> weeksProc = new List<string>();
// init var
int week = 0;
string archPath = "";
Calendar cal = new CultureInfo("it-IT").Calendar;
// leggo contenuto XML
string rawData = File.ReadAllText(recipeFile);
// deserializza file XML x recuperare righe consumo
XmlSerializer serializer = new XmlSerializer(typeof(ARecipe));
using (StringReader reader = new StringReader(rawData))
{
try
{
var currRecipe = (ARecipe)serializer.Deserialize(reader);
if (currRecipe != null)
{
// recupero data-ora ricetta da campo string
DateTime recExeDt = DateTime.Today;
DateTime.TryParse(currRecipe.DesRecipe.DesData.Date, out recExeDt);
// calcolo week da data-ora file...
week = cal.GetWeekOfYear(recExeDt, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
// verifico se ci sia la settimana indicata in elenco...
string currWeek = $"{recExeDt:yyyy}-{week:00}";
if (!weeksProc.Contains(currWeek))
{
weeksProc.Add(currWeek);
}
// sposto file x in area week
string rName = Path.GetFileName(recipeFile);
archPath = Path.Combine(archBasePath, $"{recExeDt:yyyy}-{week:00}");
baseUtils.checkDir(archPath);
string destPath = Path.Combine(archPath, rName);
// sposto files
File.Move(recipeFile, destPath);
}
}
catch (Exception exc)
{
lgError($"Eccezione in RecipeDoArchiveWeek{Environment.NewLine}{exc}");
}
}
return weeksProc;
}
/// <summary>
/// Verifica i file recuperati
/// - PODL da inviare a MAPO
/// - accumulo consumo materiali
/// </summary>
/// <param name="localPath"></param>
/// <param name="archBasePath"></param>
/// <returns></returns>
protected virtual async Task<bool> RecipeDoCheckFileProc(string localPath, string archBasePath)
{
bool answ = false;
List<string> weekProcNew = new List<string>();
// recupero elenco files...
var fileList = Directory.GetFiles(localPath);
if (fileList != null && fileList.Count() > 0)
{
foreach (var recipeFile in fileList)
{
// processo eventuale apertura/chiusura PODL
bool okPOdl = RecipeDoProcessPODL(recipeFile);
// processa i file archiviando x settimana
var procWeeks = RecipeDoArchiveWeek(recipeFile, archBasePath);
// aggiungo all'elenco settimane SE mancassero le sett inserite
foreach (var item in procWeeks)
{
if (!weekProcNew.Contains(item))
{
weekProcNew.Add(item);
}
}
}
if (weekProcNew.Count > 0)
{
// predispongo azioni di base ammesse
Dictionary<string, string> redHashWeek = new Dictionary<string, string>();
Dictionary<string, string> stdActList = new Dictionary<string, string>();
stdActList.Add("Arch", "Archivia");
stdActList.Add("SendArch", "Invia + Archivia");
List<PeriodInfo> weekHashUpdate = new List<PeriodInfo>();
// aggiorna in REDIS (hashList) status delle settimane coinvolte
foreach (var cWeek in weekProcNew)
{
string weekPath = Path.Combine(archBasePath, $"{cWeek}");
var weekFileList = Directory.GetFiles(weekPath, "*.xml");
PeriodInfo cPerInfo = new PeriodInfo()
{
NumFilesReceived = weekFileList.Count(),
CurrStatus = "Ricevute",
ActionList = stdActList
};
string rawWeek = JsonConvert.SerializeObject(cPerInfo);
redHashWeek.Add(cWeek, rawWeek);
}
// salvo in redis...
string fullKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:WeekStats");
var okHashDict = redisMan.redSaveHashDict(fullKey, redHashWeek);
// invio ANCHE in MP-IO l'update delle info...
string remUrl = urlSetHashDict;
string dictPayload = JsonConvert.SerializeObject(redHashWeek);
//await callUrlWithPayloadAsync(remUrl, dictPayload, false);
await callUrlWithPayloadAsync(remUrl, dictPayload, true);
}
}
return answ;
}
/// <summary>
/// Verifica se si debba registrare/inviare il report periodico di consumo indicato
/// </summary>
/// <returns></returns>
protected bool RecipeDoProcCons()
{
bool answ = false;
// calcolo quale sia la settimana corrente...
int week = 0;
DateTime adesso = DateTime.Now;
Calendar cal = new CultureInfo("it-IT").Calendar;
// calcolo week da data-ora file...
week = cal.GetWeekOfYear(adesso, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
// verifico se ci sia la settimana indicata in elenco...
string currWeek = $"{adesso:yyyy}-{week:00}";
// recupero elenco delle settimane da processare da redis/WeekStats
string fullKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:WeekStats");
Dictionary<string, string> currStats = redisMan.redGetHashDict(fullKey);
if (currStats != null && currStats.Count > 0)
{
// definisco il DICT delle settimane da processare (= settimane passate, NON la corrente...)
foreach (var weekData in currStats)
{
if (weekData.Key != currWeek)
{
// ...x ogni settimana PASSATA effettua SendArch... --> lancia un task!
ProcessOtherInfo(weekData.Key, "SendArch");
}
}
answ = true;
}
return answ;
}
/// <summary>
/// Acquisisce eventuali nuove ricette
/// </summary>
/// <returns></returns>
protected bool RecipeDoSyncRecipe()
{
bool answ = false;
DateTime asdesso = DateTime.Now;
try
{
string recLocalArchPath = Path.Combine(pathList["fullPath-locBase"], pathList["fullPath-00-Arch"]);
string recRemArchPath = pathList["fullPath-06-remRec"];
// leggo da redis (se disponibile) elenco hast file/MD5....
string fullKey = redisMan.redHash($"IOB:Remote:{IOBConfFull.General.FilenameIOB}:FileCheck");
Dictionary<string, string> dictRecMd5 = redisMan.redGetHashDict(fullKey);
// leggo la directory remota...
var remoteList = Directory.GetFiles(recRemArchPath);
string fileName = "";
string newFilePath = "";
bool doAcquire = false;
int recNum = 0;
int newNum = 0;
string currMd5;
foreach (var remoteFile in remoteList)
{
recNum = 0;
doAcquire = false;
// verifico MD5...
currMd5 = baseUtils.GetFileMd5(remoteFile);
fileName = Path.GetFileName(remoteFile);
// verifo se c'è il file...
if (!dictRecMd5.ContainsKey(fileName))
{
dictRecMd5.Add(fileName, currMd5);
doAcquire = true;
}
else
{
//// verifico SE sia modificato da meno di 30 gg...
//DateTime lastMod = File.GetLastWriteTime(remoteFile);
if (dictRecMd5[fileName] != currMd5)
{
dictRecMd5[fileName] = currMd5;
doAcquire = true;
}
}
// se devo aggiungere processo --> fix numRicetta + 10'000
if (doAcquire)
{
answ = true;
// leggo contenuto...
string recContent = File.ReadAllText(remoteFile);
// calcolo nuovo nome
var namePart = fileName.Split('.');
int.TryParse(namePart[0], out recNum);
newNum = recNum + 10000;
// modifico ricetta
recContent = recContent.Replace($"<RecName>{namePart[0]}</RecName>", $"<RecName>{newNum}</RecName>");
// salvo ricetta modificata in archivio
newFilePath = Path.Combine(recLocalArchPath, $"{newNum}.xml");
// elimino eventuale file precedente...
if (File.Exists(newFilePath))
{
File.Delete(newFilePath);
}
File.WriteAllText(newFilePath, recContent);
}
}
// se ho importato qualcosa...
if (answ)
{
// salvo hash dei files...
redisMan.redSaveHashDict(fullKey, dictRecMd5);
}
}
catch (Exception exc)
{
lgError($"Eccezione durante RecipeDoSyncRecipe{Environment.NewLine}{exc}");
}
return answ;
}
/// <summary>
/// restitusice i record di consumo dei materiali associati al file:
/// </summary>
/// <param name="recipeFile">Percorso file XML</param>
/// <returns></returns>
protected List<ConsRec> RecipeGetCons(string recipeFile)
{
List<ConsRec> listConsumi = new List<ConsRec>();
// leggo contenuto XML
string rawData = File.ReadAllText(recipeFile);
// deserializza file XML x recuperare righe consumo
XmlSerializer serializer = new XmlSerializer(typeof(ARecipe));
using (StringReader reader = new StringReader(rawData))
{
try
{
var currRecipe = (ARecipe)serializer.Deserialize(reader);
if (currRecipe != null)
{
if (currRecipe.ColRecipe != null && currRecipe.ColRecipe.Count > 0)
{
List<ColData> RigheConsumi = new List<ColData>();
foreach (var rigaComp in currRecipe.ColRecipe)
{
ColData datiComp = rigaComp.ColData;
RigheConsumi.Add(datiComp);
}
// recupero data-ora ricetta da campo string
DateTime recExeDt = DateTime.Today;
DateTime.TryParse(currRecipe.DesRecipe.DesData.Date, out recExeDt);
// converto le righe di consumo ColData in ConsRec
listConsumi = RigheConsumi.Select(x => new ConsRec()
{
FileRef = recipeFile,
DtRif = recExeDt,
ColourCode = x.ColourCode,
Description = x.Description,
WeightGrTot = x.Weightgrtotal
}).ToList();
}
}
}
catch (Exception exc)
{
lgError($"Eccezione in RecipeGetCons{Environment.NewLine}{exc}");
}
}
return listConsumi;
}
/// <summary>
/// Prepara la ricetta indicata partendo dai dati della ricetta template + dati ODL di riferimento
/// </summary>
/// <param name="recFilePath">Path del file template della ricetta</param>
/// <param name="podlReq">Record del PODL da inviare</param>
/// <returns>Path della ricetta da inviare</returns>
protected virtual bool RecipePrepare(string tempPath, string recFilePath, PODLModel podlReq)
{
bool fatto = false;
// leggo il file template ricetta
var rawData = File.ReadAllText(recFilePath);
string fixContent = rawData;
// eseguo sostituzioni
foreach (var rule in RecipeReplRules)
{
string replVal = rule.Value;
// calcolo la sostituzione per i valori SPECIFICI...
if (replVal.Contains("{{PODL}}"))
{
replVal = replVal.Replace("{{PODL}}", $"PODL{podlReq.IdxPromessa:00000000}");
}
if (replVal.Contains("{{Qty}}"))
{
replVal = replVal.Replace("{{Qty}}", $"{podlReq.NumPezzi}");
}
if (replVal.Contains("{{Note}}"))
{
replVal = replVal.Replace("{{Note}}", $"{podlReq.Note}");
}
fixContent = fixContent.Replace(rule.Key, replVal);
}
// scrivo file (secondo quantità...)!
string destPath = Path.Combine(tempPath, $"PODL{podlReq.IdxPromessa:00000000}.xml");
if (podlReq.NumPezzi <= maxQtyPerFile)
{
File.WriteAllText(destPath, fixContent);
fatto = true;
}
else
{
int numReq = (int)Math.Ceiling((double)podlReq.NumPezzi / maxQtyPerFile);
// splitto e scrivo...
for (int i = 1; i <= numReq; i++)
{
destPath = Path.Combine(tempPath, $"PODL{podlReq.IdxPromessa:00000000}-{i}.xml");
File.WriteAllText(destPath, fixContent);
}
fatto = true;
}
return fatto;
}
/// <summary>
/// Prepara le ricette dato fullPath temp scaricando elenco PODL
/// </summary>
/// <param name="tempPath">
/// Percorso temp di appoggio x preparare ricette (compreso nome macchina)
/// </param>
/// <param name="useLocal">
/// Indica se usare le copie locali delle ricette oppure richiedere da remoto (REST get)
/// </param>
/// <returns></returns>
protected virtual bool RecipeReqWriteLocal(string tempPath, bool useLocal)
{
bool fatto = false;
lgTrace($"RecipeReqWriteLocal start | tempPath {tempPath} | useLocal {useLocal}");
List<PODLModel> reqPOdlList = new List<PODLModel>();
// per prima cosa recupero elenco PODL da gestire....
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
var rawListPODL = await callUrl(urlGetNextPODL, false);
if (!string.IsNullOrEmpty(rawListPODL))
{
try
{
reqPOdlList = JsonConvert.DeserializeObject<List<PODLModel>>(rawListPODL);
}
catch (Exception exc)
{
lg.Error($"Errore: chiamata elenco PODL ha restituito errore{Environment.NewLine}{exc}");
}
}
else
{
lg.Error($"Errore: chiamata elenco PODL ({urlGetNextPODL}) ha restituito valore vuoto");
}
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in RecipeReqWriteLocal{Environment.NewLine}{ex.Message}");
}
// proseguo se ne ho trovati...
if (reqPOdlList.Count > 0)
{
// in questo elenco devo considerare solo PODL ATTIVI...
List<PODLModel> actPOdlList = reqPOdlList.Where(x => x.Attivabile).ToList();
// da questo elenco,se è rimasto qualcosa, ciclo x ricette da inviare
foreach (var actPodlReq in actPOdlList)
{
// devo escludere i PODL già gestiti
if (!POdlSentList.ContainsKey(actPodlReq.IdxPromessa))
{
string recFilePath = "";
try
{
// calcolo fullPath ricetta...
recFilePath = Path.Combine(pathList["fullPath-locBase"], pathList["fullPath-00-Arch"], actPodlReq.Recipe);
// preparo file ricetta x PODL
bool fileOk = RecipePrepare(tempPath, recFilePath, actPodlReq);
if (fileOk)
{
// aggiungo PODL ad elenco dei processati
POdlSentList.Add(actPodlReq.IdxPromessa, actPodlReq);
// indico okReport (almeno per 1 è ok...)
fatto = true;
}
}
catch (Exception exc)
{
lgError($"recipeReqWriteLocal | Errore durante ciclo verifica ricetta | idxPODL {actPodlReq.IdxPromessa} | recFilePath {recFilePath}{Environment.NewLine}{exc}");
}
}
}
// salvo su file elenco PODL gestiti/inviati
POdlSentFileArch = POdlSentList;
lgInfo($"RecipeReqWriteLocal | reqPOdlList: {reqPOdlList.Count} | actPOdlList: {actPOdlList.Count} | preparate: {POdlSentList.Count}");
}
return fatto;
}
/// <summary>
/// Effettua invio delle ricette alla macchina
/// </summary>
/// <param name="localPath">Area dove si trovano i fiel da trasmettere</param>
/// <param name="localArch">Area archivio</param>
/// <param name="remotePath">Area remota dove inviare files</param>
/// <returns></returns>
protected bool RecipeSend(string localPath, string localArch, string remotePath)
{
bool fatto = false;
lgTrace($"RecipeSend start | localPath {localPath} | localArch {localArch} | remotePath {remotePath}");
// recupero elenco files...
var fileList = Directory.GetFiles(localPath);
string archName = "";
string destName = "";
if (fileList != null && fileList.Count() > 0)
{
int nMove = 0;
foreach (var file in fileList)
{
string fileName = Path.GetFileName(file);
archName = Path.Combine(localArch, fileName);
destName = Path.Combine(remotePath, fileName);
lgInfo($"RecipeSend | fileName: {fileName} | archName: {archName} | destName: {destName} ");
File.Copy(file, destName, true);
File.Move(file, archName);
nMove++;
}
fatto = true;
lgInfo($"RecipeSend | trovate {fileList.Count()} file | {nMove} trasferiti");
}
return fatto;
}
/// <summary>
/// Effettua recupero in locale dei task eseguiti dalla macchina
/// </summary>
/// <param name="remotePath">Area remota da dove recuperare files</param>
/// <param name="localPath">Area dove parcheggiare temporaneamente i fiels x processing</param>
/// <returns></returns>
protected virtual bool RecipeTaskDoneRetrieve(string remotePath, string localPath)
{
bool fatto = false;
// recupero elenco files...
var fileList = Directory.GetFiles(remotePath);
string destName = "";
if (fileList != null && fileList.Count() > 0)
{
foreach (var file in fileList)
{
string fileName = Path.GetFileName(file);
destName = Path.Combine(localPath, fileName);
lgInfo($"Recupero Task Eseguiti | fileName: {fileName} | destName: {destName} ");
File.Move(file, destName);
}
fatto = true;
}
return fatto;
}
/// <summary>
/// Cancella dal server i task eseguiti
/// </summary>
/// <param name="taskName"></param>
/// <param name="esitoTask"></param>
/// <param name="codTav"></param>
/// <returns></returns>
protected async Task<string> remTask2exe(string taskName, string esitoTask, string codTav)
{
string answ = "";
bool srvAlive = await CheckServerAliveAsync();
if (srvAlive)
{
string url2call = $"{urlRemTask2Exe}{taskName}";
if (!string.IsNullOrEmpty(codTav))
{
url2call = $"{urlRemTask2ExeTav(codTav)}{taskName}";
}
#if false
try
{
Task.Run(async () => answ = await utils.callUrlAsync(url2call))
.GetAwaiter()
.GetResult();
lgInfo($"Task2Exe.remTask2exe | {esitoTask} | chiamata URL {url2call} | answ: {answ}");
}
catch (Exception ex)
{
lgError("ProcessAutoOdl | Crash nel ponte Sync/Async: " + ex.Message);
}
#endif
await utils.callUrlAsync(url2call);
#if false
answ = utils.callUrlNow(url2call);
#endif
}
return answ;
}
/// <summary>
/// Invio reset allarmi all'avvio per sicurezza...
/// </summary>
protected void SendAlarmReset()
{
if (alarmMaps != null)
{
// leggo a ciclo le aree degli allarmi CONFIGURATI, se ne trovo processo
foreach (var item in alarmMaps)
{
// banchi in array Int16 --> scompongo
for (int i = 0; i < item.size / 2; i++)
{
// INVIO CON LAST = 1 E NEW = 0 PER FORZARE INVIO...
sendAlarmVariations(item.memAddr, i, 1, 0, item.messages);
}
}
}
}
/// <summary>
/// Processa chiusura ODL
/// </summary>
/// <param name="idxOdl">Idx dell'ODL da chiudere</param>
/// <param name="dtRif">DataOra di riferimento evento stop</param>
protected async Task<bool> SendCloseOdl(int idxOdl, DateTime dtRif)
{
bool answ = false;
DateTime dtCurr = DateTime.Now;
string resp = await callUrl($"{urlODLClose}{idxOdl}&dtEve={dtRif}&dtCurr={dtCurr}", false);
answ = resp == "OK";
return answ;
}
/// <summary>
/// Processa chiusura PODL
/// </summary>
/// <param name="idxPOdl">Idx del PODL da chiudere</param>
/// <param name="dtRif">DataOra di riferimento evento stop</param>
protected bool SendClosePOdl(int idxPOdl, DateTime dtRif)
{
bool answ = false;
DateTime dtCurr = DateTime.Now;
string fullUrl = $"{urlPODLClose}{idxPOdl}&dtEve={dtRif:yyyyMMddHHmmssfff}&dtCurr={dtCurr:yyyyMMddHHmmssfff}";
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
// invio chiamata URL x chiusura ODL su macchina
string callResp = await callUrl(fullUrl, false);
answ = callResp == "OK";
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in SendClosePOdl | {Environment.NewLine}{ex.Message}");
}
return answ;
}
/// <summary>
/// Verifica se l'invio di un dato flux log sia abilitato o vietato secondo configurazione
/// - verifica rpesenza di un veto preesistente non scaduto
/// - se manca. impone nuovo veto dal "period" impostato in aree memMap (default: 60 sec)
/// </summary>
/// <param name="codFlux"></param>
/// <returns></returns>
protected bool sendEnabledFLog(string codFlux)
{
int vetoSec = vetoCodFluxDur(codFlux);
DateTime adesso = DateTime.Now;
bool hasVeto = vetoSendFLog.ContainsKey(codFlux);
// se ho un veto controllo scadenza...
if (hasVeto)
{
hasVeto = vetoSendFLog[codFlux] >= adesso;
// se fosse scaduto --> imposto nuovo...
if (!hasVeto)
{
vetoSendFLog[codFlux] = adesso.AddSeconds(vetoSec);
}
}
// se NON ci fosse veto o fosse scaduto --> imposto nuovo...
else
{
vetoSendFLog.Add(codFlux, adesso.AddSeconds(vetoSec));
}
// restituisco send enabled = NON ha veto
return !hasVeto;
}
/// <summary>
/// Invia informazioni associazione IOB 2 machine
/// </summary>
protected void SendM2IOB()
{
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () => await SendM2IobAsync())
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in SendM2IOB: {ex.Message}");
}
}
/// <summary>
/// Invia informazioni associazione IOB 2 machine
/// </summary>
protected async Task SendM2IobAsync()
{
bool srvAlive = await CheckServerAliveAsync();
if (srvAlive)
{
lgInfo("chiamata URL " + urlSetM2IOB);
await utils.callUrlAsync(urlSetM2IOB);
#if false
utils.callUrlNow(urlSetM2IOB);
#endif
}
}
/// <summary>
/// Ponte Async invio MachineConf
/// </summary>
protected virtual void SendMachineConf()
{
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () => await SendMachineConfAsync())
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in SendMachineConf: {ex.Message}");
}
}
/// <summary>
/// Effettua invio al server IO dei dati di IOB + macchina:
/// - dati dell'IOB (IdxMacchina, IobName, IobIp, Tipo, counter...) "classici"
/// - dati di configurazione IOB "extra", ad esempio
/// - IobType (FANUC, Siemens, ModBus...)
/// - MachineIp
/// - MachinePort
/// - Abilitazione gestione FOLDER di ritorno x SPEC (es x FTP)
/// </summary>
protected virtual async Task SendMachineConfAsync()
{
// se non espressamente vietato da conf...
if (enabSendMachineConf)
{
// preparo dizionario da inviare
Dictionary<string, string> currDict = new Dictionary<string, string>();
Assembly entryAssembly = Assembly.GetEntryAssembly();
if (IOBConfFull != null)
{
// creo genObj dati necessari...
currDict.Add("IobName", IOBConfFull.General.FilenameIOB);
currDict.Add("IobType", $"{IOBConfFull.General.IobType}");
currDict.Add("MachIp", IOBConfFull.Device.Connect.IpAddr);
currDict.Add("MachPort", IOBConfFull.Device.Connect.Port);
currDict.Add("Vendor", IOBConfFull.Device.Vendor);
currDict.Add("Model", IOBConfFull.Device.Model);
currDict.Add("IobExe", $"{entryAssembly?.GetName().Name}");
currDict.Add("IobVersion", IOBConfFull.General.RelVers);
if (IOBConfFull.OptPar != null)
{
var ordPar = IOBConfFull.OptPar.OrderBy(x => x.Key).ToList();
foreach (var item in ordPar)
{
currDict.Add($"OP_{item.Key}", item.Value);
}
}
// invio e salvo...
string remUrl = urlSaveMachIobConf;
string dictPayload = JsonConvert.SerializeObject(currDict);
//await callUrlWithPayloadAsync(remUrl, dictPayload, false);
await callUrlWithPayloadAsync(remUrl, dictPayload, true);
lgTrace("Invio MachineConf effettuato");
}
}
}
/// <summary>
/// Invia al server IO i valori dei parametri opzionali (es counters)
/// </summary>
/// <param name="paramName">Nome parametro</param>
/// <param name="paramValue">Valore parametro</param>
protected async Task sendOptVal(string paramName, string paramValue)
{
// salvo comunque in fluxLog...
bool sent = accodaFLog(paramName, $"{paramName}|{paramValue}", qEncodeFLog(paramName, paramValue));
if (sent)
{
// traccio valore DynData x analisi
trackDynData(paramName, paramValue);
bool srvAlive = await CheckServerAliveAsync();
if (srvAlive)
{
string url2call = $"{urlSetOptVal}pName={paramName}&pValue={paramValue}";
lgInfo("chiamata URL " + url2call);
await utils.callUrlAsync(url2call);
#if false
utils.callUrlNow(url2call);
#endif
}
}
}
/// <summary>
/// Invia al server IO i valori dei parametri opzionali (es counters)
/// </summary>
/// <param name="paramName">Nome parametro</param>
/// <param name="paramValueInt">Valore parametro INT</param>
protected async Task sendOptVal(string paramName, int paramValueInt)
{
// override!
await sendOptVal(paramName, $"{paramValueInt}");
}
/// <summary>
/// Invio contapezzi alla dataora indicata
/// </summary>
/// <param name="numPz">Num pezzi da registrare</param>
/// <param name="dtRif">DataOra di riferimento evento</param>
protected bool SendPzIncrAtDate(int numPz, DateTime dtRif)
{
bool answ = false;
DateTime dtCurr = DateTime.Now;
// chiamata avvio PODL (a posteriori)
string fullUrl = $"{urlAddPzCountAtDate}{numPz}&dtEve={dtRif:yyyyMMddHHmmssfff}&dtCurr={dtCurr:yyyyMMddHHmmssfff}";
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
// invio chiamata URL x chiusura ODL su macchina
string callResp = await callUrl(fullUrl, false);
answ = callResp == "OK";
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in SendPzIncrAtDate | {Environment.NewLine}{ex.Message}");
}
return answ;
}
/// <summary>
/// Processa avvio PODL (ed eventuale chiusura precedente)
/// </summary>
/// <param name="idxPOdl">Idx del PODL da avviare</param>
/// <param name="dtRif">DataOra di riferimento evento start</param>
protected bool SendStartPodl(int idxPOdl, DateTime dtRif)
{
bool answ = false;
DateTime dtCurr = DateTime.Now;
// chiamata avvio PODL (a posteriori)
string fullUrl = $"{urlOdlStartFromPOdl}{idxPOdl}&dtEve={dtRif:yyyyMMddHHmmssfff}&dtCurr={dtCurr:yyyyMMddHHmmssfff}";
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
// invio chiamata URL x chiusura ODL su macchina
string callResp = await callUrl(fullUrl, false);
answ = callResp == "OK";
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in SendStartPodl | {Environment.NewLine}{ex.Message}");
}
return answ;
}
/// <summary>
/// Invia messaggio a logWatcher
/// </summary>
/// <param name="messType"></param>
/// <param name="message"></param>
protected void sendToTaskWatch(string messType, string message, string codTav)
{
string logMsg = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} | {messType} | {message}";
if (string.IsNullOrEmpty(codTav))
{
logMsg += $" | {messType} | {message}";
}
else
{
logMsg += $" | {codTav} | {messType} | {message}";
}
parentForm.taskWatcher = logMsg;
}
/// <summary>
/// Impostazioni parametri PLC
/// </summary>
protected virtual void setParamPlc()
{
loadMemConf();
fixDefaultPar();
if (resetAlarmOnStart)
{
SendAlarmReset();
}
}
/// <summary>
/// setup gestione allarmi da conf
/// </summary>
protected void setupAlarmMap()
{
// indico quanti allarmi
foreach (var item in alarmMaps)
{
item.setupData();
// se ho in redis una tab di allarmi precedenti la carico...
string alarmHash = redisMan.redHash($"IOB:ALARM_STATUS:{IOBConfFull.General.FilenameIOB}:{item.memAddr}");
string rawVal = redisMan.getRSV(alarmHash);
if (!string.IsNullOrEmpty(rawVal))
{
// provo a convertire
var lastState = JsonConvert.DeserializeObject<uint[]>(rawVal);
if (lastState != null && lastState.Length > 0)
{
item.loadPrev(lastState);
}
}
// loggo
lgDebug($"Decodifica aree alarmMap: {item.description} | {item.memAddr} x {item.size} byte | {item.messages.Count} messaggi allarme");
}
// SE NECESSARIO ALL'AVVIO INVIO DATI VUOTI...
if (resetAlarmOnStart)
{
SendAlarmReset();
}
// invio oggetto alarmMap al server x successiva decodifica
// FIXME TODO FARE !!!! invio PUT del file *_alarm.json
}
/// <summary>
/// Setup parametri decode file excel (opzionale)
/// </summary>
protected virtual void setupFileDecod()
{
if (memMap == null)
{
lgError($"setupFileDecod | memMap nullo");
}
else
{
// verifica se siano necessari configuraizoni speciali dalla excDecod:
// es: lettura/invio file excel
if (memMap.FileDecod != null && memMap.FileDecod.Count > 0)
{
FileDecod = memMap.FileDecod;
}
}
}
/// <summary>
/// setup parametri da file di conf
/// </summary>
protected void setupMemMap()
{
bool serverOk = GetPingStatus() == IPStatus.Success;
if (memMap == null)
{
lgError($"setupMemMap | memMap nullo");
}
else
{
lgDebug($"setupMemMap | trovati {memMap.mMapRead.Count} parametri Read (TSVC)");
lgDebug($"setupMemMap | trovati {memMap.mMapWrite.Count} parametri Write");
if (utils.CRB("verbose"))
{
string rawMemConf = JsonConvert.SerializeObject(memMap, Formatting.Indented);
lgDebug($"setupMemMap | configurazione memoria R/W:{Environment.NewLine}{rawMemConf}");
}
// se ho variabili read --> genero dati TSVC...
if (memMap.mMapRead.Count > 0)
{
TSVC_Data.Clear();
LastTSVC.Clear();
VCData currConf;
int periodo = 0;
VC_func funz = VC_func.POINT;
// accodo nella conf...
foreach (var item in memMap.mMapRead)
{
funz = item.Value.func;
periodo = item.Value.period;
currConf = new VCData()
{
Funzione = funz,
Period = periodo,
UM = item.Value.unit,
// imposto a NOW per forzare accumulo dati prima di inviare troppo presto (es COMECA Pizzaferri valori 0)
DTStart = DateTime.Now,
//DTStart = DateTime.Now.AddHours(-1),
dataArray = new List<double>()
};
TSVC_Data.Add(item.Key, currConf);
}
// log avvio + setup lastVal...
foreach (var item in TSVC_Data)
{
lgTrace($"TSVC: {item.Key} | periodo: {item.Value.Period} | funz: {item.Value.Funzione}");
// salvo i valori PREC...
LastTSVC.Add(item.Key, 0);
}
}
// infine se genObj memoria valido salvo in MP-IO x sue applicazioni
if (memMap != null)
{
// invio su cloud conf memoria...
string rawData = JsonConvert.SerializeObject(memMap, Formatting.Indented);
// controllo ping al server...
if (serverOk)
{
var resp = utils.callUrl($"{urlSaveMemMap}", rawData);
}
else
{
lgInfo($"Mancata risposta ping da server, saltato step invio memMap a {urlSaveMemMap}");
}
// salvo ANCHE come parametri i valori...
objItem currItem = new objItem();
List<objItem> allParam = new List<objItem>();
string currValore = "";
// valori WRITE
foreach (var item in memMap.mMapWrite)
{
currValore = "";
//se ho valori current li impiego...
if (currProdData.ContainsKey(item.Value.name))
{
currValore = currProdData[item.Value.name];
item.Value.value = currValore;
}
currItem = new objItem()
{
uid = item.Value.name,
name = item.Value.name,
description = !string.IsNullOrEmpty(item.Value.description) ? item.Value.description : item.Value.name,
writable = true,
UM = item.Value.unit,
valMin = item.Value.minVal,
valMax = item.Value.maxVal,
value = currValore,
displOrdinal = item.Value.displOrdinal
};
allParam.Add(currItem);
}
// valori READ
foreach (var item in memMap.mMapRead)
{
// se non ci fosse aggiungo
var trovato = allParam.FirstOrDefault(x => x.uid == item.Value.name);
if (trovato == null)
{
currValore = "";
// se ho valori current li impiego...
if (currProdData.ContainsKey(item.Value.name))
{
currValore = currProdData[item.Value.name];
item.Value.value = currValore;
}
currItem = new objItem()
{
uid = item.Value.name,
name = item.Value.name,
description = !string.IsNullOrEmpty(item.Value.description) ? item.Value.description : item.Value.name,
writable = false,
UM = item.Value.unit,
valMin = item.Value.minVal,
valMax = item.Value.maxVal,
value = currValore,
displOrdinal = item.Value.displOrdinal
};
allParam.Add(currItem);
}
}
// invio su cloud parametri SE sono connesso alla macchina... in pratica reset parametri...
string tipoCall = urlSaveAllParams;
rawData = JsonConvert.SerializeObject(allParam, Formatting.Indented);
if (serverOk)
{
// verifica se sia un IOB "parziale" --> salva solo update ai parametri
if (IOBConfFull.Device.DisabExeTask || IOBConfFull.Device.DisabStateCh)
{
// versione upsert
tipoCall = urlUpdateWriteParams;
}
var resp = utils.callUrl($"{tipoCall}", rawData);
}
else
{
lgInfo($"Mancata risposta ping server, non effettuata chiamata {tipoCall}");
}
lgDebug($"setupMemMap | salvata conf memoria R/W");
}
}
}
/// <summary>
/// Setup parametri memoria opzionali
/// es: BitCond e IntCond x ricerca valori target
/// </summary>
protected virtual void setupOptMemPar()
{
if (memMap == null)
{
lgError($"setupOptMemPar | memMap nullo");
}
else
{
// verifica se siano necessari configurazioni speciali dalla OptMemPar:
// es: per ricerca condizioni status booleane come ModbusTCP...
if (memMap.OptMemPar != null && memMap.OptMemPar.Count > 0)
{
// cerco condizioni BIT speciali x Auto, Estop, Work.. devono essere *BitCond
var listPar2add = memMap.OptMemPar.Where(x => x.Key.EndsWith("BitCond")).ToList();
if (listPar2add != null && listPar2add.Count > 0)
{
foreach (var item in listPar2add)
{
addCheckConditionBit(item.Key, item.Value);
}
}
// cerco condizioni INT speciali x Auto, Estop, Work.. devono essere *IntCond
var listParInt2add = memMap.OptMemPar.Where(x => x.Key.EndsWith("IntCond")).ToList();
if (listParInt2add != null && listParInt2add.Count > 0)
{
foreach (var item in listParInt2add)
{
addCheckConditionInt(item.Key, item.Value);
}
}
// cerco valori *2Translate
var listVal2Translate = memMap.OptMemPar.Where(x => x.Key.Contains("2Translate")).ToList();
if (listVal2Translate != null && listVal2Translate.Count > 0)
{
// cerco solo valori BIT...
var listValBit = listVal2Translate.Where(x => x.Key.StartsWith("VarBit2Translate")).ToList();
if (listValBit != null && listValBit.Count > 0)
{
foreach (var item in listValBit)
{
addVal2TranslBit(item.Value);
}
}
// ...e poi valori INT...
var listValInt = listVal2Translate.Where(x => x.Key.StartsWith("VarInt2Translate")).ToList();
if (listValInt != null && listValInt.Count > 0)
{
foreach (var item in listValInt)
{
addVal2TranslInt(item.Value);
}
}
}
}
}
}
/// <summary>
/// Init parametri speciali, tipicamente KVP opzionali da json
/// </summary>
protected virtual void setupSpecialParams()
{
string sCond = "";
// per prima cosa inizializzo lista PODL inviati
POdlSentList = POdlSentFileArch;
// verifico se sia attiva gestione riduzione FluxLog...
if (!string.IsNullOrEmpty(getOptJsonKVP("fluxLogReduce")))
{
bool.TryParse(getOptJsonKVP("fluxLogReduce"), out fluxLogReduce);
double.TryParse(getOptJsonKVP("fluxLogRedDeadBand"), NumberStyles.Any, CultureInfo.InvariantCulture, out fluxLogRedDeadBand);
int.TryParse(getOptJsonKVP("fluxLogResendPeriod"), out fluxLogResendPeriod);
lgInfo($"FluxLog Redux | fluxLogReduce: {fluxLogReduce} | fluxLogRedDeadBand: {fluxLogRedDeadBand:N2} | fluxLogResendPeriod: {fluxLogResendPeriod} min");
}
// check modalità ricetta attiva
bool.TryParse(getOptJsonKVP("hasRecipe"), out hasRecipe);
// verifico se usare ricette locali/http...
bool.TryParse(getOptJsonKVP("useLocalRecipe"), out useLocalRecipe);
// recupero quantitativo massimo KG x PODL
int.TryParse(getOptJsonKVP("maxPodlQty"), out maxQtyPerFile);
if (memMap == null)
{
lgError("setupSpecialParams: memMap nullo!");
}
else
{
// recupero le folder specifiche x IN/OUT ricette filtrando direttamente l'area di memoria...
sCond = "fullPath";
var dirList = memMap.OptKVP
.Where(x => x.Key.StartsWith(sCond))
.ToList();
pathList = dirList.ToDictionary(x => x.Key, x => x.Value);
// gestione replace in file ricette...
sCond = "replace-";
var ruleList = memMap.OptKVP
.Where(x => x.Key.StartsWith(sCond))
.ToList();
RecipeReplRules = ruleList.ToDictionary(x => x.Key.Replace(sCond, ""), x => x.Value);
}
}
/// <summary>
/// Cleanup stringa x impiego tipo ident da char dubbi
/// </summary>
/// <param name="origData"></param>
/// <returns></returns>
protected string strFixId(string origData)
{
return origData.Replace(".", "").Replace(" ", "_");
}
/// <summary>
/// Traccia in redis un dynData x analisi frequenza e campionamento valori FluxLog ...
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
protected void trackDynData(string key, string value)
{
// attenzione: x ora cablato 3 gg e 1 mese le registrazioni
DateTime oggi = DateTime.Today;
string DayCurr = $"{oggi:yyMMdd}";
// se cambiato giorno --> forzo scrittura senza aggiunte
if (!LastDayCurr.Equals(DayCurr))
{
DoTrackDataSave();
}
// accumulo stat Day
DoTrackDataCount(key, value);
// ...e se > soglia registro
DoTrackDataSave();
}
/// <summary>
/// Traccia in redis l'attività di scambio dati (tipicamente non in polling)
/// </summary>
/// <param name="byteSize">Dim pacchetto (numero byte scambiati/ricevuti)</param>
/// <param name="val2rec">Dim minima x registrare info</param>
protected void trackExchData(long byteSize, long val2rec = 512)
{
// accumulo counter byte
currByteCount += byteSize;
// se superato limite scrivo (default 128byte)
if (currByteCount >= val2rec)
{
string rkeyTS = $"{redisMan.redIobTrackKey}:DataInTS";
string rkeyHS = $"{redisMan.redIobTrackKey}:DataInHS";
// chiamo direttamente metodo redis...
string newUid_ = redisMan.TrackExchData(rkeyTS, rkeyHS, currByteCount);
currByteCount = 0;
}
}
/// <summary>
/// Track dati ricevuti generici (serializzando)
/// </summary>
/// <param name="genObj"></param>
protected void trackExchDataRaw(object genObj, long val2rec = 512)
{
try
{
long byteSize = GetObjectSize(genObj, false);
// salvo dim caratteri ricevuti
trackExchData(byteSize, val2rec);
}
catch (Exception exc)
{
lgError($"trackExchDataRaw | errore in fase di track Obj ricevuto{Environment.NewLine}{exc}");
}
}
protected static long GetObjectSize(object genObj, bool isSerializable)
{
long result = 0;
if (isSerializable)
{
using (var stream = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(stream, genObj);
result = stream.Length;
}
}
else
{
string rawVal = System.Text.Json.JsonSerializer.Serialize(genObj, options);
result = rawVal.Length;
}
return result;
}
private static readonly JsonSerializerOptions options = new JsonSerializerOptions
{
ReferenceHandler = ReferenceHandler.IgnoreCycles,
WriteIndented = false // Optional: set to true for pretty-printing
};
/// <summary>
/// Traccia in redis l'attività di lettura dati
/// </summary>
/// <param name="byteSize">Dim pacchetto (numero byte letti)</param>
/// <param name="timeSec">Se specificato indica il tempo effettivo di lettura, se zero uso timespan giornaliero...</param>
protected void trackReadData(int byteSize, double timeSec)
{
// attenzione: x ora cablato 1 mese le registrazioni
DateTime adesso = DateTime.Now;
double kbRead = (double)byteSize / 1024;
var cultInv = CultureInfo.InvariantCulture;
// per prima cosa LEGGO il valore precedente
string rKey = $"{redisMan.redIobTrackKey}:DataIn:{adesso:yyMMdd}";
var actData = redisMan.redGetHashDict(rKey);
// KB letti
double dVal = 0;
if (actData.ContainsKey("KbRead"))
{
double.TryParse(actData["KbRead"], NumberStyles.Float, cultInv, out dVal);
}
dVal += kbRead;
string sDouble = dVal.ToString("F3", cultInv);
if (actData.ContainsKey("KbRead"))
{
actData["KbRead"] = sDouble;
}
else
{
actData.Add("KbRead", sDouble);
}
// TIMING: se ho un valore lo aggiungo, altrimenti calcolo da mezzanotte
double dPTime = 0;
if (timeSec > 0)
{
if (actData.ContainsKey("ProcTime"))
{
double.TryParse(actData["ProcTime"], NumberStyles.Float, cultInv, out dPTime);
}
dPTime += timeSec;
}
else
{
dPTime = adesso.Subtract(adesso.Date).TotalSeconds;
}
sDouble = dPTime.ToString("F3", cultInv);
if (actData.ContainsKey("ProcTime"))
{
actData["ProcTime"] = sDouble;
}
else
{
actData.Add("ProcTime", sDouble);
}
// salvo il mio hash aggiornato!
redisMan.redSaveHashDict(rKey, actData);
}
/// <summary>
/// Reset valori trackdata all'avvio dell'adapter IOB
/// </summary>
protected void trackReadDataReset()
{
// per prima cosa LEGGO il valore precedente
DateTime adesso = DateTime.Now;
string rKey = $"{redisMan.redIobTrackKey}:DataIn:{adesso:yyMMdd}";
redisMan.redDelKey(rKey);
}
/// <summary>
/// Versione sync richiesta chiusura ODL corrente
/// </summary>
/// <returns></returns>
protected bool TryAskCloseCurrODL()
{
bool fatto = false;
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
fatto = await TryAskCloseCurrODLAsync();
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in TryAskCloseCurrODL{Environment.NewLine}{ex.Message}");
}
return fatto;
}
/// <summary>
/// Effettua chiamata MP-IO per chiedere all'utente se vuole effettuare chiusura ODL
/// corrente della macchina
/// </summary>
/// <returns></returns>
protected async Task<bool> TryAskCloseCurrODLAsync()
{
bool fatto = false;
string fullUrl = $"{urlODLAskClose}{currIdxODL}";
try
{
// invio chiamata URL x chiusura ODL su macchina
string callResp = await callUrl(fullUrl, false);
fatto = (callResp != "KO") ? true : false;
}
catch
{ }
lgInfo($"Eseguito TryAskCloseCurrODLAsync per {currIdxODL} | url: {fullUrl} | esito: {fatto}");
return fatto;
}
/// <summary>
/// Versione sync chiusura ODL corrente
/// </summary>
/// <returns></returns>
protected bool TryCloseCurrODL()
{
bool fatto = false;
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
fatto = await TryCloseCurrODLAsync();
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in TryCloseCurrODLAsync{Environment.NewLine}{ex.Message}");
}
return fatto;
}
/// <summary>
/// Effettua chiamata MP-IO per tentare chiusura ODL corrente della macchina
/// </summary>
/// <returns></returns>
protected async Task<bool> TryCloseCurrODLAsync()
{
bool fatto = false;
string fullUrl = $"{urlODLClose}{currIdxODL}";
try
{
// invio chiamata URL x chiusura ODL su macchina
string callResp = await callUrl(fullUrl, false);
fatto = (callResp != "KO") ? true : false;
}
catch
{ }
lgInfo($"Eseguito TryCloseCurrODLAsync per {currIdxODL} | url: {fullUrl} | esito: {fatto}");
return fatto;
}
/// <summary>
/// Versione sync chiusura ODL specifico
/// </summary>
/// <param name="IdxODL"></param>
/// <returns></returns>
protected bool TryCloseODL(int IdxODL)
{
bool fatto = false;
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
fatto = await TryCloseODLAsync(IdxODL);
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in TryCloseODLAsync | IdxODL: {IdxODL}{Environment.NewLine}{ex.Message}");
}
return fatto;
}
/// <summary>
/// Effettua chiamata MP-IO per tentare chiusura ODL specifico
/// </summary>
/// <param name="IdxODL"></param>
/// <returns></returns>
protected async Task<bool> TryCloseODLAsync(int IdxODL)
{
bool fatto = false;
string fullUrl = $"{urlODLClose}{IdxODL}";
try
{
// invio chiamata URL x chiusura ODL su macchina
string callResp = await callUrl(fullUrl, false);
fatto = (callResp != "KO") ? true : false;
}
catch
{ }
lgInfo($"Eseguito TryCloseODLAsync per {IdxODL} | url: {fullUrl} | esito: {fatto}");
return fatto;
}
/// <summary>
/// Effettua chiamata MP-IO per tentare chiusura PODL --&gt; ODL specifico
/// </summary>
/// <param name="idxPODL"></param>
/// <returns></returns>
protected bool TryClosePODL(int idxPODL)
{
bool fatto = false;
string fullUrl = $"{urlPODLClose}{idxPODL}";
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
// invio chiamata URL x chiusura ODL su macchina
string callResp = await callUrl(fullUrl, false);
fatto = (callResp != "KO") ? true : false;
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in TryClosePODL | idxPODL: {idxPODL}{Environment.NewLine}{ex.Message}");
}
lgInfo($"Eseguito TryClosePODL per {idxPODL} | url: {fullUrl} | esito: {fatto}");
return fatto;
}
/// <summary>
/// Effettua chiamata MP-IO per tentare chiusura PODL --&gt; ODL specifico
/// </summary>
/// <param name="idxPODL"></param>
/// <param name="doConfirm"></param>
/// <param name="dtEve"></param>
/// <param name="dtCurr"></param>
/// <returns></returns>
protected bool TryClosePODL(int idxPODL, DateTime dtEve, DateTime dtCurr)
{
bool fatto = false;
string fullUrl = $"{urlPODLClose}{idxPODL}&dtEve={dtEve:yyyyMMddHHmmssfff}&dtCurr={dtCurr:yyyyMMddHHmmssfff}";
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
// invio chiamata URL x chiusura ODL su macchina
string callResp = await callUrl(fullUrl, false);
fatto = (callResp != "KO") ? true : false;
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in TryClosePODL | idxPODL: {idxPODL} | dtEve: {dtEve} | dtCurr: {dtCurr}{Environment.NewLine}{ex.Message}");
}
lgInfo($"Eseguito TryClosePODL per {idxPODL} | url: {fullUrl} | esito: {fatto}");
return fatto;
}
/// <summary>
/// Processa avvio PODL (ed eventuale chiusura precedente)
/// </summary>
/// <param name="codArt">Cod Articolo del PODL da avviare</param>
/// <param name="codGruppo">Cod Gruppo dell'impianto come default</param>
/// <param name="numPz">num pz richiesti</param>
protected int TryCreatePodl(string codArt, string codGruppo, int numPz)
{
int answ = 0;
string urlEncoded = $"{urlCreatePOdl}CodArt={codArt}&CodGruppo={codGruppo}&numPz={numPz}";
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
string resp = await callUrl(urlEncoded, false);
int.TryParse(resp, out answ);
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in TryCreatePodl | codArt: {codArt} | codGruppo: {codGruppo} | numPz: {numPz}{Environment.NewLine}{ex.Message}");
}
return answ;
}
/// <summary>
/// Effettua verifica se abilitato invio pezzi in blocco PER TAVOLE e nel caso
/// - invio in blocco pezzi
/// - aggiornamento del contapezzi (passato come ref) x nuovo valore post invio
/// </summary>
/// <param name="fullCode">Idx macchina completo, con tavola/pallet di invio</param>
/// <param name="pzCountMes">Contapezzi MES (IOB) attuale</param>
/// <param name="pzCountImp">Contapezzi impianto (per la tavola indicata)</param>
protected virtual int trySendPzCountBlock(string fullCode, int pzCountMes, int pzCountImp)
{
int qtyAdded = 0;
lgDebug($"Chiamata trySendPzCountBlock MULTI | fullCode: {fullCode} | pzCountMes: {pzCountMes} | pzCountImp: {pzCountImp}");
// in primis HA SENSO procedere SOLO SE server MP è Online...
if (MPOnline)
{
// SOLO SE online la macchina...
if (IobOnline)
{
int numIncr = 0;
// verifico se la funzione SIA abilitata
if (enableSendPzCountBlock)
{
int delta = pzCountImp - pzCountMes;
// se è abilitata verifico differenza: se ho DELTA > minSendPzCountBlock -->
// invio un blocco <= maxSendPzCountBlock
if (delta > minSendPzCountBlock)
{
// init genObj display
newDisplayData currDispData = new newDisplayData();
// resta indietro di ALMENO minSendPzCountBlock pezzi x recuperare 1:1...
numIncr = delta > maxSendPzCountBlock + minSendPzCountBlock ? maxSendPzCountBlock : delta - minSendPzCountBlock;
// invio il num max di pezzi ammesso in blocco!
lastUrl = $"{urlAddPzCount}{numIncr}".Replace(IOBConfFull.General.CodIOB, fullCode);
string resp = utils.callUrlNow(lastUrl);
if (!string.IsNullOrEmpty(resp))
{
// dalla risposta (come numero) capisco SE ha aggiunto i pezzi (e quanti)
int.TryParse(resp, out qtyAdded);
if (qtyAdded > 0)
{
// aggiorno contapezzi ...
pzCountMes += qtyAdded;
lgInfo($"SEND incremento contapezzi: send: {numIncr} | resp: {qtyAdded} | contapezziMES: {pzCountMes}");
// invio conferma contapezzi..
string fullUrl = $"{urlSetPzCount}{pzCountMes}".Replace(IOBConfFull.General.CodIOB, fullCode);
string retVal = utils.callUrl(fullUrl);
// verifica se tutto OK
if (retVal != $"{pzCountMes}")
{
// errore salvataggio contapezzi
lgError($"trySendPzCountBlock: errore salvataggio contapezzi: contapezziMES {pzCountMes} | risposta: {retVal}");
}
}
else
{
lgError($"Richiesto incremento {numIncr} ma NON registrato su server MP-IO");
}
}
currDispData.newUrlCallData = lastUrl;
currDispData.counter = pzCountMes;
currDispData.semOut = Semaforo.SV;
raiseRefresh(currDispData);
}
}
}
else
{
lgError("Impossibile trySendPzCountBlock: IobOnline è false");
}
}
else
{
lgError("Impossibile trySendPzCountBlock: MPOnline è false");
}
return qtyAdded;
}
/// <summary>
/// Effettua verifica se abilitato invio pezzi in blocco (caso macchina standard/singolo contapezzi) e nel caso
/// - invio in blocco pezzi
/// - aggiornamento del contapezzi (passato come ref) x nuovo valore post invio
/// </summary>
protected virtual void trySendPzCountBlock()
{
lgDebug($"Chiamata trySendPzCountBlock STD | pzCountMes: {contapezziIOB} | pzCountImp: {contapezziPLC}");
// in primis HA SENSO procedere SOLO SE server MP è Online...
if (MPOnline)
{
// SOLO SE online la macchina...
if (IobOnline)
{
int numIncr = 0;
int qtyAdded = 0;
// verifico se la funzione SIA abilitata
if (enableSendPzCountBlock)
{
int delta = contapezziPLC - contapezziIOB;
// se è abilitata verifico differenza: se ho DELTA > minSendPzCountBlock -->
// invio un blocco <= maxSendPzCountBlock
if (delta > minSendPzCountBlock)
{
// init genObj display
newDisplayData currDispData = new newDisplayData();
// resta indietro di ALMENO minSendPzCountBlock pezzi x recuperare 1:1...
numIncr = delta > maxSendPzCountBlock + minSendPzCountBlock ? maxSendPzCountBlock : delta - minSendPzCountBlock;
// invio il num max di pezzi ammesso in blocco!
lastUrl = $"{urlAddPzCount}{numIncr}";
string resp = utils.callUrlNow(lastUrl);
if (!string.IsNullOrEmpty(resp))
{
// dalla risposta (come numero) capisco SE ha aggiunto i pezzi (e quanti)
int.TryParse(resp, out qtyAdded);
if (qtyAdded > 0)
{
// aggiorno IL MIO contapezzi...
contapezziIOB += qtyAdded;
lgInfo($"SEND incremento contapezzi: send: {numIncr} | resp: {qtyAdded} | contapezziIOB: {contapezziIOB}");
// invio conferma contapezzi..
string fullUrl = $"{urlSetPzCount}{contapezziIOB}";
string retVal = utils.callUrl(fullUrl);
// verifica se tutto OK
if (retVal != contapezziIOB.ToString())
{
// errore salvataggio contapezzi
lgError($"trySendPzCountBlock: errore salvataggio contapezzi: contapezziIOB {contapezziIOB} | risposta: {retVal}");
}
}
else
{
lgError($"Richiesto incremento {numIncr} ma NON registrato su server MP-IO");
}
}
currDispData.newUrlCallData = lastUrl;
currDispData.counter = contapezziIOB;
currDispData.semOut = Semaforo.SV;
raiseRefresh(currDispData);
}
}
}
else
{
lgError("Impossibile trySendPzCountBlock: IobOnline è false");
}
}
else
{
lgError("Impossibile trySendPzCountBlock: MPOnline è false");
}
}
/// <summary>
/// Cerca di inviare su un altro thread i vari dati accumulati...
/// </summary>
protected async Task TrySendValuesAsync()
{
// init genObj display
newDisplayData currDispData = new newDisplayData();
try
{
// verifico se risponde il server...
bool srvAlive = await CheckServerAliveAsync();
if (srvAlive)
{
bool iobOk = false;
iobOk = await CheckIobEnabled();
// verifico SE posso inviare dati
if (iobOk)
{
currDispData.semOut = Semaforo.SV;
// gestione queue SignalIN (invio, display)
await svuotaCodaSignInAsync();
currDispData.counter = contapezziIOB;
raiseRefresh(currDispData);
// provo a svuotare coda contapezzi
await SvuotaCodaContapezziAsync();
currDispData.counter = contapezziIOB;
raiseRefresh(currDispData);
// gestione queue FluxLog (invio, display)
await SvuotaCodaFLogAsync();
// coda RawTransf
await SvuotaCodaRawTransfAsync();
// coda UserLog
await SvuotaCodaULogAsync();
// refresh
raiseRefresh(currDispData);
// se arrivo è tutto ok...
currSendErrors = 0;
}
else
{
// mostro VETO-SEND x invio... GIALLO
currDispData.semOut = Semaforo.SG;
if (periodicLog)
{
lgInfo("IOB - VETO SEND");
}
}
}
else
{
// mostro SERVER KO x invio... ROSSO
currDispData.semOut = Semaforo.SR;
if (periodicLog)
{
lgError("IOB - SERVER NOT READY");
}
}
}
catch (Exception exc)
{
lgError($"Errore in fase TrySendValuesAsync | currSendErrors: {currSendErrors}{Environment.NewLine}{exc}");
currDispData.semOut = Semaforo.SR;
}
raiseRefresh(currDispData);
}
/// <summary>
/// Versione sync chiamata setup PODL
/// </summary>
/// <param name="idxPODL"></param>
/// <returns></returns>
protected bool TrySetupPODL(int idxPODL)
{
bool fatto = false;
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
fatto = await TrySetupPODLAsync(idxPODL);
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in TrySetupPODL | idxPODL: {idxPODL}{Environment.NewLine}{ex.Message}");
}
return fatto;
}
/// <summary>
/// Effettua chiamata MP-IO per tentare setup del PODL indicato
/// </summary>
/// <param name="idxPODL"></param>
/// <returns></returns>
protected async Task<bool> TrySetupPODLAsync(int idxPODL)
{
bool fatto = false;
string fullUrl = $"{urlOdlStartFromPOdl}{idxPODL}";
try
{
// invio chiamata URL x avvio PODL su macchina
string rawSplit = await callUrl(fullUrl, false);
fatto = (rawSplit != "KO") ? true : false;
}
catch
{ }
return fatto;
}
/// <summary>
/// Effettua chiamata MP-IO per tentare setup del PODL indicato con indicazioni estese
/// (confirm pezzi, dtEvento, dtCorrente)
/// </summary>
/// <param name="idxPODL"></param>
/// <param name="doConfirm"></param>
/// <param name="dtEve"></param>
/// <param name="dtCurr"></param>
/// <returns></returns>
protected async Task<bool> TrySetupPODLAsync(int idxPODL, bool doConfirm, DateTime dtEve, DateTime dtCurr)
{
bool fatto = false;
//string format = "yyyyMMddHHmmssfff";
string fullUrl = $"{urlOdlStartFromPOdl}{idxPODL}&doConfirm={doConfirm}&dtEve={dtEve:yyyyMMddHHmmssfff}&dtCurr={dtCurr:yyyyMMddHHmmssfff}";
try
{
// invio chiamata URL x avvio PODL su macchina
string rawSplit = await callUrl(fullUrl, false);
fatto = (rawSplit != "KO") ? true : false;
}
catch
{ }
return fatto;
}
/// <summary>
/// URL del comando letto da conf in aggiunta al server MES da contattare
/// </summary>
protected string urlCommand(string cmqReq)
{
return $@"{urlMesServer}{IOBConfFull.MapoMes.ApiUrl(cmqReq)}";
}
/// <summary>
/// URL come urlCommand + aggiunta codice IOB da cui viene inviato
/// </summary>
protected string urlCommandIob(string cmqReq)
{
return $@"{urlMesServer}{IOBConfFull.MapoMes.ApiUrl(cmqReq)}{IOBConfFull.General.CodIOB}";
}
/// <summary>
/// URL come urlCommand + aggiunta codice IOB da file (= specifico x Reboot) da cui viene inviato
/// </summary>
protected string urlCommandIobFile(string cmqReq)
{
return $@"{urlMesServer}{IOBConfFull.MapoMes.ApiUrl(cmqReq)}{IOBConfFull.General.FilenameIOB}";
}
/// <summary>
/// Verifica se il parametro passi il limite della DeadBand (globale o specifica se
/// configurata) Il riferimento è al prec valore currProdData
/// </summary>
/// <param name="keyName">nome del parametro da verificare</param>
/// <param name="actVal">valore attuale da testare</param>
/// <returns>
/// true = passa controllo, è da inviare / false = variazione entro deadband non da inviare
/// </returns>
protected virtual bool valueOverDBand(string keyName, string actVal)
{
bool answ = true;
float oldVal = 0;
float newVal = 0;
// DEVE esserci un valore global deadband > 0...
if (GLOBAL_DBAND > 0)
{
// in primis deve essere in currProdData
if (currProdData.ContainsKey(keyName))
{
// se c'è DEVE passare il test decodifica float...
bool isFloatOld = float.TryParse(currProdData[keyName], out oldVal);
bool isFloatNew = float.TryParse(actVal, out newVal);
if (isFloatOld && isFloatNew)
{
// in questo caso verifico la differenza sia superiore alla deadband
answ = Math.Abs(newVal - oldVal) > GLOBAL_DBAND;
}
}
}
return answ;
}
/// <summary>
/// Recupera da conf durata del veto x FluxLog (default 60sec)
/// </summary>
/// <param name="codFlux"></param>
/// <returns></returns>
protected int vetoCodFluxDur(string codFlux)
{
int answ = 60;
if (memMap != null && memMap.mMapRead != null && memMap.mMapRead.Count > 0)
if (memMap.mMapRead.ContainsKey(codFlux))
{
answ = memMap.mMapRead[codFlux].period;
}
return answ;
}
#endregion Protected Methods
#region Private Fields
/// <summary>
/// Oggetto logger della classe
/// </summary>
private static Logger _logger = LogManager.GetCurrentClassLogger();
/// <summary>
/// Accumulatore counter byte x inviare meno record in REDIS
/// </summary>
private long currByteCount = 0;
/// <summary>
/// Dizionario dei valori FluxLog filtrati
/// </summary>
private Dictionary<string, long> DictFiltFLog = new Dictionary<string, long>();
/// <summary>
/// Valore veto al salvataggio di valori filtrati in FluxLog se NON superato limite chiamate
/// </summary>
private DateTime VetoFlushFiltFL = DateTime.Now.AddHours(1);
/// <summary>
/// Salva nel dizionario il num di valori letti e filtrati/non inviati
/// </summary>
/// <param name="codFlux"></param>
protected void SaveFiltFluxLog(string codFlux)
{
if (DictFiltFLog.ContainsKey(codFlux))
{
DictFiltFLog[codFlux]++;
}
else
{
DictFiltFLog.Add(codFlux, 1);
_logger.Info($"FluxLog | veto | {codFlux}");
}
}
/// <summary>
/// Verifica se salvare (nel log) info dei FluxLog filtrati
/// - per scadenza valore VetoFlushFiltFL
/// - per un numero complessivo di eventi superiori a soglia
/// </summary>
/// <param name="minCount">numero minimo di eventi necessari al salvataggio (come somma complessiva)</param>
protected void FiltFluxLogCheckSave(long minCount)
{
DateTime adesso = DateTime.Now;
bool doWrite = VetoFlushFiltFL < adesso;
// conteggio valori
if (!doWrite)
{
long numFL = DictFiltFLog.Sum(x => x.Value);
doWrite = numFL > minCount;
}
if (doWrite)
{
// scrivo log x ogni valore
string sep = "------------------------------------------------------------";
_logger.Info(sep);
_logger.Info("- Paret0 Eventi FluxLog filtrati");
_logger.Info(sep);
foreach (var item in DictFiltFLog.OrderByDescending(x => x.Value))
{
// loggo!
_logger.Info($"{item.Key}: {item.Value}");
}
_logger.Info(sep);
// reset dizionario
DictFiltFLog.Clear();
// nuovo veto a 1h
VetoFlushFiltFL = adesso.AddHours(1);
}
}
private string LastDayCurr = "";
/// <summary>
/// Ultimo invio valore a server
/// </summary>
private DateTime lastSigVarSent = DateTime.Now;
/// <summary>
/// periodo minimo di veto invio dati qualora non siano variati
/// </summary>
private int lastSigVarVeto = 55;
private Dictionary<string, int> TrackDayStatsCount = new Dictionary<string, int>();
private Dictionary<string, int> TrackDetStatsCount = new Dictionary<string, int>();
private Dictionary<string, Dictionary<string, string>> TrackDetValsCount = new Dictionary<string, Dictionary<string, string>>();
/// <summary>
/// Dizionario dei valori bloccati x evitare log eccessivo
/// </summary>
private Dictionary<string, DateTime> vetoLogError = new Dictionary<string, DateTime>();
/// <summary>
/// Periodo di veto log in minuti
/// </summary>
private int vetoPeriodMin = 15;
/// <summary>
/// Periodo Veto prima di eseguire ProcessAutoOdlAsync
/// </summary>
private DateTime VetoProcessAutoOdl = DateTime.Now;
/// <summary>
/// Variabile x vietare rilettura conf memoria (se è stato letto metto veto lungo ad 10 min x evitare loop in avvio)
/// </summary>
private DateTime vetoReloadConf = DateTime.Now.AddMinutes(-1);
/// <summary>
/// Dizionario dei valori bloccati x evitare log basato su veto temporale
/// </summary>
private Dictionary<string, DateTime> vetoSendFLog = new Dictionary<string, DateTime>();
/// <summary>
/// Ms di attesa x uscita processo (std)
/// </summary>
private int waitForExitMsec = 250;
#endregion Private Fields
#region Private Properties
/// <summary>
/// Verifica se la IOB sia ENABLED (da server o Demo)
/// </summary>
public async Task<bool> CheckIobEnabled()
{
// 1. Controllo Veto (Sincrono)
if (dtVetoCheckIOB >= DateTime.Now)
return IobOnline;
bool currentAnsw = false;
if (DemoOut)
{
currentAnsw = (QueueIN.Count + QueueFLog.Count >= nMaxSend);
}
else
{
currentAnsw = await ExecuteIobCheckWithRetry(maxRetries: 5);
#if false
// 2. Chiamata asincrona con Retry (Ponte Sync/Async)
currentAnsw = Task.Run(async () => await ExecuteIobCheckWithRetry(maxRetries: 5))
.GetAwaiter()
.GetResult();
#endif
}
// 3. Gestione Stato e Logica UI
UpdateIobState(currentAnsw);
return currentAnsw;
}
private async Task<bool> ExecuteIobCheckWithRetry(int maxRetries)
{
var rand = new Random();
for (int i = 0; i <= maxRetries; i++)
{
try
{
if (i > 0)
{
// Al terzo tentativo fallito resetto i client
if (i == 3) resetWebClients();
int delay = i == 3 ? rand.Next(250, 1000) : rand.Next(250, 500);
await Task.Delay(delay);
}
string callResp = await callUrl(urlIobEnabled, i < 3); // true per i primi tentativi
if (callResp == "OK") return true;
}
catch (Exception exc)
{
lgError($"Eccez<ione in ExecuteIobCheckWithRetry{Environment.NewLine}{exc}");
}
}
return false;
}
private void UpdateIobState(bool newStatus)
{
// Log se lo stato è cambiato
if (IobOnline != newStatus)
{
lgInfo(newStatus ? "IOB ONLINE for server MP/IO" : "IOB OFFLINE for server MP/IO");
IobOnline = newStatus;
if (newStatus)
{
lastIobOnline = DateTime.Now;
parentForm.commSrvActive = 2; // Stato Online
}
else
{
parentForm.commSrvActive = 1; // Stato Degradato/Offline
}
}
// Imposto il veto per il prossimo controllo
dtVetoCheckIOB = DateTime.Now.AddMilliseconds(baseUtils.nextPauseSendMSec * 5);
}
/// <summary>
/// Boolean abilitazione coda eventi IN
/// </summary>
private bool qInEnabCurr { get; set; } = false;
/// <summary>
/// Redis key del dizionari valori currProdData persistiti
/// </summary>
private string rKeyCurrProdData
{
get => redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:CurrProdData");
}
/// <summary>
/// Test ping all'indirizzo impostato nei parametri
/// </summary>
/// <returns></returns>
private IPStatus GetPingStatus()
{
var pStatus = Task.Run(async () => await GetPingStatusAsync(maxPingRetry + 1))
.GetAwaiter()
.GetResult();
return pStatus;
}
/// <summary>
/// Test ping all'indirizzo impostato nei parametri
/// </summary>
/// <param name="maxRetry">Numero max tentativi (maxPingRetry + 1)</param>
/// <returns></returns>
private async Task<IPStatus> GetPingStatusAsync(int maxAttempts)
{
// 1. Check disabilitazione
if (IOBConfFull.MapoMes.DisabPing) return IPStatus.Success;
// 2. Estrazione Host/IP pulita
string host = IOBConfFull.MapoMes.IpAddr;
try
{
// Rimuove protocollo (http://, ftp://, etc)
if (host.Contains("://"))
host = new Uri(host).Host;
else if (host.Contains(":"))
host = host.Split(':')[0];
}
catch { /* fallback al valore originale se Uri fallisce */ }
// 3. Risoluzione Indirizzo
if (!IPAddress.TryParse(host, out IPAddress address))
{
try
{
var addresses = Dns.GetHostAddresses(host);
if (addresses.Length > 0) address = addresses[0];
}
catch (Exception ex)
{
lgError($"Impossibile risolvere DNS per {host}: {ex.Message}");
return IPStatus.DestinationHostUnreachable;
}
}
if (address == null) return IPStatus.Unknown;
// 4. Ciclo di Ping con Retry
var rand = new Random();
using (Ping pingSender = new Ping())
{
for (int attempt = 1; attempt <= maxAttempts; attempt++)
{
try
{
// Timeout dinamico come nel tuo originale
int timeout = (attempt == 1)
? rand.Next(200, 400)
: (pingServerMsTimeout * attempt / 2);
PingReply reply = pingSender.Send(address, timeout);
if (reply.Status == IPStatus.Success)
{
if (attempt > 1) lgInfo("Server PING OK dopo retry!");
return IPStatus.Success;
}
lgInfo($"Ping tent. {attempt} fallito: {reply.Status} (Timeout: {timeout}ms)");
}
catch (Exception ex)
{
lgError($"Eccezione durante ping tent. {attempt}: {ex.Message}");
}
// Attesa prima del prossimo tentativo (se non è l'ultimo)
if (attempt < maxAttempts)
{
await Task.Delay(rand.Next(50, 200));
}
}
}
return IPStatus.TimedOut;
}
/// <summary>
/// URL di base del server MES da contattare
/// </summary>
private string urlMesServer
{
get => $@"{IOBConfFull.MapoMes.Transport}://{IOBConfFull.MapoMes.IpAddr}";
}
#endregion Private Properties
#region Private Methods
/// <summary>
/// Aggiunge in setup memoria la checkCondition BIT "decodificata"
/// </summary>
/// <param name="ompKey">Chiave da aggiungere</param>
/// <param name="ompVal">Valore da decodificare e poi aggiungere</param>
private void addCheckConditionBit(string ompKey, string ompVal)
{
var newCond = new BitConditionCheck(ompKey, ompVal);
// cerco x aggiornare o aggiungere...
if (OptCheckCondBit.ContainsKey(ompKey))
{
OptCheckCondBit[ompKey] = newCond;
}
else
{
OptCheckCondBit.Add(ompKey, newCond);
}
}
/// <summary>
/// Aggiunge in setup memoria la checkCondition INT "decodificata"
/// </summary>
/// <param name="ompKey">Chiave da aggiungere</param>
/// <param name="ompVal">Valore da decodificare e poi aggiungere</param>
private void addCheckConditionInt(string ompKey, string ompVal)
{
var newCond = new IntConditionCheck(ompKey, ompVal);
// cerco x aggiornare o aggiungere...
if (OptCheckCondInt.ContainsKey(ompKey))
{
OptCheckCondInt[ompKey] = newCond;
}
else
{
OptCheckCondInt.Add(ompKey, newCond);
}
}
/// <summary>
/// Aggiunge in setup memoria l'oggetto tipo valore BIT (NON mutuamente esclusivo) da tradurre
/// </summary>
/// <param name="csvList">Elenco valori da aggiungere formato csv (es "a,b,c")</param>
private void addVal2TranslBit(string csvList)
{
var list2add = csvList.Split(',');
foreach (var memKey in list2add)
{
// cerco x aggiungere...
if (!OptVar2TranslBit.ContainsKey(memKey))
{
// init traduzione vuota
OptVar2TranslBit.Add(memKey, "");
}
}
}
/// <summary>
/// Aggiunge in setup memoria l'oggetto tipo valore INT (mutuamente esclusivo) da tradurre
/// </summary>
/// <param name="csvList">Elenco valori da aggiungere formato csv (es "a,b,c")</param>
private void addVal2TranslInt(string csvList)
{
var list2add = csvList.Split(',');
foreach (var memKey in list2add)
{
// cerco x aggiungere...
if (!OptVar2TranslInt.ContainsKey(memKey))
{
// init traduzione vuota
OptVar2TranslInt.Add(memKey, "");
}
}
}
/// <summary>
/// Verifica e se necessario comprime directory log...
/// </summary>
private void checkShrinkDir()
{
// comprimo x prima cosa la folder dell'IOB corrente... testo sia IOBConf che FilenameIOB
List<string> path2chk = new List<string>() { IOBConfFull.General.CodIOB, IOBConfFull.General.FilenameIOB, "MAIN" };
string fullPath = "";
foreach (var item in path2chk)
{
fullPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs", item);
try
{
baseUtils.shrinkDir(fullPath);
}
catch (Exception exc)
{
lgError($"Eccezione in checkShrinkDir{Environment.NewLine}{exc}");
}
}
}
/// <summary>
/// retituisce data-ora dell'ODL corrente
/// </summary>
/// <returns></returns>
private async Task<DateTime> currOdlStart()
{
DateTime inizioOdl = DateTime.Now;
string rawDataInizio = await callUrl(urlInizioOdlIob, false);
DateTime.TryParse(rawDataInizio, out inizioOdl);
return inizioOdl;
}
/// <summary>
/// Mostra i dati grezzi letti in esadecimale <paramref name="currDispData">Parametri da
/// aggiornare x display in form</paramref>
/// </summary>
private void displayRawData(ref newDisplayData currDispData)
{
// mostro update...
string newString = string.Format("{0:X}", B_input);
currDispData.newInData = newString;
}
/// <summary>
/// Accumula statistiche daily e di dettaglio x la chiave indicata
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
private void DoTrackDataCount(string key, string value)
{
if (TrackDayStatsCount.ContainsKey(key))
{
TrackDayStatsCount[key]++;
}
else
{
TrackDayStatsCount.Add(key, 1);
}
// dettaglio è insieme key_val
string detKey = $"{key}:{value}";
if (TrackDayStatsCount.ContainsKey(detKey))
{
TrackDayStatsCount[detKey]++;
}
else
{
TrackDayStatsCount.Add(detKey, 1);
}
// salvo anche dettaglio...
if (TrackDetValsCount.ContainsKey(key))
{
TrackDetValsCount[key].Add($"{DateTime.Now:yyyy-MM-dd HH:mm:ss}", value);
}
else
{
Dictionary<string, string> nDict = new Dictionary<string, string>();
nDict.Add($"{DateTime.Now:yyyy-MM-dd HH:mm:ss}", value);
TrackDetValsCount.Add(key, nDict);
}
}
/// <summary>
/// Salva le statistiche accumulate alla giornata di riferimento salvata e resetta contatori
/// </summary>
/// <param name="DayCurr"></param>
private void DoTrackDataSave()
{
string rKey = "";
// verifico se ho almeno num minimo di dati da tracciare...
if (TrackDayStatsCount.Count() > 0)
{
// cerco se ho qualcosa oltre soglia...
int numOver = TrackDayStatsCount.Where(x => x.Value > IOBConfFull.FluxLog.TrackDataThreshold).Count();
// se ho --> processo!
if (numOver > 0)
{
// scadenza: 1 mese
DateTime scadHash = DateTime.Today.AddMonths(1);
// salvo statistiche Day
rKey = $"{redisMan.redIobTrackKey}:DayStats:{LastDayCurr}";
foreach (var item in TrackDayStatsCount)
{
// traccio con scadenza 1 mese da oggi pareto chiamate giornaliere
redisMan.redIncrHashCount(rKey, item.Key, scadHash, item.Value);
}
// salvo statistiche Detail, scadenza 10 gg
scadHash = DateTime.Today.AddDays(10);
foreach (var item in TrackDetStatsCount)
{
// separo statistiche...
var kvp = item.Key.Split(':');
if (kvp.Count() > 1)
{
rKey = $"{redisMan.redIobTrackKey}:DetailStats:{LastDayCurr}:{kvp[0]}";
redisMan.redIncrHashCount(rKey, kvp[1], scadHash, item.Value);
}
}
// salvo dizionario valori dettaglio... scadenza 3 gg
scadHash = DateTime.Today.AddDays(3);
foreach (var item in TrackDetValsCount)
{
rKey = $"{redisMan.redIobTrackKey}:DataLog:{LastDayCurr}:{item.Key}";
redisMan.redSaveHashDict(rKey, item.Value, scadHash);
}
// reset dizionari
TrackDayStatsCount.Clear();
TrackDetStatsCount.Clear();
TrackDetValsCount.Clear();
}
}
// salvo variabile giorno di registrazione
LastDayCurr = $"{DateTime.Today:yyMMdd}";
}
/// <summary>
/// Archivia una cartella in un file zip
/// </summary>
/// <param name="folderPath">Cartelal da archiviare</param>
/// <param name="zipName">Nome del file zip da produrre...</param>
private bool doZipArchiveFolder(string folderPath, string zipName)
{
bool fatto = false;
if (Directory.Exists(folderPath))
{
fatto = fileMover.zippaDirectory(folderPath, zipName);
// se sent elimino vecchia directory...
if (fatto)
{
Directory.Delete(folderPath, true);
}
}
return fatto;
}
/// <summary>
/// Esegue filtraggio dati x bit blinking!!!
/// </summary>
private void filterData()
{
// effettuo filtraggio dei valori letti... inizializzo OUT!
B_output = 0;
// in primis verifico SE ci siano bit blinkng... se non ci sono OUT=IN...
if (IOBConfFull.SignalProc.BlinkFilterMask == 0)
{
B_output = B_input;
}
else
{
// incomincio con i valori NON blinking: questi "passano invariati", inizio a
// sommare nel valore OUT...
B_output = B_input & ~IOBConfFull.SignalProc.BlinkFilterMask;
// calcolo il valore dei BIT che "passano la maschera"
int iBlink = B_input & IOBConfFull.SignalProc.BlinkFilterMask;
// ...aggiungo i "bit che passano"
B_output += iBlink;
// calcolo QUALI valori (tra quelli blink) siano PASSATI da 0 a 1 --> init counters...
BitArray bBlinkStart = new BitArray(new byte[] { Convert.ToByte(iBlink) });
int[] bitsUp = bBlinkStart.Cast<bool>().Select(bit => bit ? 1 : 0).ToArray();
for (int i = 0; i < bitsUp.Length; i++)
{
// SE 1... impostiamo contatori al MAX
if (bitsUp[i] == 1)
{
// se era zero indico START blink...
if (i_counters[i] == 0)
{
lgTrace("START BLINK: B{0}", i);
}
// imposto comunque contatore al cambio fronte...
i_counters[i] = IOBConfFull.SignalProc.BlinkMaxCounter;
}
}
// quelli che sono zero... LI RECUPERO E LI PROCESSO...
int iZero = ~B_input & IOBConfFull.SignalProc.BlinkFilterMask;
BitArray bBlinkEnd = new BitArray(new byte[] { Convert.ToByte(iZero) });
int[] bitsDown = bBlinkEnd.Cast<bool>().Select(bit => bit ? 1 : 0).ToArray();
for (int i = 0; i < bitsDown.Length; i++)
{
// se era a zero (invertito...)
if (bitsDown[i] == 1)
{
// SE è in corso il conteggio...
if (i_counters[i] > 0)
{
// decremento!
i_counters[i] -= 1;
// se è zero NON faccio nulla, altrimenti SOMMO...
if (i_counters[i] > 0)
{
B_output += 1 << i;
}
else
{
lgTrace("END BLINK: B{0}", i);
}
}
}
}
}
}
/// <summary>
/// Legge da conf il valore di demoltiplica lettura dynData (se presente) o ignora e pone a 1...
/// </summary>
private void fixDemFactDynData()
{
demFactDynData = IOBConfFull.FluxLog.DemFactDynData;
}
/// <summary>
/// Verifica se il log di un dato errore sia permesso
/// </summary>
/// <param name="logKey">ID del valore log da loggare/verificare</param>
/// <returns></returns>
private bool logValuePermit(string logKey)
{
bool doLog = false;
if (vetoLogError.ContainsKey(logKey))
{
// verifico se veto scaduto...
if (DateTime.Now > vetoLogError[logKey])
{
doLog = true;
vetoLogError[logKey] = DateTime.Now.AddMinutes(vetoPeriodMin);
}
}
else
{
doLog = true;
vetoLogError.Add(logKey, DateTime.Now.AddMinutes(vetoPeriodMin));
}
return doLog;
}
/// <summary>
/// Classe fittizia in caso di processing GLOBALE di tutto in 1 solo colpo...
/// </summary>
private void processAllMemory()
{
// verifico se sia abilitato processing...
if (queueInEnabCurr)
{
// init genObj display
newDisplayData currDispData = new newDisplayData();
// in primis SALVO valori previous/precedenti
B_previous = B_output;
// poi faccio lettura NUOVI valori
readAllData(ref currDispData);
// eseguo il filtering dei valori (per i bit "blinking")
filterData();
// effettuo confronto valori vecchi/nuovi... SE trovo variazione OPPURE se è passato
// + di un timeout di controllo...
DateTime adesso = DateTime.Now;
bool scaduto = Math.Abs(adesso.Subtract(lastSigVarSent).TotalSeconds) > lastSigVarVeto;
if (B_output != B_previous || (scaduto && !IOBConfFull.Device.DisabResendScaduto))
{
lgDebug($"processAllMemory | B_output: {B_output} | B_previous: {B_previous}");
accodaSigIN(ref currDispData);
lastSigVarSent = adesso;
}
else
{
lgTrace($"processAllMemory | scaduto: {scaduto} | lastSigVarSent: {lastSigVarSent} | B_output: {B_output} | B_previous: {B_previous}");
}
raiseRefresh(currDispData);
}
else
{
lgInfo($"VETO on processAllMemory | queueInEnabCurr = {queueInEnabCurr}");
checkVetoQueueIn();
}
}
private void processMem2Write()
{
// solo SE queue in enabled è true...
if (queueInEnabCurr)
{
DateTime adesso = DateTime.Now;
List<objItem> updatedPar = new List<objItem>();
List<objItem> currWritePar = new List<objItem>();
// ciclo tutti gli oggetti write x vedere se modificati...
foreach (var item in currProdData)
{
bool needWrite = false;
string lastVal = item.Value;
// li cerco su last... se non ci sono o modificati --> salvo da scrivere e copio
if (lastProdData.ContainsKey(item.Key))
{
lastVal = lastProdData[item.Key];
// verifico se variato...
if (!item.Value.Equals(lastProdData[item.Key]))
{
needWrite = true;
lastProdData[item.Key] = item.Value;
}
}
// aggiungo
else
{
needWrite = true;
lastProdData.Add(item.Key, item.Value);
}
if (memMap != null && memMap.mMapWrite.ContainsKey(item.Key))
{
// preparo genObj da scrivere
objItem newWrite = new objItem()
{
uid = item.Key,
name = item.Key,
description = memMap.mMapWrite[item.Key].description,
reqValue = needWrite ? item.Value : "",
value = lastVal, //item.Value,
lastRequest = adesso,
writable = true,
displOrdinal = memMap.mMapWrite[item.Key].displOrdinal
};
// se devo scrivere --> riporto
if (needWrite)
{
updatedPar.Add(newWrite);
}
else
{
currWritePar.Add(newWrite);
}
}
}
// se ho da scrivere... scrivo TUTTI!
if (updatedPar.Count > 0 && ENABLE_MEM_REWRITE)
{
// scrivo valore!
lgInfo($"Chiamata di plcWriteParams da processMem2Write: {updatedPar.Count} updatedPar");
plcWriteParams(ref updatedPar);
// invio su cloud parametri!
utils.callUrl($"{urlUpdateWriteParams}", JsonConvert.SerializeObject(updatedPar));
lgInfo($"Notificato a server scrittura {updatedPar.Count} parametri");
}
else
{
// se scaduto tempo da ultimo invio... e ho valori
if (currWritePar.Count > 0 && (lastWriteParamsUpsert.AddMinutes(vetoSendWriteUpsert) < adesso))
{
// invio su cloud parametri!
var res = utils.callUrl($"{urlUpdateWriteParams}", JsonConvert.SerializeObject(currWritePar));
lgInfo($"Reinviato a server stato {updatedPar.Count} parametri WRITE");
lastWriteParamsUpsert = adesso;
}
}
}
}
/// <summary>
/// Effettua gestioen programma: legge e mostra su display...
/// </summary>
private void processProgram()
{
currPrgName = "";
// se abilitata lettura prgName
if (enablePrgName)
{
if (connectionOk)
{
try
{
currPrgName = getPrgName();
}
catch (Exception exc)
{
lgError($"Eccezione in getPrgName{Environment.NewLine}{exc}");
}
}
else
{
lgError("Errore connessione mancante x getPrgName");
}
}
else
{
currPrgName = lastPrgName;
}
// verifico SE sia cambiato il programma...
if (lastPrgName != currPrgName)
{
// salvo!
lastPrgName = currPrgName;
string sVal = $"[PROG]{currPrgName}";
// chiamo accodamento...
bool sent = accodaFLog("PROG", sVal, qEncodeFLog("PROG", currPrgName));
if (sent)
{
// traccio valore DynData x analisi
trackDynData("PROG", currPrgName);
}
}
}
/// <summary>
/// Processo lettura dati sysinfo
/// </summary>
private void processSysInfo()
{
if (utils.CRB("enableSysInfo"))
{
Dictionary<string, string> currSysInfo = new Dictionary<string, string>();
if (connectionOk)
{
currSysInfo = getSysInfo();
}
else
{
lgError("Errore connessione mancante x getSysInfo");
}
// verifico SE sia cambiato il programma...
if (lastSysInfo != currSysInfo["SYSINFO"])
{
// salvo!
lastSysInfo = currSysInfo["SYSINFO"];
// per ogni valore del dizionario mostro ed accodo!
string sVal = "";
foreach (var item in currSysInfo)
{
// verifico NON sia un ND...
if (item.Key == "ND" || string.IsNullOrEmpty(item.Key))
{
// log anomalia...
lgTrace($"Errore in processSysInfo: item.key risulta ND! | item.key: {item.Key} | item.Value: {item.Value}");
}
else
{
sVal = string.Format("[SYSINFO]{0}|{1}", item.Key, item.Value);
// chiamo accodamento...
bool sent = accodaFLog(item.Key, sVal, qEncodeFLog(item.Key, item.Value));
if (sent)
{
// traccio valore DynData x analisi
trackDynData(item.Key, item.Value);
}
}
}
}
}
}
/// <summary>
/// Effettua calcolo dei consumi + esportazione in file richiesto
/// </summary>
/// <param name="folderPath">Folder dei file da processare</param>
/// <param name="reportPath">Percorso file out di export</param>
private bool RecipeDoConsumeReport(string folderPath, string reportPath)
{
// var di base
bool fatto = false;
string expMode = "csv";
List<ConsRec> ListConsDet = new List<ConsRec>();
List<ConsOut> ListConsSum = new List<ConsOut>();
// file conf x conversioni...
string confSetupPath = pathList["fullPath-confSetup"];
ConvSetup currConf = new ConvSetup();
bool addHeader = false;
int numDec = 4;
string codMag = "NA";
if (!string.IsNullOrEmpty(confSetupPath))
{
string rawConfFile = File.ReadAllText(confSetupPath);
if (!string.IsNullOrEmpty(rawConfFile))
{
currConf = JsonConvert.DeserializeObject<ConvSetup>(rawConfFile);
if (currConf != null)
{
addHeader = currConf.addHeader;
numDec = currConf.numDec;
codMag = currConf.codMag;
expMode = currConf.mode.ToLower();
}
}
}
// ciclo x ogni file ricevendone i consumi ed accumulandoli in lista...
var fileList = Directory.GetFiles(folderPath);
foreach (var cFile in fileList)
{
var consumiFile = RecipeGetCons(cFile);
ListConsDet.AddRange(consumiFile);
}
// se ho trovato dati
double ratioConv = 1;
if (ListConsDet.Count > 0)
{
var dirName = new DirectoryInfo(folderPath).Name;
// recupero elenco colori
var elencoColori = ListConsDet
.GroupBy(x => x.ColourCode)
.Select(grp => grp.Last())
.ToList();
// faccio le somme x colore
foreach (var colore in elencoColori)
{
ratioConv = 1;
// ricerca in conf (% di consumo)
if (currConf != null && currConf.convRatio != null)
{
if (currConf.convRatio.ContainsKey(colore.ColourCode))
{
ratioConv = currConf.convRatio[colore.ColourCode];
}
}
// calcolo peso equivalente in kg con conversione ratio
var totWeightKg = ListConsDet
.Where(x => x.ColourCode == colore.ColourCode)
.Sum(x => x.WeightGrTot) / 1000 * ratioConv;
// record finale trasformato in KG...
ConsOut colTotal = new ConsOut()
{
CodMag = codMag,
ColourCode = colore.ColourCode,
Description = colore.Description,
UM = "KG",
WeightKgProc = totWeightKg > 0.01 ? Math.Round(totWeightKg, numDec) : 0.01,
DtRif = colore.DtRif
};
ListConsSum.Add(colTotal);
}
// infine serializzo x output in base alla modalità richiesta
switch (expMode)
{
case "fixwidth":
fatto = DataExport.SaveFixedWidth(ListConsSum, $"{reportPath}.txt", currConf.fieldLength, currConf.fieldLPad, currConf.fieldNDec);
break;
case "csv":
default:
fatto = DataExport.SaveToCsv(ListConsSum, $"{reportPath}.csv", addHeader);
break;
}
}
return fatto;
}
/// <summary>
/// Processa la ricetta alla ricerca dei dati PODL x inviare chiamate ad MP-IO
/// </summary>
/// <param name="recipeFile">Path RIcetta</param>
private bool RecipeDoProcessPODL(string recipeFile)
{
bool answ = false;
// init var
int idxPOdl = 0;
string rawVal = "";
// leggo righe file!
var recipeRows = File.ReadAllLines(recipeFile);
// per ogni file ricevuto controlla se viene dal MES (cerca <Variant>PODL)
string tokenSearch = "<Variant>PODL";
// recupero riga PODL...
var rowPodl = recipeRows.Where(x => x.Contains(tokenSearch)).FirstOrDefault();
if (rowPodl != null)
{
DateTime dtStartPOdl = DateTime.Today;
int duration = 0;
DateTime dtEndPOdl = DateTime.Today.AddMinutes(10);
string dtEve = "";
string dtCurr = "";
// inizio ricerca PODL
rawVal = rowPodl.Trim().Replace(tokenSearch, "").Replace("</Variant>", "");
int.TryParse(rawVal, out idxPOdl);
if (idxPOdl > 0)
{
// leggo inizio commessa
tokenSearch = "<Date>";
var rowStart = recipeRows.Where(x => x.Contains(tokenSearch)).FirstOrDefault();
if (rowStart != null)
{
rawVal = rowStart.Trim().Replace(tokenSearch, "").Replace("</Date>", "");
DateTime.TryParse(rawVal, out dtStartPOdl);
}
// leggo durata commessa
tokenSearch = "<DosDuration>";
var rowDurSec = recipeRows.Where(x => x.Contains(tokenSearch)).FirstOrDefault();
if (rowDurSec != null)
{
rawVal = rowDurSec.Trim().Replace(tokenSearch, "").Replace("</DosDuration>", "");
int.TryParse(rawVal, out duration);
dtEndPOdl = dtStartPOdl.AddSeconds(duration);
}
try
{
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
// prova ad avviare/chiudere PODL relativo (eventualmente duplicandolo)
dtEve = $"{dtStartPOdl:yyyyMMddHHmmssfff}";
dtCurr = $"{DateTime.Now:yyyyMMddHHmmssfff}";
await callUrl($"{urlOdlStartFromPOdl}{idxPOdl}&dtEve={dtEve}&dtCurr={dtCurr}", false);
// ora chiamo chiusura...
dtEve = $"{dtEndPOdl:yyyyMMddHHmmssfff}";
dtCurr = $"{DateTime.Now:yyyyMMddHHmmssfff}";
await callUrl($"{urlPODLClose}{idxPOdl}&dtEve={dtEve}&dtCurr={dtCurr}", false);
})
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
lgError($"Errore in RecipeDoProcessPODL.callUrl: {ex.Message}");
}
}
answ = true;
}
return answ;
}
/// <summary>
/// Elimino record da REDIS (locale e remoto)
/// </summary>
/// <param name="keyReq"></param>
private async Task RecipeRemoveWeekStatus(string keyReq)
{
string fullKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:WeekStats");
var okHashDict = redisMan.redRemoveHashField(fullKey, keyReq);
// rileggo status hash
Dictionary<string, string> currDict = redisMan.redGetHashDict(fullKey);
// invio ANCHE in MP-IO l'update delle info...
string remUrl = urlSetHashDict;
string dictPayload = JsonConvert.SerializeObject(currDict);
await callUrlWithPayloadAsync(remUrl, dictPayload, true);
//await callUrlWithPayloadAsync(remUrl, dictPayload, false);
}
private void reportDataProc()
{
// update valori visualizzazione...
parentForm.dataProcLabel = string.Format("RAW: {0} --> IN: {1} --> OUT: {2}", nReadIN, nReadFilt, nSendOut);
}
/// <summary>
/// Imposto alcuni valori di default
/// </summary>
/// <param name="resetQueue">indica se sia richiesto di SVUOTARE le code delle info</param>
private void setDefaults(bool resetQueue)
{
numSim = utils.CRI("numSim");
lastPrgName = "";
nReadIN = 0;
nReadFilt = 0;
nSendOut = 0;
currMode = 0;
lastAlarm = "";
doStartMemDump = utils.CRB("doStartMemDump");
doSampleMemory = utils.CRB("doSampleMemory");
// fix url wait random...
urlRandWait = utils.CRI("urlRandWait");
// fix fattore demoltiplica dynData
fixDemFactDynData();
// svuoto code se richiesto
if (resetQueue)
{
string codIob = IOBConfFull.General.FilenameIOB;
bool useRedis = IOBConfFull.General.EnabRedisQue;
QueueAlarm = new DataQueue(codIob, "QueueAlarm", false, redisMan);
QueueIN.Dispose();
QueueIN = new DataQueue(codIob, "QueueIN", useRedis, redisMan);
// no coda redis
QueueFLog = new DataQueue(codIob, "QueueFLog", false, redisMan);
QueueMessages = new DataQueue(codIob, "QueueMessages", false, redisMan);
QueueRawTransf = new DataQueue(codIob, "QueueRawTransf", false, redisMan);
QueueULog = new DataQueue(codIob, "QueueULog", false, redisMan);
QueuePing = new DataQueue(codIob, "QueuePing", false, redisMan);
}
// imposto contatori blink a zero...
i_counters = new int[32];
lastPeriodicLog = DateTime.Now;
// fix parametri generali...
pzCountDelay = IOBConfFull.Device.PzCountDelay;
enablePrgName = IOBConfFull.Device.EnabProgName;
enablePzCountByApp = IOBConfFull.Device.EnabPzCount;
mem2trace = IOBConfFull.Special.BankConf != null ? IOBConfFull.Special.BankConf.Mem2Trace : "";
numArtCharTrim = IOBConfFull.Device.NumArtCharTrim;
// valore standard divieto accodamento segnali IN
vetoQueueIn = IOBConfFull.Device.StartupVetoQueueIN;
// fix slow data
enableSlowData = IOBConfFull.FluxLog.EnableSlowData;
ENABLE_MEM_REWRITE = IOBConfFull.Device.EnabMemRewrite;
GLOBAL_DBAND = IOBConfFull.FluxLog.GlobalDeadBand;
enabSendMachineConf = IOBConfFull.General.SenMachineConf;
resetAlarmOnStart = IOBConfFull.General.ResetAlarmOnStart;
disableOdl = IOBConfFull.Odl.DisableOdl;
}
private async Task SvuotaCodaContapezziAsync()
{
// permetto al max 2 tentativi infruttuosi...
int maxTry = 2;
int oldContapezzi = contapezziIOB;
// SE HO gestione contapezzi...
if (!IOBConfFull.Device.EnabPzCount)
{
lgDebug($"Contapezzi disabilitato da configuraizone: IOBConfFull.Device.EnabPzCount: {IOBConfFull.Device.EnabPzCount}");
}
else
{
// se ho contapezzi OLTRE limite...
while ((MPOnline) && (contapezziPLC > contapezziIOB + minSendPzCountBlock))
{
lgInfo($"Ciclo SvuotaCodaContapezziAsync --> contapezziPLC: {contapezziPLC} | contapezziIOB: {contapezziIOB}");
if (!isMulti)
{
pzCntReload(true);
}
// provo invio
if (!isMulti)
{
trySendPzCountBlock();
}
// verifica per evitare loop infinito invio fallito
if (oldContapezzi == contapezziIOB)
{
maxTry--;
}
else
{
maxTry = 2;
oldContapezzi = contapezziIOB;
}
// verifico maxTry: se li ho esauriti esco!
if (maxTry <= 0)
{
return;
}
// aspetto x dare tempo calcolo
await Task.Delay(500);
}
}
}
/// <summary>
/// Processo la coda FLog...
/// </summary>
private async Task SvuotaCodaFLogAsync()
{
bool sendOnMachineOff = false;
// controllo se è passato oltre watchdog e non ho inviato nulla --> RE-INVIO (ultimo inviato)!!!!
if (DateTime.Now.Subtract(lastWatchDog).TotalSeconds > IOBConfFull.General.WatchDogSec)
{
string wdStatus = "elapsed";
string sVal = string.Format("[WDST]{0}", wdStatus);
// chiamo accodamento... SE non disabilitato..
if (!disableWdst)
{
bool sent = accodaFLog("WDST", sVal, qEncodeFLog("WDST", wdStatus));
if (sent)
{
// traccio valore DynData x analisi
trackDynData("WDST", wdStatus);
}
}
lastWatchDog = DateTime.Now;
sendOnMachineOff = true;
lgInfo("Impostato sendOnMachineOff a true");
}
// verifico SE la coda abbia dei valori...
if (QueueFLog.Count > 0)
{
// invio pacchetto di dati (max da conf)
for (int i = 0; i < nMaxSend; i++)
{
// SE ho qualcosa in coda...
if (QueueFLog.Count > 0)
{
string currVal = "";
if (MPOnline)
{
if (IobOnline || sendOnMachineOff)
{
// se ho + di 2 elementi in coda --> uso invio JSON in blocco...
if (QueueFLog.Count > 1)
{
List<string> listaValori = new List<string>();
// se ho + di maxJsonData elementi --> invio un set di dati alla volta
if (QueueFLog.Count > maxJsonData)
{
// prendoi primi maxJsonDataValori
for (int j = 0; j < maxJsonData; j++)
{
QueueFLog.TryDequeue(out currVal);
listaValori.Add(currVal);
}
await sendDataBlock(urlType.FLog, listaValori);
lastWatchDog = DateTime.Now;
}
else
{
// invio in blocco
listaValori = QueueFLog.ToList();
// invio
await sendDataBlock(urlType.FLog, listaValori);
// svuoto! NO redis
QueueFLog = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueFLog", false, redisMan);
//QueueFLog = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueFLog", IOBConfFull.General.EnabRedisQue, redisMan);
lastWatchDog = DateTime.Now;
}
}
else
{
// INVIO SINGOLO...!!!
QueueFLog.TryDequeue(out currVal);
await sendToMoonPro(urlType.FLog, currVal);
lastWatchDog = DateTime.Now;
}
}
else
{
break;
}
}
else
{
break;
}
}
else
{
break;
}
}
}
}
/// <summary>
/// Processo la coda RawTransf...
/// </summary>
private async Task<bool> SvuotaCodaRawTransfAsync(bool force = false)
{
bool fatto = false;
// verifico SE la coda abbia dei valori...
if (QueueRawTransf.Count > 0)
{
// invio pacchetto di dati (max da conf)
for (int i = 0; i < nMaxSend; i++)
{
// SE ho qualcosa in coda...
if (QueueRawTransf.Count > 0)
{
string currVal = "";
if (MPOnline || force)
{
if (IobOnline || force)
{
List<string> listaValori = new List<string>();
// se ho + di maxJsonData elementi --> invio un set di dati alla volta
if (QueueRawTransf.Count > maxJsonData)
{
// prendoi primi maxJsonDataValori
for (int j = 0; j < maxJsonData; j++)
{
QueueRawTransf.TryDequeue(out currVal);
listaValori.Add(currVal);
}
fatto = await sendDataBlock(urlType.RawTransf, listaValori, force);
}
else
{
// invio in blocco
listaValori = QueueRawTransf.ToList();
// invio
fatto = await sendDataBlock(urlType.RawTransf, listaValori, force);
if (fatto)
{
// svuoto se ha okReport!
QueueRawTransf = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueRawTransf", false, redisMan);
}
}
}
else
{
break;
}
}
else
{
break;
}
}
else
{
break;
}
}
}
return fatto;
}
/// <summary>
/// Processo la coda UserLog...
/// </summary>
private async Task SvuotaCodaULogAsync()
{
// verifico SE la coda abbia dei valori...
if (QueueULog.Count > 0)
{
// invio pacchetto di dati (max da conf)
for (int i = 0; i < nMaxSend; i++)
{
// SE ho qualcosa in coda...
if (QueueULog.Count > 0)
{
string currVal = "";
if (MPOnline)
{
if (IobOnline)
{
List<string> listaValori = new List<string>();
// se ho + di maxJsonData elementi --> invio un set di dati alla volta
if (QueueULog.Count > maxJsonData)
{
// prendoi primi maxJsonDataValori
for (int j = 0; j < maxJsonData; j++)
{
QueueULog.TryDequeue(out currVal);
listaValori.Add(currVal);
}
await sendDataBlock(urlType.ULog, listaValori);
}
else
{
// invio in blocco
listaValori = QueueULog.ToList();
// invio
await sendDataBlock(urlType.ULog, listaValori);
// svuoto!
QueueULog = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueULog", false, redisMan);
}
}
else
{
break;
}
}
else
{
break;
}
}
else
{
break;
}
}
}
}
#endregion Private Methods
}
}