fa976dd2bb
- aggiunto InitializeAsync - gestione override x ogni IOB
3793 lines
160 KiB
C#
3793 lines
160 KiB
C#
using EgwProxy.Ftp;
|
|
using IOB_UT_NEXT;
|
|
using IOB_UT_NEXT.Config;
|
|
using MapoSDK;
|
|
using MathNet.Numerics.Statistics;
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
using NLog;
|
|
using NLog.Targets.Wrappers;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.NetworkInformation;
|
|
using System.Text;
|
|
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 Public Fields
|
|
|
|
public int numPzReqOdl = 0;
|
|
|
|
#endregion Public Fields
|
|
|
|
#region Public Constructors
|
|
|
|
/// <summary>
|
|
/// inizializzo l'oggetto sulla form SULLA BASE DEL FILE DI CONFIGURAZIONE letto
|
|
/// </summary>
|
|
/// <param name="caller">Form chiamante</param>
|
|
/// <param name="IobConfNew">Configurazione (v 4.x)</param>
|
|
public Generic(AdapterForm caller, IobConfTree IobConfNew)
|
|
{
|
|
// salvo il form chiamante
|
|
parentForm = caller;
|
|
if (IobConfNew != null)
|
|
{
|
|
// salvo configurazione...
|
|
IOBConfFull = IobConfNew;
|
|
|
|
// init oggetto redis...
|
|
redisMan = new RedisIobCache(IobConfNew.MapoMes.IpAddr, IobConfNew.General.FilenameIOB, $"{IobConfNew.General.IobType}", IobConfNew.General.MinDeltaSec);
|
|
|
|
// init code
|
|
SetupQueue();
|
|
|
|
// initi oggetto TCMan
|
|
tcMan = new TCMan(IobConfNew.TCDataConf.Lambda, IobConfNew.TCDataConf.MaxDelayFactor, IobConfNew.TCDataConf.MaxIncrPz);
|
|
|
|
|
|
lastConnectTry = DateTime.Now;
|
|
|
|
lgInfo("Avvio preliminare AdapterGeneric");
|
|
lastLogStartup = DateTime.Now;
|
|
|
|
// setup currProdData & last prod data
|
|
currProdData = redisMan.redGetHashDict(rKeyCurrProdData);
|
|
// i last li avvio a VUOTI... x evitare errore mancata riscrittura SIMEC che ha memorie NON ritentive
|
|
lastProdData = new Dictionary<string, string>();
|
|
//lastProdData = new Dictionary<string, string>(currProdData);
|
|
// aggiungo altri defaults
|
|
setDefaults(true);
|
|
// imposta valori memoria (e resetta parametri su server)
|
|
setParamPlc();
|
|
|
|
// checkLogDir x shrink!
|
|
checkShrinkDir();
|
|
|
|
// imposto veto invio per i prossimi sec
|
|
dtVetoQueueIN = DateTime.Now.AddSeconds(vetoQueueIn);
|
|
lgInfoStartup($"Impostato veto QUEUE-IN a {vetoQueueIn}s | fino alle {dtVetoQueueIN:yyyy.MM.dd HH:mm:ss}");
|
|
|
|
// invio info IOB
|
|
SendM2IOB();
|
|
// invio altri dati accessori...
|
|
SendMachineConf();
|
|
if (resetAlarmOnStart)
|
|
{
|
|
SendAlarmReset();
|
|
}
|
|
// concluso!
|
|
lgInfoStartup("Istanziata classe preliminare IOBGeneric");
|
|
}
|
|
else
|
|
{
|
|
lgError("Error: IobCOnf is null!");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Area init asincrono (fare override async!o)
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public virtual Task InitializeAsync()
|
|
{
|
|
// da usare per implementare logiche di init specifiche
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
#endregion Public Constructors
|
|
|
|
#region Public Events
|
|
|
|
/// <summary>
|
|
/// Evento Iob ha subito un refresh
|
|
/// </summary>
|
|
public event EventHandler<iobRefreshedEventArgs> eh_refreshed;
|
|
|
|
#endregion Public Events
|
|
|
|
#region Public Properties
|
|
|
|
/// <summary>
|
|
/// Salva verifica stato connessione OK
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public virtual bool connectionOk
|
|
{
|
|
get
|
|
{
|
|
return _connOk || DemoIn;
|
|
}
|
|
set
|
|
{
|
|
_connOk = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contapezzi attuale
|
|
/// </summary>
|
|
public Int32 contapezziIOB
|
|
{
|
|
get
|
|
{
|
|
return tcMan.pzCountIOB;
|
|
}
|
|
set
|
|
{
|
|
tcMan.pzCountIOB = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ultima lettura variabile contapezzi da CNC
|
|
/// </summary>
|
|
public Int32 contapezziPLC
|
|
{
|
|
get
|
|
{
|
|
return tcMan.pzCountPLC;
|
|
}
|
|
set
|
|
{
|
|
tcMan.pzCountPLC = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contatore x invio dati FluxLog
|
|
/// </summary>
|
|
public int counterFLog { get; set; }
|
|
|
|
/// <summary>
|
|
/// Contatore x invio dati RawTransf
|
|
/// </summary>
|
|
public int counterRawTransf { get; set; }
|
|
|
|
/// <summary>
|
|
/// Contatore x invio dati SignalIN
|
|
/// </summary>
|
|
public int counterSigIN { get; set; }
|
|
|
|
/// <summary>
|
|
/// Contatore x invio dati UserLog
|
|
/// </summary>
|
|
public int counterULog { get; set; }
|
|
|
|
/// <summary>
|
|
/// nome Programma corrente
|
|
/// </summary>
|
|
public string currPrgName { get; set; }
|
|
|
|
/// <summary>
|
|
/// Verifica se sia in modalità DEMO --> da tipo IOB SIMULA...
|
|
/// </summary>
|
|
public bool DemoIn
|
|
{
|
|
get => IOBConfFull.General.IobType == tipoAdapter.SIMULA;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dizionario contapezzi Macchina (valori da impianto) x macchine multi tavola/pallet
|
|
/// </summary>
|
|
public Dictionary<string, int> DictPzCountImp { get; set; } = new Dictionary<string, int>();
|
|
|
|
/// <summary>
|
|
/// Dizionario contapezzi MES (valori salvati su server) x macchine multi tavola/pallet
|
|
/// </summary>
|
|
public Dictionary<string, int> DictPzCountMes { get; set; } = new Dictionary<string, int>();
|
|
|
|
/// <summary>
|
|
/// Indica se la chiamata WDST dit racking watchdog sia disabilitata dall'invio nel FluxLog
|
|
/// </summary>
|
|
public bool disableWdst { get; set; } = false;
|
|
|
|
/// <summary>
|
|
/// Indica lo stato Online/Offline della IOB
|
|
/// </summary>
|
|
public bool IobOnline
|
|
{
|
|
get
|
|
{
|
|
return utils.IOB_Online;
|
|
}
|
|
set
|
|
{
|
|
utils.IOB_Online = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifica se sia macchina multi = DoppioPallet da CONF
|
|
/// </summary>
|
|
public bool isMulti
|
|
{
|
|
get => IOBConfFull.Device.IsMulti;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Log verboso da configurazione (SOLO CHIAVE "verbose"...)
|
|
/// </summary>
|
|
public bool isVerboseLog { get; set; } = utils.CRB("verbose");
|
|
|
|
/// <summary>
|
|
/// Ultimo Alarm letto
|
|
/// </summary>
|
|
public string lastAlarm { get; set; }
|
|
|
|
/// <summary>
|
|
/// Ultimo ARRAY DynData letto
|
|
/// </summary>
|
|
public Dictionary<string, string> lastDynData { get; set; } = new Dictionary<string, string>();
|
|
|
|
/// <summary>
|
|
/// Ultimo DynData (sunto) letto
|
|
/// </summary>
|
|
public string lastDynDataCtrlVal { get; set; }
|
|
|
|
/// <summary>
|
|
/// Ultimo Override set letto
|
|
/// </summary>
|
|
public string lastOverrideFS { get; set; }
|
|
|
|
/// <summary>
|
|
/// Ultimo Override set letto
|
|
/// </summary>
|
|
public string lastOverrideRapid { get; set; }
|
|
|
|
/// <summary>
|
|
/// Ultimo programma letto
|
|
/// </summary>
|
|
public string lastPrgName { get; set; }
|
|
|
|
/// <summary>
|
|
/// Ultimo SysInfo letto
|
|
/// </summary>
|
|
public string lastSysInfo { get; set; }
|
|
|
|
/// <summary>
|
|
/// Ultimo URL
|
|
/// </summary>
|
|
public string lastUrl { get; set; }
|
|
|
|
/// <summary>
|
|
/// Valore massimo accettato x incremento pezzi letti dal PLC (valore moltiplicato per
|
|
/// 100... 200% --> 200)
|
|
/// </summary>
|
|
public int maxPzDeltaPerc
|
|
{
|
|
get => IOBConfFull.Counters.MaxIncrPzCountPerc;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifica SE si debba fare log periodico (ogni "verboseLogTOut" sec...)
|
|
/// </summary>
|
|
public bool periodicLog
|
|
{
|
|
get
|
|
{
|
|
bool answ = false;
|
|
answ = (DateTime.Now.Subtract(lastPeriodicLog).TotalSeconds > utils.CRI("verboseLogTOut"));
|
|
if (answ)
|
|
{
|
|
lastPeriodicLog = DateTime.Now;
|
|
}
|
|
|
|
return answ;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Valore medio del TC rilevato x verifica derive sul delta variazione contapezzi
|
|
/// </summary>
|
|
public double plcAvgTc
|
|
{
|
|
get
|
|
{
|
|
double answ = tcMan.avgTC > 0 ? tcMan.avgTC : 1;
|
|
return answ;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// DataOra dell'ultima lettura variabile contapezzi da CNC
|
|
/// </summary>
|
|
public DateTime plcLastPzRead
|
|
{
|
|
get
|
|
{
|
|
return tcMan.lastObservedData;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finestra dei byte da mostrare di default x il RawDataInput
|
|
/// </summary>
|
|
public int RawDataInputSize { get; set; } = 8;
|
|
|
|
/// <summary>
|
|
/// Indice di partenza della memoria RawData Input da mostrare
|
|
/// </summary>
|
|
public int RawDataInputStart { get; set; } = 0;
|
|
|
|
/// <summary>
|
|
/// URL per segnalazione reboot...
|
|
/// </summary>
|
|
public string urlReboot
|
|
{
|
|
get => $@"{urlCommandIobFile("sendReboot")}?mac={GetMACAddress()}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// URL per salvataggio dati conf YAML completi IOB...
|
|
/// </summary>
|
|
public string urlSaveConfYaml
|
|
{
|
|
get => $@"{urlCommandIobFile("saveConfYaml")}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifica SE si debba fare log verboso (verboso + ogni tot letture IN)
|
|
/// </summary>
|
|
public bool verboseLog
|
|
{
|
|
get
|
|
{
|
|
bool answ = false;
|
|
int logEvery = utils.CRI("logEvery");
|
|
if (logEvery < 1)
|
|
{
|
|
logEvery = 10;
|
|
}
|
|
|
|
answ = utils.CRB("verbose") && (nReadIN % logEvery == 0);
|
|
return answ;
|
|
}
|
|
}
|
|
|
|
#endregion Public Properties
|
|
|
|
#region Public Methods
|
|
|
|
/// <summary>
|
|
/// Esegue conversione in un dizionario di tipo string/string serializzando e deserializzando
|
|
/// </summary>
|
|
/// <param name="input"></param>
|
|
/// <returns></returns>
|
|
public static Dictionary<string, string> ConvertToStringDict(Dictionary<string, object> input)
|
|
{
|
|
return input.ToDictionary(pair => pair.Key, pair => pair.Value?.ToString());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accumula in coda i valori ALARM e logga...
|
|
/// </summary>
|
|
/// <param name="val">VALORE RAW (x display)</param>
|
|
/// <param name="encodedVal">VALORE già processato con qEncodeFLog(...)</param>
|
|
public void accodaAlarmLog(string val, string encodedVal)
|
|
{
|
|
// mostro dati variati letti...
|
|
displayOtherData(val);
|
|
// accodo IN PRIMIS al FluxLog --> accodo (valore già formattato)!
|
|
QueueFLog.Enqueue(encodedVal);
|
|
// accodo ANCHE alla coda allarmi che trasmetterò SOLO SE a scadenza impostata (10 sec?)
|
|
// ho allarmi perdurati...
|
|
|
|
// loggo!
|
|
lgInfo(string.Format("[QUEUE-ALARM-LOG] {0}", encodedVal));
|
|
counterFLog++;
|
|
if (counterFLog > 9999)
|
|
{
|
|
counterFLog = 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accumula in coda i valori Flux Log e logga. Restituisce true se inviato (x track su REDIS)
|
|
/// </summary>
|
|
/// <param name="codFlux">Nome del flusso da inviare (per verifica veto invio)</param>
|
|
/// <param name="val">VALORE RAW (x display)</param>
|
|
/// <param name="encodedVal">VALORE già processato con qEncodeFLog(...)</param>
|
|
public bool accodaFLog(string codFlux, string val, string encodedVal)
|
|
{
|
|
bool enabled = false;
|
|
// verifico se il parametro sia abilitato...
|
|
if (IOBConfFull.Memory != null && IOBConfFull.Memory.mMapRead != null && IOBConfFull.Memory.mMapRead.ContainsKey(codFlux))
|
|
{
|
|
enabled = IOBConfFull.Memory.mMapRead[codFlux].sendEnabled;
|
|
if (!enabled)
|
|
{
|
|
lgDebug($"accodaFLog : invio bloccato per conf aprametro sendEnabled | codFlux: {codFlux}");
|
|
}
|
|
else
|
|
{
|
|
// processo SOLO SE ho dei valori non nulli x encodedVal...
|
|
if (!string.IsNullOrEmpty(encodedVal))
|
|
{
|
|
// mostro dati variati letti...
|
|
displayOtherData(val);
|
|
// --> accodo (valore già formattato)!
|
|
QueueFLog.Enqueue(encodedVal);
|
|
// se abilitato controllo coda FLog (superiore a 0...)
|
|
if (maxQueueFLog > 0)
|
|
{
|
|
// se ho una coda superiore a max ammesso
|
|
if (QueueFLog.Count > maxQueueFLog)
|
|
{
|
|
// elimino valori iniziali fino a tornare al max ammesso...
|
|
while (QueueFLog.Count > maxQueueFLog)
|
|
{
|
|
string currVal = "";
|
|
QueueFLog.TryDequeue(out currVal);
|
|
lgInfo($"Eliminazione da coda FLog per superamento maxLengh: {currVal}");
|
|
}
|
|
}
|
|
}
|
|
// loggo!
|
|
lgTrace($"[QUEUE-FLOG] {encodedVal}");
|
|
counterFLog++;
|
|
if (counterFLog > 9999)
|
|
{
|
|
counterFLog = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgTrace($"ERRORE in [QUEUE-FLOG] : encodedVal vuoto | val: {val}");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// registro nel dizionario dei valori FluxLog filtrati
|
|
SaveFiltFluxLog(codFlux);
|
|
// verifico se registrare elenco valori filtrati in log...
|
|
FiltFluxLogCheckSave(100);
|
|
}
|
|
return enabled;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accoda (visualizzando in cima allo stack) la nuova stringa di output per area OTHER DATA
|
|
/// </summary>
|
|
/// <param name="newLine"></param>
|
|
public void accodaOtherData(string newLine)
|
|
{
|
|
// inserisco in cima allo stack
|
|
parentForm.WriteTextSafe(newLine);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accumula in coda i valori RawData + log
|
|
/// </summary>
|
|
/// <param name="mesType"></param>
|
|
/// <param name="mesContent"></param>
|
|
public void accodaRawData(rawTransfType mesType, object mesContent)
|
|
{
|
|
/*--------------------------------
|
|
* nuova gestione coda dictionary
|
|
* fixme todo da fare !!!
|
|
*
|
|
* - conterrà una lista di oggetti baseRawTransf
|
|
* - i dati vanno poi "scodati" dal + vecchio ed inviati a MP/IO
|
|
* - mostra un sunto delle info da inviare
|
|
* - accodamento vero e proprio
|
|
* - verifica (opzionale) coda massima x gestire roundRobin ultimi eventi
|
|
* - trace della coda
|
|
* - counter invio??? valutare se c'è dataora e poi sono da salvare su MongoDb / Redis
|
|
*
|
|
* */
|
|
|
|
// serializzo il valore...
|
|
JObject njObj;
|
|
if (mesType == rawTransfType.IcoelBatch || mesType == rawTransfType.IcoelVarInfo)
|
|
{
|
|
njObj = (JObject)mesContent;
|
|
}
|
|
else
|
|
{
|
|
njObj = (JObject)JToken.FromObject(mesContent);
|
|
}
|
|
BaseRawTransf newVal = new BaseRawTransf(DateTime.Now, njObj, mesType);
|
|
|
|
string encodedVal = JsonConvert.SerializeObject(newVal);
|
|
// --> accodo (valore già formattato)!
|
|
QueueRawTransf.Enqueue(encodedVal);
|
|
// se abilitato controllo coda Max (superiore a 0...)
|
|
if (maxQueueRawTransf > 0)
|
|
{
|
|
// se ho una coda superiore a max ammesso
|
|
if (QueueRawTransf.Count > maxQueueRawTransf)
|
|
{
|
|
// elimino valori iniziali fino a tornare al max ammesso...
|
|
while (QueueRawTransf.Count > maxQueueRawTransf)
|
|
{
|
|
string currVal = "";
|
|
QueueRawTransf.TryDequeue(out currVal);
|
|
lgInfo($"Eliminazione da coda RawTransf per superamento maxLengh: {currVal}");
|
|
}
|
|
}
|
|
}
|
|
// loggo!
|
|
lgTrace(string.Format("[QUEUE-RTRANSF] {0}", encodedVal));
|
|
counterRawTransf++;
|
|
if (counterRawTransf > 9999)
|
|
{
|
|
counterRawTransf = 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accumula in coda i valori Signal IN e logga...
|
|
/// <paramref name="currDispData">Parametri da aggiornare x display in form</paramref>
|
|
/// </summary>
|
|
public void accodaSigIN(ref newDisplayData currDispData)
|
|
{
|
|
DateTime adesso = DateTime.Now;
|
|
lgDebug($"accodaSigIN 01 | qEncodeIN: {qEncodeIN}");
|
|
// mostro dati variati letti...
|
|
displayInData(ref currDispData);
|
|
// verifico veto a invio status macchina
|
|
if (IOBConfFull.Device.DisabSigIn)
|
|
{
|
|
lgTrace($"Filtrato accodamento valore da conf IOB | DisabSigIn | [QUEUE-IN] {qEncodeIN}");
|
|
}
|
|
else
|
|
{
|
|
// verifico non sia in veto invio iniziale...
|
|
if (queueInEnabCurr)
|
|
{
|
|
// --> accodo (valore già formattato)!
|
|
QueueIN.Enqueue(qEncodeIN);
|
|
// loggo!
|
|
lgDebug($"[QUEUE-IN] {qEncodeIN}");
|
|
#if false
|
|
B_output_sent = B_output;
|
|
// verifico il valore encoded sia variato (x evitare doppioni successivi...)
|
|
if (B_output_sent != B_output || (nReadFilt % 2 == 1))
|
|
{
|
|
}
|
|
else
|
|
{
|
|
lgDebug($"NON accodado valore [QUEUE-IN] {qEncodeIN} | B_output_sent: {B_output_sent} | B_output: {B_output}");
|
|
}
|
|
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
lgTrace($"[VETO FOR QUEUE-IN] | {qEncodeIN} - MESSAGE NOT SENT | {adesso:yyyyMMdd_HHmmss}");
|
|
checkVetoQueueIn();
|
|
}
|
|
// aggiorno counters ed eventuale reset
|
|
nReadFilt++;
|
|
if (nReadFilt > int.MaxValue - 1)
|
|
{
|
|
nReadFilt = 0; // per evitare buffer overflow...
|
|
}
|
|
|
|
counterSigIN++;
|
|
if (counterSigIN > 9999)
|
|
{
|
|
counterSigIN = 0;
|
|
}
|
|
}
|
|
lgDebug($"accodaSigIN 02 | nReadFilt: {nReadFilt} | counterSigIN: {counterSigIN} | QueueIN len: {QueueIN.Count}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accumula in coda i valori USER LOG e logga...
|
|
/// </summary>
|
|
/// <param name="val">VALORE RAW (x display)</param>
|
|
/// <param name="encodedVal">VALORE già processato con qEncodeULog(...)</param>
|
|
public void accodaUserLog(string val, string encodedVal)
|
|
{
|
|
// mostro dati variati letti...
|
|
displayOtherData(val);
|
|
// accodo IN PRIMIS al FluxLog --> accodo (valore già formattato)!
|
|
QueueULog.Enqueue(encodedVal);
|
|
|
|
// loggo!
|
|
lgInfo(string.Format("[QUEUE-USER-LOG] {0}", encodedVal));
|
|
counterULog++;
|
|
if (counterULog > 9999)
|
|
{
|
|
counterFLog = 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifica se il server sia ALIVE (tramite PING)
|
|
/// </summary>
|
|
public bool CheckServerAlive() // Rimosso async, restituisce bool
|
|
{
|
|
// 1. Controllo Veto (Sincrono)
|
|
if (dtVetoPing >= DateTime.Now) return MPOnline;
|
|
if (DemoOut) return true;
|
|
|
|
// 2. Eseguo la parte asincrona forzando l'attesa
|
|
// Usiamo Task.Run per far girare il codice asincrono su un thread del pool
|
|
// evitando deadlock con il thread della UI
|
|
bool isAlive = Task.Run(async () => await ExecuteApiCheckWithRetryAsync(maxRetries: 7))
|
|
.GetAwaiter()
|
|
.GetResult();
|
|
|
|
// 3. Gestione Stato (Sincrono)
|
|
UpdateServerState(isAlive);
|
|
|
|
return isAlive;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifica se il server sia ALIVE (tramite PING)
|
|
/// </summary>
|
|
public async Task<bool> CheckServerAliveAsync()
|
|
{
|
|
// 1. Controllo Veto (Sincrono)
|
|
if (dtVetoPing >= DateTime.Now) return MPOnline;
|
|
if (DemoOut) return true;
|
|
|
|
#if false
|
|
// 2. Eseguo la parte asincrona forzando l'attesa
|
|
// Usiamo Task.Run per far girare il codice asincrono su un thread del pool
|
|
// evitando deadlock con il thread della UI
|
|
bool isAlive = Task.Run(async () => await ExecuteApiCheckWithRetryAsync(maxRetries: 7))
|
|
.GetAwaiter()
|
|
.GetResult();
|
|
#endif
|
|
|
|
bool isAlive = await ExecuteApiCheckWithRetryAsync(maxRetries: 7);
|
|
|
|
|
|
// 3. Gestione Stato (Sincrono)
|
|
UpdateServerState(isAlive);
|
|
|
|
return isAlive;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test ping + api al server in modalità Async
|
|
/// </summary>
|
|
/// <param name="maxRetries"></param>
|
|
/// <returns></returns>
|
|
private async Task<bool> ExecuteApiCheckWithRetryAsync(int maxRetries)
|
|
{
|
|
var rand = new Random();
|
|
for (int i = 0; i <= maxRetries; i++)
|
|
{
|
|
try
|
|
{
|
|
// Se non è il primo tentativo, resetta i client e aspetta
|
|
if (i > 0)
|
|
{
|
|
if (i == 4) resetWebClients(); // Reset specifico a metà tentativi
|
|
await Task.Delay(rand.Next(150, 500));
|
|
}
|
|
|
|
string resp = await callUrl(urlAlive, false);
|
|
if (resp == "OK") return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (i == 0) lgError($"Errore API Check: {ex.Message}");
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update stato server
|
|
/// </summary>
|
|
/// <param name="currentAlive"></param>
|
|
private void UpdateServerState(bool currentAlive)
|
|
{
|
|
if (MPOnline != currentAlive)
|
|
{
|
|
MPOnline = currentAlive;
|
|
parentForm.commSrvActive = currentAlive ? 1 : 0;
|
|
|
|
if (currentAlive)
|
|
{
|
|
lgInfo("SERVER ONLINE");
|
|
dtVetoPing = DateTime.Now.AddMilliseconds(baseUtils.nextPauseSendMSec * 5);
|
|
}
|
|
else
|
|
{
|
|
lgError("SERVER OFFLINE");
|
|
// Veto standard per server offline
|
|
dtVetoPing = DateTime.Now.AddMilliseconds(baseUtils.nextPauseSendMSec * 10);
|
|
utils.dtVetoSend = dtVetoPing;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Se lo stato è invariato (es. era online e resta online), allunghiamo il prossimo controllo
|
|
dtVetoPing = DateTime.Now.AddMilliseconds(baseUtils.nextPauseSendMSec * 20);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update visualizzaizone BIT in ingresso <paramref name="currDispData">Parametri da
|
|
/// aggiornare x display in form</paramref>
|
|
/// </summary>
|
|
public void displayInData(ref newDisplayData currDispData)
|
|
{
|
|
if (currDispData != null)
|
|
{
|
|
// mostro update...
|
|
string newString = string.Format("{0:0000}|{1}", counterSigIN, utils.IntToBinStr(B_output, 8));
|
|
currDispData.newInData = $"{B_output}";
|
|
currDispData.newSignalData = newString;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Mostra cosa ha/avrebbe inviato
|
|
/// </summary>
|
|
/// <param name="newData"></param>
|
|
public void displayOtherData(string newData)
|
|
{
|
|
// mostro update...
|
|
accodaOtherData(newData);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Esecuzione dei task richiesti e pulizia coda richieste eseguite
|
|
/// </summary>
|
|
/// <param name="task2exe">Elenco task da eseguire</param>
|
|
/// <param name="codTav">Codice TAV (per macchine multi pallet) - opzionale</param>
|
|
public virtual Dictionary<string, string> executeTasks(Dictionary<string, string> task2exe, string codTav)
|
|
{
|
|
string logMsg = $"Generic: call executeTasks | {task2exe.Count} task";
|
|
if (!string.IsNullOrEmpty(codTav))
|
|
{
|
|
logMsg += $" | codTav: {codTav}";
|
|
}
|
|
lgInfo(logMsg);
|
|
// Verificare il protocollo: dovrebbe togliere SOLO i task eseguiti...
|
|
Dictionary<string, string> taskDone = new Dictionary<string, string>();
|
|
if (task2exe != null)
|
|
{
|
|
// controllo se memMap != null...
|
|
if (memMap != null)
|
|
{
|
|
bool taskOk = false;
|
|
string taskVal = "";
|
|
string newVal = "";
|
|
// cerco task specifici: se ho startSetup --> imposto bit DBB701.DBB0.4
|
|
foreach (var item in task2exe)
|
|
{
|
|
taskOk = false;
|
|
taskVal = "";
|
|
// converto richiesta in enum...
|
|
taskType tName = taskType.nihil;
|
|
Enum.TryParse(item.Key, out tName);
|
|
string iKey = item.Key;
|
|
// se è DP aggiungo in chiave il valore della TAV richiesta... ad es setComm --> setComm#TAV_1
|
|
if (!string.IsNullOrEmpty(codTav))
|
|
{
|
|
iKey += $"#{codTav}";
|
|
}
|
|
// controllo sulla KEY...
|
|
switch (tName)
|
|
{
|
|
case taskType.setArt:
|
|
case taskType.setComm:
|
|
case taskType.setProg:
|
|
case taskType.setPzComm:
|
|
// recupero dati da memMap...
|
|
if (memMap != null && memMap.mMapWrite != null)
|
|
{
|
|
if (memMap.mMapWrite.ContainsKey(iKey))
|
|
{
|
|
dataConf currMem = memMap.mMapWrite[iKey];
|
|
string addr = currMem.memAddr;
|
|
taskVal = $"SET task: {iKey} --> {item.Value} | mem: {currMem.memAddr} - {currMem.size} byte";
|
|
// salvo il nuovo valore nella memoria... così prox invio lo trasmetterà
|
|
memMap.mMapWrite[iKey].value = item.Value;
|
|
}
|
|
else
|
|
{
|
|
taskVal = $"NO DATA MEM, SET task: {iKey} --> {item.Value}";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
taskVal = $"NO BankConf found, SET task: {iKey} --> {item.Value}";
|
|
}
|
|
// salvo in currProd..
|
|
upsertKey(iKey, item.Value);
|
|
|
|
break;
|
|
|
|
case taskType.endProd:
|
|
// reset contapezzi inizio setup
|
|
if (IOBConfFull.Counters.ResetOnProdEnd)
|
|
{
|
|
lgInfo($"Generic.executeTasks {tName} | resetContapezziPLC");
|
|
taskOk = resetContapezziPLC(codTav);
|
|
}
|
|
break;
|
|
|
|
case taskType.forceResetPzCount:
|
|
// recupero dati da memMap...
|
|
if (memMap != null && memMap.mMapWrite != null)
|
|
{
|
|
lgInfo($"Generic.executeTasks {tName} | resetContapezziPLC | memMap");
|
|
if (memMap.mMapWrite.ContainsKey(iKey))
|
|
{
|
|
dataConf currMem = memMap.mMapWrite[iKey];
|
|
string addr = currMem.memAddr;
|
|
taskVal = $"SET task: {iKey} --> {item.Value} | mem: {currMem.memAddr} - {currMem.size} byte";
|
|
// salvo il nuovo valore nella memoria... così prox invio lo trasmetterà
|
|
memMap.mMapWrite[iKey].value = item.Value;
|
|
taskOk = true;
|
|
}
|
|
else
|
|
{
|
|
taskVal = $"NO DATA MEM, SET task: {iKey} --> {item.Value}";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// reset contapezzi senza memMap
|
|
lgInfo($"Generic.executeTasks {tName} | resetContapezziPLC | NO memMap");
|
|
taskOk = resetContapezziPLC(codTav);
|
|
taskVal = taskOk ? "forceResetPzCount | RESET PZ COUNT OK" : "forceResetPzCount | PZ RESET DISABLED | NO EXEC";
|
|
}
|
|
lgInfo($"Chiamata forceResetPzCount: taskOk: {taskOk} | taskVal: {taskVal}");
|
|
break;
|
|
|
|
case taskType.forceSetPzCount:
|
|
// recupero dati da memMap...
|
|
if (memMap != null && memMap.mMapWrite != null)
|
|
{
|
|
if (memMap.mMapWrite.ContainsKey(iKey))
|
|
{
|
|
dataConf currMem = memMap.mMapWrite[iKey];
|
|
string addr = currMem.memAddr;
|
|
taskVal = $"SET task: {iKey} --> {item.Value} | mem: {currMem.memAddr} - {currMem.size} byte";
|
|
// salvo il nuovo valore nella memoria... così prox invio lo trasmetterà
|
|
memMap.mMapWrite[iKey].value = item.Value;
|
|
}
|
|
else
|
|
{
|
|
taskVal = $"NO DATA MEM, SET task: {iKey} --> {item.Value}";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
taskVal = $"NO BankConf found, SET task: {iKey} --> {item.Value}";
|
|
}
|
|
|
|
lgInfo($"Chiamata forceSetPzCount: taskVal: {taskVal}");
|
|
break;
|
|
|
|
case taskType.setArtNum:
|
|
// in primis faccio una chiamata per tutta la tab SE fosse vuoto il
|
|
// dict di traduzione
|
|
if (DictNumArt == null || DictNumArt.Count == 0)
|
|
{
|
|
getNumArt("");
|
|
}
|
|
// chiamo server x avere decodifica valore INT
|
|
newVal = getNumArt(item.Value);
|
|
// procedo come il resto cercando mappatura in memMap: recupero dati
|
|
// da memMap...
|
|
if (memMap != null && memMap.mMapWrite != null)
|
|
{
|
|
if (memMap.mMapWrite.ContainsKey(iKey))
|
|
{
|
|
dataConf currMem = memMap.mMapWrite[iKey];
|
|
string addr = currMem.memAddr;
|
|
taskVal = $"SET task: {iKey} --> {newVal} | mem: {currMem.memAddr} - {currMem.size} byte";
|
|
// salvo il nuovo valore nella memoria... così prox invio lo trasmetterà
|
|
memMap.mMapWrite[iKey].value = newVal;
|
|
}
|
|
else
|
|
{
|
|
taskVal = $"NO DATA MEM, SET task: {iKey} --> {newVal} ({item.Value})";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
taskVal = $"NO BankConf found, SET task: {iKey} --> {newVal} ({item.Value})";
|
|
}
|
|
|
|
// salvo in currProd..
|
|
upsertKey(iKey, newVal);
|
|
break;
|
|
|
|
case taskType.setCommNum:
|
|
// chiamo server x avere decodifica valore INT
|
|
newVal = getNumComm(item.Value);
|
|
// procedo come il resto cercando mappatura in memMap: recupero dati
|
|
// da memMap...
|
|
if (memMap != null && memMap.mMapWrite != null)
|
|
{
|
|
if (memMap.mMapWrite.ContainsKey(iKey))
|
|
{
|
|
dataConf currMem = memMap.mMapWrite[iKey];
|
|
string addr = currMem.memAddr;
|
|
taskVal = $"SET task: {iKey} --> {newVal} | mem: {currMem.memAddr} - {currMem.size} byte";
|
|
// salvo il nuovo valore nella memoria... così prox invio lo trasmetterà
|
|
memMap.mMapWrite[iKey].value = newVal;
|
|
}
|
|
else
|
|
{
|
|
taskVal = $"NO DATA MEM, SET task: {iKey} --> {newVal} ({item.Value})";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
taskVal = $"NO BankConf found, SET task: {iKey} --> {newVal} ({item.Value})";
|
|
}
|
|
|
|
// salvo in currProd..
|
|
upsertKey(iKey, newVal);
|
|
break;
|
|
|
|
case taskType.startSetup:
|
|
// reset contapezzi inizio setup
|
|
if (IOBConfFull.Counters.ResetOnSetupStart)
|
|
{
|
|
lgDebug($"Generic.executeTasks {tName} | resetContapezziPLC");
|
|
taskOk = resetContapezziPLC(codTav);
|
|
}
|
|
taskVal = taskOk ? "startSetup | RESET: SETUP START" : "startSetup | PZ RESET DISABLED | NO EXEC";
|
|
lgInfo($"Chiamata startSetup: taskOk: {taskOk} | taskVal: {taskVal}");
|
|
break;
|
|
|
|
case taskType.stopSetup:
|
|
// reset contapezzi fine setup SE ESPLICITAMENTE IMPOSTATO
|
|
if (IOBConfFull.Counters.ResetOnSetupStop)
|
|
{
|
|
lgDebug($"Generic.executeTasks {tName} | resetContapezziPLC");
|
|
taskOk = resetContapezziPLC(codTav);
|
|
}
|
|
taskVal = taskOk ? "stopSetup | RESET: SETUP END" : "stopSetup | PZ RESET DISABLED | NO EXEC";
|
|
lgInfo($"Chiamata stopSetup: taskOk: {taskOk} | taskVal: {taskVal}");
|
|
break;
|
|
|
|
case taskType.setParameter:
|
|
// richiedo da URL i parametri WRITE da popolare
|
|
lgInfo("Chiamata setParameter --> processMemWriteRequests");
|
|
taskVal = processMemWriteRequests();
|
|
// se restituiscce "" faccio altra prova...
|
|
if (string.IsNullOrEmpty(taskVal))
|
|
{
|
|
// i parametri me li aspetto come stringa composta paramName|paramvalue
|
|
if (item.Value.Contains("|"))
|
|
{
|
|
string[] paramsJob = item.Value.Split('|');
|
|
taskVal = $"REQUEST SET PARAMETERS: {paramsJob[0]} --> {paramsJob[1]}";
|
|
}
|
|
else
|
|
{
|
|
taskVal = $"WRONG REQUEST FOR SET PARAMETERS: {item.Value} doesnt contain pipe for splitting key/value";
|
|
}
|
|
}
|
|
break;
|
|
|
|
case taskType.syncDbData:
|
|
ProcessDataSync();
|
|
break;
|
|
|
|
case taskType.processOtherInfo:
|
|
bool okProc = ProcessOtherInfo(iKey, item.Value);
|
|
taskVal = okProc ? $"OK ProcessOtherInfoAsync | {iKey} | {item.Value}" : $"ERROR ProcessOtherInfoAsync | {iKey} | {item.Value}";
|
|
#if false
|
|
try
|
|
{
|
|
Task.Run(async () => okProc = await ProcessOtherInfoAsync(iKey, item.Value))
|
|
.GetAwaiter()
|
|
.GetResult();
|
|
taskVal = okProc ? $"OK ProcessOtherInfoAsync | {iKey} | {item.Value}" : $"ERROR ProcessOtherInfoAsync | {iKey} | {item.Value}";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
lgError("ProcessOtherInfoAsync | Crash nel ponte Sync/Async: " + ex.Message);
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
taskVal = $"taskReq: {tName} | key: {iKey} | val: {item.Value} | SKIPPED | NO EXEC";
|
|
lgInfo($"Chiamata senza processing: taskOk: {taskOk} | taskVal: {taskVal}");
|
|
break;
|
|
}
|
|
// aggiungo task!
|
|
taskDone.Add(item.Key, taskVal);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (var item in task2exe)
|
|
{
|
|
// converto richiesta in enum...
|
|
taskType tName = taskType.nihil;
|
|
Enum.TryParse(item.Key, out tName);
|
|
string taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | SKIPPED (no BankConf) | NO EXEC";
|
|
// aggiungo task!
|
|
taskDone.Add(item.Key, taskVal);
|
|
}
|
|
lgError($"Attenzione! memMap è nullo, non posso eseguire task2exe! Tutte le richieste sono state chiuse senza esecuzione");
|
|
}
|
|
}
|
|
|
|
return taskDone;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cerca parametri opzionali in modalità "like" del nome
|
|
/// </summary>
|
|
/// <param name="keyStartSearch"></param>
|
|
/// <returns></returns>
|
|
public Dictionary<string, string> findOptPar(string keyStartSearch = "")
|
|
{
|
|
Dictionary<string, string> answ = new Dictionary<string, string>();
|
|
// controllo SE keySearch !=""
|
|
if (!string.IsNullOrWhiteSpace(keyStartSearch))
|
|
{
|
|
if (IOBConfFull.OptPar.Count > 0)
|
|
{
|
|
// ciclo su tutti e cerco occorrenze che INIZINO...
|
|
foreach (var item in IOBConfFull.OptPar)
|
|
{
|
|
if (item.Key.StartsWith(keyStartSearch))
|
|
{
|
|
answ.Add(item.Key, item.Value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// forza reset ODL
|
|
/// </summary>
|
|
public void forceResetOdl()
|
|
{
|
|
lgInfo("Registrato richiesta forzatura reset ODL");
|
|
pzCountResetted = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua chiamata x split ODL
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public async Task<bool> forceSplitOdl()
|
|
{
|
|
bool fatto = false;
|
|
if (vetoSplit < DateTime.Now)
|
|
{
|
|
lgInfo("Richiesto forceSplitOdl");
|
|
// imposto veto x 1 minuto ad altre chiamate...
|
|
vetoSplit = DateTime.Now.AddMinutes(1);
|
|
// eseguo SOLO SE sono online...
|
|
if (MPOnline && IobOnline)
|
|
{
|
|
string fullUrl = "";
|
|
string rawSplit = "";
|
|
try
|
|
{
|
|
/***************************************************
|
|
* Descrizione procedura (OK X SIMULATORI... da provare x DP come OpcUaSiemensSW)
|
|
*
|
|
* - chiamata su MP/IO
|
|
* - verifica che su DB sia abilitato AUTO ODL
|
|
* - il server inserisce un evento fine prod HW 1 minuto prima e inizio setup HW
|
|
* - viene duplicato e chiuso ODL corrente
|
|
* - viene okReport partire ODL nuovo ADESSO
|
|
* - num pezzi come ODL precedente (o da media 3 ODL precedenti)
|
|
* - conferme pezzi & co... gestione NULL (NON SERVONO si tratta di impianti SENZA gestione vera ODL)
|
|
* - reset contapezzi PLC locale...
|
|
*
|
|
*
|
|
*
|
|
* DA VALUTARE (x macchine tipo linea con + impianti... es valvital) SE
|
|
* - creare una gestione ALTERNATIVA sul server che preveda impianto LEADER e impianti follower (RIGIDAMENTE CONFIGURATI)
|
|
* - ogni volta che si fa setup LEADER --> si ripete su impianti FOLLOWER (eventi!!!)
|
|
* - viene okReport reset contapezzi sui follower (+ altre operazioni opzionali, ES imposstazione nome commessa, quantità, articolo...)
|
|
* - viene okReport reset + nuovo ODL (con stessi articoli e quantità) su follower, SENZA avere gestione x ODL di un codice esterno (quindi registra TUTTO ma NON RITORNERA' dati non avendo link verso esterno)
|
|
* - serve NUOVA TABELLA delle macchine LEADER | FOLLOWER (1:n) e gestione da MP/IO
|
|
*
|
|
* ***************************************************/
|
|
|
|
// se normale splitto!
|
|
if (!isMulti)
|
|
{
|
|
// invio chiamata URL x reset ODL su macchina
|
|
rawSplit = await callUrl(urlForceSplit, false);
|
|
fatto = (rawSplit != "KO") ? true : false;
|
|
}
|
|
// se multi gestisco il bit delle tavole...
|
|
else
|
|
{
|
|
foreach (string item in IOBConfFull.Device.MultiIobList)
|
|
{
|
|
// invio chiamata URL x reset ODL su macchina, ATTENZIONE scriviamo
|
|
// | al posto di "#" che in URL sarebbe filtrato...
|
|
fullUrl = $"{urlForceSplit}&multi={item}";
|
|
rawSplit = await callUrl(fullUrl, false);
|
|
lgDebug($"Esecuzione forceSplit | URL: {fullUrl} | esito: {rawSplit}");
|
|
}
|
|
fatto = (rawSplit == "OK") ? true : false;
|
|
}
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError($"Eccezione in forceSplitOdl{Environment.NewLine}{exc}");
|
|
}
|
|
// se okReport --> resetto contapezzi!!!
|
|
if (fatto)
|
|
{
|
|
contapezziPLC = 0;
|
|
contapezziIOB = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgError("Richiesto forceSplitOdl ma MP/IOB offline --> NON eseguito");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgError("Richiesto forceSplitOdl ma veto attivo --> NON eseguito");
|
|
}
|
|
return fatto;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processing degli allarmi (se presenti e gestiti)
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public virtual Dictionary<string, string> getAlarmData()
|
|
{
|
|
Dictionary<string, string> outVal = new Dictionary<string, string>();
|
|
// ora aggiungo (se ci fossero) gli allarmi...
|
|
if (alarmMaps != null && alarmMaps.Count > 0)
|
|
{
|
|
if (hasAlarms())
|
|
{
|
|
string bankVal = "";
|
|
foreach (var item in alarmMaps)
|
|
{
|
|
bankVal = "";
|
|
// verifico ed eseguo secondo il tipo di allarme gestito...
|
|
if (alarmType == AlarmBlockType.Bitmap)
|
|
{
|
|
var currState = currAlarmsState(item);
|
|
// calcolo vettore stringa degli allarmi...
|
|
bankVal = getAlarmState(item, currState);
|
|
}
|
|
else if (alarmType == AlarmBlockType.ActiveList)
|
|
{
|
|
// cerco se sia superiore al livello minimo da conf
|
|
if (item.blockLevel >= alarmLevelMin)
|
|
{
|
|
int numActive = getAlarmStatus(item);
|
|
// calcolo vettore stringa degli allarmi...
|
|
bankVal = getAlarmState(item, numActive);
|
|
}
|
|
}
|
|
// sistemo se OK e/o invio
|
|
bankVal = string.IsNullOrEmpty(bankVal) ? "OK" : bankVal;
|
|
saveAlarmString(ref outVal, bankVal, item.description);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgTrace("No alarm found in alarmMaps");
|
|
}
|
|
|
|
if (periodicLog || outVal.Count > 0)
|
|
{
|
|
lgDebug($"Esito getAlarmData: {outVal.Count} allarmi attivi/validi in outVal");
|
|
}
|
|
return outVal;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua conversione da valore bitmap ai valori configurati x allarme
|
|
/// </summary>
|
|
/// <param name="memConf">Configurazione banco allarmi</param>
|
|
/// <param name="bState">BitState allarmi</param>
|
|
/// <returns></returns>
|
|
public string getAlarmState(BaseAlarmConf memConf, byte[] bState)
|
|
{
|
|
string valTransl = "";
|
|
List<string> descAttivi = new List<string>();
|
|
BitArray bitAttivi = new BitArray(bState);
|
|
bool[] bitStati = new bool[bitAttivi.Count];
|
|
bitAttivi.CopyTo(bitStati, 0);
|
|
// cerco bit attivi
|
|
int idx = 0;
|
|
foreach (var item in bitStati)
|
|
{
|
|
if (item && memConf.messages.Count > idx)
|
|
{
|
|
string currState = memConf.messages[idx];
|
|
descAttivi.Add(currState);
|
|
}
|
|
idx++;
|
|
}
|
|
// combino string
|
|
valTransl = string.Join(",", descAttivi);
|
|
return valTransl;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua conversione tra gli allarmi attivi secondo configurazione banco allarmi
|
|
/// </summary>
|
|
/// <param name="memConf">Configurazione banco allarmi</param>
|
|
/// <param name="numAlarms">num allarmi attivi</param>
|
|
/// <returns></returns>
|
|
public virtual string getAlarmState(BaseAlarmConf memConf, int numAlarms)
|
|
{
|
|
string valTransl = "";
|
|
List<string> descAttivi = new List<string>();
|
|
|
|
// FAKE!!!: dovrebbe cercare in aree memoria x ogni valore configurato, vedere
|
|
// implementazione specifica es OpcUa
|
|
|
|
// combino string
|
|
valTransl = string.Join(",", descAttivi);
|
|
return valTransl;
|
|
}
|
|
|
|
/// <summary>
|
|
/// effettua recupero dati ed invio valori modificati...
|
|
/// </summary>
|
|
/// <param name="ciclo"></param>
|
|
public async Task getAndSendAsync(gatherCycle ciclo)
|
|
{
|
|
// init obj display
|
|
newDisplayData currDispData = new newDisplayData();
|
|
// IN OGNI CASO a prima di tutto EFFETTUO GESTIONE INVII dati da code!!!
|
|
try
|
|
{
|
|
await TrySendValuesAsync();
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError(exc, "Errore in gestione svuotamento/invio preliminare code memoria");
|
|
currDispData.semOut = Semaforo.SR;
|
|
}
|
|
// controllo connessione/connettività
|
|
if (connectionOk)
|
|
{
|
|
// controllo non sia già in esecuzione...
|
|
if (!adpCommAct)
|
|
{
|
|
// provo ad avviare
|
|
try
|
|
{
|
|
// imposto flag adapter running..
|
|
adpCommAct = true;
|
|
adpStartRun = DateTime.Now;
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
string errore = $"Adapter NOT STARTED!!!{Environment.NewLine}{exc}";
|
|
adpCommAct = false;
|
|
adpStartRun = DateTime.Now;
|
|
currDispData.newLiveLogData = errore;
|
|
}
|
|
if (adpCommAct)
|
|
{
|
|
// try / catch generale altrimenti segno che è disconnesso...
|
|
try
|
|
{
|
|
bool showDebugData = false;
|
|
if (ciclo == gatherCycle.VHF)
|
|
{
|
|
processVHF();
|
|
}
|
|
// processing dati memoria (lettura, filtraggio, enqueque)
|
|
else if (ciclo == gatherCycle.HF)
|
|
{
|
|
processWhatchDog();
|
|
processAllMemory();
|
|
}
|
|
else if (ciclo == gatherCycle.MF)
|
|
{
|
|
processMode();
|
|
await processServerRequests();
|
|
processCustomTaskMF();
|
|
processOverride();
|
|
processContapezzi();
|
|
processCncAlarms();
|
|
processDynData();
|
|
processMem2Write();
|
|
processAllMemory();
|
|
}
|
|
else if (ciclo == gatherCycle.LF)
|
|
{
|
|
processCustomTaskLF();
|
|
processOtherCounters();
|
|
processProgram();
|
|
// verifico se devo gestire cambio ODL in modo automatico
|
|
await ProcessAutoOdlAsync();
|
|
// verifico se devo gestire auto generazione dossier quotidiana
|
|
ProcessAutoDossier();
|
|
// effettua gestione import file se configurato...
|
|
await ProcessFileImportAsync();
|
|
// effettua process ritorno ricette
|
|
await ProcessRecipeFileRetAsync();
|
|
}
|
|
else if (ciclo == gatherCycle.VLF)
|
|
{
|
|
if (utils.CRB("enableContapezzi"))
|
|
{
|
|
// rilettura contapezzi da server...
|
|
lgTrace("Ciclo MsVLF: pzCntReload(true)");
|
|
if (!isMulti)
|
|
{
|
|
pzCntReload(true);
|
|
}
|
|
// refresh associazione Macchina - IOB
|
|
await SendM2IobAsync();
|
|
// invio altri dati accessori...
|
|
await SendMachineConfAsync();
|
|
}
|
|
// recupero dati SETUP (sysinfo) e li invio/mostro se variati...
|
|
processSysInfo();
|
|
// checkLogDir x shrink!
|
|
checkShrinkDir();
|
|
// eventuale log!
|
|
if (utils.CRB("recTime"))
|
|
{
|
|
try
|
|
{
|
|
logTimeResults();
|
|
}
|
|
catch
|
|
{ }
|
|
}
|
|
processRecipeSyncArch();
|
|
if (enableSlowData)
|
|
{
|
|
processSlowDataRead();
|
|
}
|
|
}
|
|
// mostra eventuali altri dati di processo...
|
|
reportDataProc();
|
|
if (showDebugData)
|
|
{
|
|
// verifica se debba salvare e mostrare dati
|
|
checkSavePersDataLayer();
|
|
}
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
// segnalo eccezione e indico disconnesso...
|
|
lgError(exc, string.Format("Errore in gestione ciclo principale ADP, fermo adapter{0}{1}", Environment.NewLine, exc));
|
|
parentForm.fermaAdapter(true, false, true);
|
|
}
|
|
// tolgo flag running
|
|
adpCommAct = false;
|
|
}
|
|
else
|
|
{
|
|
if (periodicLog)
|
|
{
|
|
lgDebug("ADP not running...");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// log ADP running
|
|
lgError("Non eseguo chiamata: ADP ancora in running");
|
|
// se è bloccato da oltre maxSec lo sblocco...
|
|
if (DateTime.Now.Subtract(adpStartRun).TotalSeconds > utils.CRI("maxAdapterLockSec"))
|
|
{
|
|
// tolgo flag running
|
|
adpCommAct = false;
|
|
adpStartRun = DateTime.Now;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// anche se NON connesso alcuni task di bassa freq li eseguo...
|
|
if (ciclo == gatherCycle.LF)
|
|
{
|
|
// verifico se devo gestire cambio ODL in modo automatico
|
|
await ProcessAutoOdlAsync();
|
|
// verifico se devo gestire auto generazione dossier quotidiana
|
|
ProcessAutoDossier();
|
|
// effettua gestione import file se configurato...
|
|
await ProcessFileImportAsync();
|
|
// effettua process ritorno ricette
|
|
await ProcessRecipeFileRetAsync();
|
|
}
|
|
else if (ciclo == gatherCycle.VLF)
|
|
{
|
|
processRecipeSyncArch();
|
|
}
|
|
|
|
// provo a riconnettere SE abilitato tryRestart...
|
|
if (adpTryRestart && !connectionOk)
|
|
{
|
|
// controllo se sia scaduto periodi di veto al tryConnect...
|
|
int waitRecMSec = utils.CRI("waitRecMSec") * 2;
|
|
// cerco se ci sia un valore in ovverride x il singolo IOB...
|
|
if (IOBConfFull.General.WaitRecMsec > 0)
|
|
{
|
|
waitRecMSec = IOBConfFull.General.WaitRecMsec;
|
|
}
|
|
DateTime dtVeto = lastConnectTry.AddMilliseconds(waitRecMSec);
|
|
if (DateTime.Now > dtVeto)
|
|
{
|
|
lgInfo($"Veto Time Elapsed | lastConnectTry: {lastConnectTry} | waitRecMSec {waitRecMSec} ms) | NOW tryConnect");
|
|
lastConnectTry = DateTime.Now;
|
|
tryConnect();
|
|
}
|
|
}
|
|
currDispData.semIn = Semaforo.SR;
|
|
processDisconnectedTask();
|
|
processMemoryDiscon();
|
|
}
|
|
raiseRefresh(currDispData);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua conversione da valore bitmap ai valori configurati
|
|
/// </summary>
|
|
/// <param name="memConf"></param>
|
|
/// <param name="bState"></param>
|
|
/// <returns></returns>
|
|
public string getBitmapState(dataConfTSVC memConf, byte[] bState)
|
|
{
|
|
string valTransl = "";
|
|
List<string> descAttivi = new List<string>();
|
|
BitArray bitAttivi = new BitArray(bState);
|
|
bool[] bitStati = new bool[bitAttivi.Count];
|
|
bitAttivi.CopyTo(bitStati, 0);
|
|
// cerco bit attivi
|
|
int idx = 0;
|
|
foreach (var item in bitStati)
|
|
{
|
|
if (item && memConf.decodeMap.Count > idx)
|
|
{
|
|
string currState = memConf.decodeMap[idx];
|
|
descAttivi.Add(currState);
|
|
}
|
|
idx++;
|
|
}
|
|
// combino string
|
|
valTransl = string.Join(",", descAttivi);
|
|
return valTransl;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recupera eventuali allarmi CNC...
|
|
/// </summary>
|
|
public virtual Dictionary<string, string> getCncAlarms()
|
|
{
|
|
Dictionary<string, string> outVal = new Dictionary<string, string>();
|
|
return outVal;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restituisce info DINAMICHE
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public virtual Dictionary<string, string> getDynData()
|
|
{
|
|
Dictionary<string, string> outVal = new Dictionary<string, string>();
|
|
return outVal;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cerca se esiste il parametro opzionale nei KVP (JSON) e lo restituisce
|
|
/// </summary>
|
|
/// <param name="key"></param>
|
|
/// <returns></returns>
|
|
public string getOptJsonKVP(string key)
|
|
{
|
|
string answ = "";
|
|
// controllo SE HO il parametro
|
|
if (memMap != null && memMap.OptKVP != null && memMap.OptKVP.Count > 0)
|
|
{
|
|
if (memMap.OptKVP.ContainsKey(key))
|
|
{
|
|
answ = memMap.OptKVP[key];
|
|
}
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cerca se esiste il parametro opzionale e lo restituisce
|
|
/// </summary>
|
|
/// <param name="key"></param>
|
|
/// <returns></returns>
|
|
public string getOptPar(string key)
|
|
{
|
|
return IOBConfFull.OptParGet(key);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cerca se esiste un link tra aree di memoria in write e lo restituisce
|
|
/// </summary>
|
|
/// <param name="key"></param>
|
|
/// <returns></returns>
|
|
public string getOptWriteLink(string key)
|
|
{
|
|
string answ = "";
|
|
// controllo SE HO il parametro
|
|
if (memMap != null && memMap.mMapWriteLink != null && memMap.mMapWriteLink.Count > 0)
|
|
{
|
|
if (memMap.mMapWriteLink.ContainsKey(key))
|
|
{
|
|
answ = memMap.mMapWriteLink[key];
|
|
}
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restituisce info OVERRIDES
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public virtual Dictionary<string, string> getOverrides()
|
|
{
|
|
Dictionary<string, string> outVal = new Dictionary<string, string>();
|
|
return outVal;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restituisce programma in esecuzione
|
|
/// </summary>
|
|
public virtual string getPrgName()
|
|
{
|
|
return "";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restituisce info sistema
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public virtual Dictionary<string, string> getSysInfo()
|
|
{
|
|
Dictionary<string, string> outVal = new Dictionary<string, string>();
|
|
return outVal;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recupera la VC x TS, svuotando lista e resettando periodo partenza
|
|
/// </summary>
|
|
/// <param name="VCName">Nome della VC</param>
|
|
/// <param name="doReset">Reimposta e resetta array VC</param>
|
|
/// <returns></returns>
|
|
public double getVal_TSVC(string VCName, bool doReset)
|
|
{
|
|
double answ = -999999;
|
|
// cerco VC...
|
|
if (TSVC_Data.ContainsKey(VCName))
|
|
{
|
|
try
|
|
{
|
|
switch (TSVC_Data[VCName].Funzione)
|
|
{
|
|
case VC_func.POINT:
|
|
// prendo PRIMO
|
|
answ = TSVC_Data[VCName].dataArray.FirstOrDefault();
|
|
break;
|
|
|
|
case VC_func.AVG:
|
|
answ = TSVC_Data[VCName].dataArray.Average();
|
|
break;
|
|
|
|
case VC_func.MEDIAN:
|
|
answ = TSVC_Data[VCName].dataArray.Median();
|
|
break;
|
|
|
|
case VC_func.MIN:
|
|
answ = TSVC_Data[VCName].dataArray.Min();
|
|
break;
|
|
|
|
case VC_func.MAX:
|
|
default:
|
|
answ = TSVC_Data[VCName].dataArray.Max();
|
|
break;
|
|
}
|
|
}
|
|
catch
|
|
{ }
|
|
// ora resetto... SE richiesto...
|
|
if (doReset)
|
|
{
|
|
TSVC_Data[VCName].dataArray = new List<double>();
|
|
TSVC_Data[VCName].DTStart = DateTime.Now;
|
|
}
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recupera la VC x TS, svuotando lista e resettando periodo partenza
|
|
/// </summary>
|
|
/// <param name="VCName">Nome della VC</param>
|
|
/// <param name="doReset">Reimposta e resetta array VC</param>
|
|
/// <returns></returns>
|
|
public int getVal_TSVC_int(string VCName, bool doReset)
|
|
{
|
|
int answ = 0;
|
|
// cerco VC...
|
|
if (TSVC_Data.ContainsKey(VCName))
|
|
{
|
|
// !!!FARE!!! vero calcolo... x ora FIX a MAX...
|
|
foreach (var item in TSVC_Data[VCName].dataArray)
|
|
{
|
|
answ = (int)item > answ ? (int)item : answ;
|
|
}
|
|
// ora resetto... SE richiesto..
|
|
if (doReset)
|
|
{
|
|
TSVC_Data[VCName].dataArray = new List<double>();
|
|
TSVC_Data[VCName].DTStart = DateTime.Now;
|
|
}
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restituisce un payload in formato json della lista di valori ricevuta
|
|
/// </summary>
|
|
/// <param name="tipoUrl">Tipo di URL (eventi / FLog)</param>
|
|
/// <param name="elencoValori">elenco di valori da coda string salvata</param>
|
|
/// <returns></returns>
|
|
public string jsonPayload(urlType tipoUrl, List<string> elencoValori)
|
|
{
|
|
string answ = "";
|
|
if (elencoValori != null)
|
|
{
|
|
string[] valori;
|
|
int counter = 0;
|
|
DateTime dtEve = DateTime.Now;
|
|
switch (tipoUrl)
|
|
{
|
|
case urlType.FLog:
|
|
flogData currFlData = new flogData();
|
|
flogJsonPayload fullFlObj = new flogJsonPayload();
|
|
fullFlObj.fluxData = new List<flogData>();
|
|
// inizio processando ogni valore
|
|
foreach (var item in elencoValori)
|
|
{
|
|
if (item != null)
|
|
{
|
|
valori = qDecodeIN(item);
|
|
CultureInfo provider = CultureInfo.InvariantCulture;
|
|
DateTime.TryParseExact(valori[0], "yyyyMMddHHmmssfff", provider, DateTimeStyles.AssumeLocal, out dtEve);
|
|
int.TryParse(valori[3], out counter);
|
|
currFlData = new flogData()
|
|
{
|
|
flux = valori[1],
|
|
valore = valori[2],
|
|
dtEve = dtEve,
|
|
dtCurr = DateTime.Now,
|
|
cnt = counter
|
|
};
|
|
fullFlObj.fluxData.Add(currFlData);
|
|
}
|
|
}
|
|
// conversione finale
|
|
try
|
|
{
|
|
answ = JsonConvert.SerializeObject(fullFlObj);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError($"FLog Errore in costruzione jsonPayload:{Environment.NewLine}{exc}");
|
|
}
|
|
break;
|
|
|
|
case urlType.SignIN:
|
|
evData currSigData = new evData();
|
|
evJsonPayload fullEvObj = new evJsonPayload();
|
|
fullEvObj.eventList = new List<evData>();
|
|
// inizio processando ogni valore
|
|
foreach (var item in elencoValori)
|
|
{
|
|
valori = qDecodeIN(item);
|
|
CultureInfo provider = CultureInfo.InvariantCulture;
|
|
DateTime.TryParseExact(valori[0], "yyyyMMddHHmmssfff", provider, DateTimeStyles.AssumeLocal, out dtEve);
|
|
int.TryParse(valori[2], out counter);
|
|
currSigData = new evData()
|
|
{
|
|
valore = valori[1],
|
|
dtEve = dtEve,
|
|
dtCurr = DateTime.Now,
|
|
cnt = counter
|
|
};
|
|
fullEvObj.eventList.Add(currSigData);
|
|
}
|
|
// conversione finale
|
|
try
|
|
{
|
|
answ = JsonConvert.SerializeObject(fullEvObj);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError($"SignIN Errore in costruzione jsonPayload:{Environment.NewLine}{exc}");
|
|
}
|
|
break;
|
|
|
|
case urlType.RawTransf:
|
|
BaseRawTransf currRTData = new BaseRawTransf();
|
|
#if false
|
|
rawTransfJsonPayload fullRTObj = new rawTransfJsonPayload();
|
|
fullRTObj.rawTransfData = new List<BaseRawTransf>();
|
|
// inizio processando ogni valore
|
|
foreach (var item in elencoValori)
|
|
{
|
|
try
|
|
{
|
|
currRTData = JsonConvert.DeserializeObject<BaseRawTransf>(item);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError($"Eccezione in deserializzazione BaseRawTransf:{Environment.NewLine}{exc}");
|
|
}
|
|
fullRTObj.rawTransfData.Add(currRTData);
|
|
}
|
|
// conversione finale
|
|
try
|
|
{
|
|
answ = JsonConvert.SerializeObject(fullRTObj);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError($"RawTransf Errore in costruzione jsonPayload:{Environment.NewLine}{exc}");
|
|
}
|
|
#endif
|
|
|
|
// provo una serializzazione "brutale", ovvero aggiungo alla stringa il
|
|
// valore di testa...
|
|
string rawResult = "";
|
|
foreach (var item in elencoValori)
|
|
{
|
|
rawResult += $"{item},";
|
|
}
|
|
if (rawResult.Length > 0)
|
|
{
|
|
rawResult = rawResult.Substring(0, rawResult.Length - 1);
|
|
}
|
|
answ = $"[{rawResult}]";
|
|
|
|
//hasVeto = "{" + $"\"rawTransfData\":[{rawResult}]" + "}";
|
|
|
|
break;
|
|
|
|
case urlType.ULog:
|
|
int numVal = 0;
|
|
int matrOp = 0;
|
|
ulogData currUlData = new ulogData();
|
|
ulogJsonPayload fullUlObj = new ulogJsonPayload();
|
|
fullUlObj.fluxData = new List<ulogData>();
|
|
// inizio processando ogni valore
|
|
foreach (var item in elencoValori)
|
|
{
|
|
valori = qDecodeIN(item);
|
|
CultureInfo provider = CultureInfo.InvariantCulture;
|
|
DateTime.TryParseExact(valori[0], "yyyyMMddHHmmssfff", provider, DateTimeStyles.AssumeLocal, out dtEve);
|
|
int.TryParse(valori[3], out matrOp);
|
|
int.TryParse(valori[5], out numVal);
|
|
int.TryParse(valori[6], out counter);
|
|
currUlData = new ulogData()
|
|
{
|
|
flux = valori[1],
|
|
valore = valori[2],
|
|
dtEve = dtEve,
|
|
dtCurr = DateTime.Now,
|
|
cnt = counter,
|
|
matrOpr = matrOp,
|
|
label = valori[4],
|
|
valNum = numVal
|
|
};
|
|
fullUlObj.fluxData.Add(currUlData);
|
|
}
|
|
// conversione finale
|
|
try
|
|
{
|
|
answ = JsonConvert.SerializeObject(fullUlObj);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError($"ULog Errore in costruzione jsonPayload:{Environment.NewLine}{exc}");
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua un trim della stringa al numero max di linee da mostrare a video
|
|
/// </summary>
|
|
/// <param name="newString"></param>
|
|
/// <returns></returns>
|
|
public string limitLine2show(string newString)
|
|
{
|
|
// se num righe superiore a limite trimmo...
|
|
if (newString.Split('\n').Length > parentForm.nLine2show)
|
|
{
|
|
//int idx = newString.LastIndexOf('\r');
|
|
int idx = newString.LastIndexOf(Environment.NewLine);
|
|
newString = newString.Substring(0, idx);
|
|
}
|
|
return newString;
|
|
}
|
|
|
|
/// <summary>
|
|
/// riporta il log di tutti i dati di results temporali registrati
|
|
/// </summary>
|
|
public void logTimeResults()
|
|
{
|
|
if (TimingData.results.Count > 0)
|
|
{
|
|
lgInfo("{0}--------------- START TIMING DATA ---------------", Environment.NewLine);
|
|
int globNumCall = 0;
|
|
TimeSpan globAvgMsec = new TimeSpan(0);
|
|
foreach (TimeRec item in TimingData.results)
|
|
{
|
|
// loggo SOLO se del mio IOB corrente...
|
|
if (item.classCall == IOBConfFull.General.FilenameIOB)
|
|
{
|
|
lgInfo("{4}|Chiamate {0}: effettuate {1}, tempo medio {2:N2} msec | impegno canale {3:P3}", item.codCall, item.numCall, item.avgMsec, item.totMsec.TotalSeconds / DateTime.Now.Subtract(dtAvvioAdp).TotalSeconds, IOBConfFull.General.CodIOB);
|
|
globNumCall += item.numCall;
|
|
globAvgMsec += item.totMsec;
|
|
}
|
|
}
|
|
// riporto conteggio medio al secondo...
|
|
lgInfo("{4}|Chiamate GLOBALI: {0}, periodo: {1:N2} minuti.cent, tempo medio {2:N2} msec | impegno canale {3:P3}", globNumCall, DateTime.Now.Subtract(dtAvvioAdp).TotalMinutes, globAvgMsec.TotalMilliseconds / globNumCall, globAvgMsec.TotalSeconds / DateTime.Now.Subtract(dtAvvioAdp).TotalSeconds, IOBConfFull.General.CodIOB);
|
|
lgInfo("{0}--------------- STOP TIMING DATA ---------------{0}", Environment.NewLine);
|
|
// mostro in form statistiche globali!
|
|
parentForm.updateComStats(string.Format("Periodo: {0:N2}min | {1} x {2:N2}ms | canale {3:P3}", DateTime.Now.Subtract(dtAvvioAdp).TotalMinutes, globNumCall, globAvgMsec.TotalMilliseconds / globNumCall, globAvgMsec.TotalSeconds / DateTime.Now.Subtract(dtAvvioAdp).TotalSeconds));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processa un monitoredItem, e ritorna boolean SE richiede invio (cambio o scadenza)
|
|
/// </summary>
|
|
/// <param name="newVal"></param>
|
|
/// <param name="item"></param>
|
|
/// <returns></returns>
|
|
public bool monItem2Send(string newVal, DynDataItem item)
|
|
{
|
|
bool answ = false;
|
|
if (item != null)
|
|
{
|
|
// controllo in base al tipo di function...
|
|
switch (item.func)
|
|
{
|
|
case "SAMPLE":
|
|
// controllo se scaduto sample period...
|
|
if (item.DTScad < DateTime.Now)
|
|
{
|
|
answ = true;
|
|
}
|
|
break;
|
|
|
|
case "CHANGE":
|
|
default:
|
|
// controllo se scaduto o se variato...
|
|
if (newVal != item.actVal || item.DTScad < DateTime.Now)
|
|
{
|
|
// aggiorno scadenza e che vada inviato
|
|
answ = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifica e processing x gestione Dossier quotidiani automatica
|
|
/// </summary>
|
|
public void ProcessAutoDossier()
|
|
{
|
|
bool fatto = false;
|
|
string callResp = "";
|
|
if (IOBConfFull.FluxLog.AutoSnapshotDossier)
|
|
{
|
|
DateTime adesso = DateTime.Now;
|
|
if (adesso > dtVetoAutoDossier)
|
|
{
|
|
dtVetoAutoDossier = adesso.AddMinutes(30);
|
|
lgTrace("Richiesta ProcessAutoDossier");
|
|
|
|
lgTrace("AutoSnapshotDossier abilitato");
|
|
// chiamo stored x creare Snapshot Dossier giornalieri fino alla data...
|
|
callResp = utils.callUrl(urlFixDailyDossier);
|
|
fatto = callResp == "OK";
|
|
lgDebug($"Esecuzione ProcessAutoDossier completata --> esito: {callResp}");
|
|
}
|
|
else
|
|
{
|
|
lgTrace("AutoSnapshotDossier DISABILITATO");
|
|
}
|
|
}
|
|
// loggo se enabled
|
|
if (fatto)
|
|
{
|
|
lgTrace($"Effettuato ProcessAutoDossier, esito positivo | {DateTime.Now:HH.mm.ss}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wrapper AutoOdl in modalità Sync
|
|
/// </summary>
|
|
public void ProcessAutoOdl()
|
|
{
|
|
try
|
|
{
|
|
Task.Run(async () => await ProcessAutoOdlAsync())
|
|
.GetAwaiter()
|
|
.GetResult();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
lgError("ProcessAutoOdl | Crash nel ponte Sync/Async: " + ex.Message);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Verifica e processing x gestione ODL automatica
|
|
/// </summary>
|
|
public async Task ProcessAutoOdlAsync()
|
|
{
|
|
bool fatto = false;
|
|
if (IOBConfFull.Odl.AutoChangeOdl)
|
|
{
|
|
lgTrace("ProcessAutoOdlAsync | AutoChangeOdl abilitato");
|
|
// imposto il veto lettura contapezzi a 1 minuto x iniziare...
|
|
dtVetoReadPzCount = DateTime.Now.AddMinutes(delayMinReadPzCount);
|
|
string fullUrl = "";
|
|
DateTime adesso = DateTime.Now;
|
|
DateTime inizioOdl = adesso;
|
|
string rawDataInizio = "";
|
|
if (VetoProcessAutoOdl > adesso)
|
|
{
|
|
lgTrace($"Veto per check autoOdl attivo fino a {VetoProcessAutoOdl} | salto verifica");
|
|
}
|
|
else
|
|
{
|
|
bool callChangeODL = false;
|
|
DateTime dtStart = await currOdlStart();
|
|
switch (IOBConfFull.Odl.ChangeOdlMode)
|
|
{
|
|
case "PZCOUNT_RESET":
|
|
/* verifico se sia "armato" il reset cambio ODL, DEVO essere :
|
|
* - NON in produzione
|
|
* - contapezzi ACT < contapezzi LAST
|
|
* */
|
|
lgTrace("ProcessAutoOdlAsync: caso PZCOUNT_RESET");
|
|
if ((!isRunning || forceResetInRun) && pzCountResetted)
|
|
{
|
|
callChangeODL = true;
|
|
lgInfo("Attivato cambio ODL da PZCOUNT_RESET");
|
|
}
|
|
else
|
|
{
|
|
lgInfo($"isRunning: {isRunning} | pzCountResetted: {pzCountResetted} | forceResetInRun: {forceResetInRun} | contapezziIOB: {contapezziIOB} | contapezziPLC: {contapezziPLC}");
|
|
}
|
|
break;
|
|
|
|
case "DAILY":
|
|
// verifico inizio ODL, se è data di oggi NON eseguo...
|
|
if (dtStart.Date < adesso.Date)
|
|
{
|
|
// chiamo stored x creare ODL giornalieri alla data...
|
|
string autoOdlRes = utils.callUrl(urlFixDailyOdl);
|
|
fatto = autoOdlRes == "OK";
|
|
// imposto x prox controllo veto a 10 min
|
|
VetoProcessAutoOdl = adesso.AddMinutes(10);
|
|
}
|
|
else
|
|
{
|
|
// imposto x prox controllo veto a 30 min
|
|
VetoProcessAutoOdl = adesso.AddMinutes(30);
|
|
}
|
|
break;
|
|
|
|
case "DAILY_CONF_PZ":
|
|
// verifico inizio ODL, se è data di oggi NON eseguo...
|
|
if (dtStart.Date < adesso.Date)
|
|
{
|
|
// imposto x prox controllo veto a 10 min
|
|
VetoProcessAutoOdl = adesso.AddMinutes(10);
|
|
string autoOdlRes = "";
|
|
if (!isMulti)
|
|
{
|
|
// chiamo stored x creare ODL giornalieri alla data + conferma pezzi...
|
|
autoOdlRes = utils.callUrl(urlFixDailyOdlConfPzCount);
|
|
}
|
|
else
|
|
{
|
|
// prendo il + vecchio...
|
|
foreach (var item in IOBConfFull.Device.MultiIobList)
|
|
{
|
|
fullUrl = $@"{urlCommand("fixDailyOdlConfPzCount")}{item}";
|
|
autoOdlRes = await callUrl(fullUrl, false);
|
|
}
|
|
}
|
|
fatto = autoOdlRes == "OK";
|
|
}
|
|
else
|
|
{
|
|
// imposto x prox controllo veto a 30 min
|
|
VetoProcessAutoOdl = adesso.AddMinutes(30);
|
|
}
|
|
break;
|
|
|
|
case "SIMUL":
|
|
case "TIME":
|
|
// imposto x prox controllo veto a 1 min
|
|
VetoProcessAutoOdl = adesso.AddMinutes(1);
|
|
// controllo parametri validi
|
|
if (IOBConfFull.Odl.OdlDurationHours > 0 && IOBConfFull.Odl.IdleStateMin >= 0)
|
|
{
|
|
// leggo da server inizio ODL... se non multi 1 solo...
|
|
inizioOdl = DateTime.Now;
|
|
rawDataInizio = "";
|
|
if (!isMulti)
|
|
{
|
|
rawDataInizio = await callUrl(urlInizioOdlIob, false);
|
|
DateTime.TryParse(rawDataInizio, out inizioOdl);
|
|
}
|
|
else
|
|
{
|
|
DateTime tmpData = DateTime.Now;
|
|
// prendo il + vecchio...
|
|
foreach (var item in IOBConfFull.Device.MultiIobList)
|
|
{
|
|
fullUrl = $"{urlInizioOdlIob}|{item}";
|
|
rawDataInizio = await callUrl(fullUrl, false);
|
|
DateTime.TryParse(rawDataInizio, out tmpData);
|
|
inizioOdl = (tmpData < inizioOdl) ? tmpData : inizioOdl;
|
|
}
|
|
}
|
|
// verifico se sia scaduto...
|
|
if (inizioOdl.AddHours(IOBConfFull.Odl.OdlDurationHours) < adesso)
|
|
{
|
|
string rawIdle = "";
|
|
int idlePeriod = 0;
|
|
if (!isMulti)
|
|
{
|
|
// controllo SE sono fermo (spento o in manuale) per il
|
|
// periodo minimo richiesto...
|
|
rawIdle = await callUrl(urlIdleTime, false);
|
|
int.TryParse(rawIdle, out idlePeriod);
|
|
}
|
|
else
|
|
{
|
|
int tmpIdle = 0;
|
|
// prendo il + grande...
|
|
foreach (var item in IOBConfFull.Device.MultiIobList)
|
|
{
|
|
fullUrl = $"{urlIdleTime}|{item}";
|
|
rawIdle = await callUrl(fullUrl, false);
|
|
int.TryParse(rawIdle, out tmpIdle);
|
|
idlePeriod = tmpIdle > idlePeriod ? tmpIdle : idlePeriod;
|
|
}
|
|
}
|
|
if (idlePeriod >= IOBConfFull.Odl.IdleStateMin)
|
|
{
|
|
callChangeODL = true;
|
|
}
|
|
}
|
|
}
|
|
// se NON fosse scaduto MA è simulato esegue controllo sul num pezzi da
|
|
// fare, se sfora (RANDOM) > +(50...110)% --> cambia!
|
|
if (!callChangeODL && IOBConfFull.Odl.ChangeOdlMode == "SIMUL")
|
|
{
|
|
var rawCount = await callUrl(urlGetNumPzCurrODL, false);
|
|
if (int.TryParse(rawCount, out var numPzReqOdl))
|
|
{
|
|
int limitQty = (numPzReqOdl * rndGen.Next(150, 210)) / 100;
|
|
callChangeODL = contapezziPLC > limitQty;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// vero processing...
|
|
if (callChangeODL)
|
|
{
|
|
lgTrace("Chiamata: ProcessAutoOdlAsync --> forceSplitODL");
|
|
fatto = await forceSplitOdl();
|
|
// metto contapezzi a zero..
|
|
contapezziPLC = 0;
|
|
// aspetto 2 sec per proseguire dopo force split...
|
|
await Task.Delay(2000);
|
|
pzCountResetted = false;
|
|
lgInfo("Esecuzione ProcessAutoOdlAsync completata --> pzCountResetted = false");
|
|
// imposto x prox controllo veto a 15 min
|
|
VetoProcessAutoOdl = adesso.AddMinutes(15);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgTrace("ProcessAutoOdlAsync | AutoChangeOdl DISABILITATO");
|
|
}
|
|
// loggo se ok + processing post opzionali
|
|
if (fatto)
|
|
{
|
|
lgInfo($"Effettuato ProcessAutoOdlAsync | mode: {IOBConfFull.Odl.ChangeOdlMode}");
|
|
processAutoOdlExtraStep();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua processing degli allarmi CNC SE disponibili
|
|
/// </summary>
|
|
public void processCncAlarms()
|
|
{
|
|
if (utils.CRB("enableAlarms"))
|
|
{
|
|
Dictionary<string, string> currAlarms = new Dictionary<string, string>();
|
|
if (connectionOk)
|
|
{
|
|
currAlarms = getCncAlarms();
|
|
}
|
|
else
|
|
{
|
|
lgError("Errore connessione mancante x getCncAlarms");
|
|
}
|
|
// verifico SE sia cambiato il programma...
|
|
if (currAlarms.Count > 0)
|
|
{
|
|
try
|
|
{
|
|
string sVal = "";
|
|
if (lastAlarm != currAlarms["CNC_ALARM"])
|
|
{
|
|
// salvo!
|
|
lastAlarm = currAlarms["CNC_ALARM"];
|
|
// per ogni valore del dizionario mostro ed accodo!
|
|
foreach (var item in currAlarms)
|
|
{
|
|
// verifico NON sia un ND...
|
|
if (item.Key == "ND" || string.IsNullOrEmpty(item.Key))
|
|
{
|
|
// log anomalia...
|
|
lgTrace($"Errore in predisposizione FL Allarmi CNC: item.key risulta ND! | item.key: {item.Key} | item.Value: {item.Value}");
|
|
}
|
|
else
|
|
{
|
|
sVal = string.Format("[CNC_ALARM]{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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// accodo ALTRI allarmi NON CNC...
|
|
foreach (var item in currAlarms.Where(X => X.Key != "CNC_ALARM"))
|
|
{
|
|
// verifico NON sia un ND...
|
|
if (item.Key == "ND" || string.IsNullOrEmpty(item.Key))
|
|
{
|
|
// log anomalia...
|
|
lgTrace($"Errore in predisposizione FL Allarmi NON CNC: item.key risulta ND! | item.key: {item.Key} | item.Value: {item.Value}");
|
|
}
|
|
else
|
|
{
|
|
sVal = $"{item.Key} | {item.Value}";
|
|
bool sent = accodaFLog(item.Key, sVal, qEncodeFLog(item.Key, item.Value));
|
|
if (sent)
|
|
{ // traccio valore DynData x analisi
|
|
trackDynData(item.Key, item.Value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError($"Eccezione in processCncAlarms{Environment.NewLine}{exc}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua processing contapezzi (ed eventualmente alza il bit di contapezzo...)
|
|
/// </summary>
|
|
public virtual void processContapezzi()
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// Effettua processing CUSTOM x l'IOB corrente (ed invia ad IO) (task svolto tipicamente
|
|
/// ogni 5 sec se base timer 10ms, vedere app.config)
|
|
/// </summary>
|
|
public virtual void processCustomTaskLF()
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// Effettua processing CUSTOM x l'IOB corrente (ed invia ad IO) (task svolto tipicamente
|
|
/// ogni 3 sec se base timer 10ms, vedere app.config)
|
|
/// </summary>
|
|
public virtual void processCustomTaskMF()
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// Task periodici SE disconnesso
|
|
/// </summary>
|
|
public virtual void processDisconnectedTask()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua processing del recupero dei valori dinamici:
|
|
/// es: speed (RPM, feedrate) degli assi, valori pressioni, temeprature
|
|
/// </summary>
|
|
public void processDynData()
|
|
{
|
|
// FixMe Todo: generalizzare parametri nell'obj?
|
|
bool enableByApp = utils.CRB("enableDynData");
|
|
Dictionary<string, string> currDynData = new Dictionary<string, string>();
|
|
|
|
if (enableByApp || IOBConfFull.FluxLog.EnableDynData)
|
|
{
|
|
// verifico se ho fattore demoltiplica...
|
|
if (demFactDynData > 1)
|
|
{
|
|
lgTrace($"Non eseguo processDynData: fatt veto {demFactDynData}");
|
|
// riduco...
|
|
demFactDynData--;
|
|
}
|
|
else
|
|
{
|
|
lgTrace("Inizio processDynData");
|
|
// sistemo il valore di demoltiplica a default
|
|
fixDemFactDynData();
|
|
|
|
// proseguo
|
|
if (connectionOk)
|
|
{
|
|
currDynData = getDynData();
|
|
bool hasDynDataVal = currDynData.Count > 0;
|
|
if (!hasDynDataVal)
|
|
{
|
|
lgInfo($"Errore processDynData.getDynData: nessun valori DynData ricevuto");
|
|
}
|
|
else
|
|
{
|
|
// verifico DynData siano validi: se tutti uguali NON li considero validi...
|
|
bool isValidSet = checkValidDynData(currDynData);
|
|
// se non valido loggo e NON proseguo..
|
|
if (!isValidSet)
|
|
{
|
|
lgInfo($"Errore processDynData.getDynData: Valori ricevuti NON validi | # dynData{currDynData.Count}");
|
|
}
|
|
else
|
|
{
|
|
var currAlarmData = getAlarmData();
|
|
lgTrace($"currDynData: {currDynData.Count} | currAlarmData: {currAlarmData.Count}");
|
|
// se ho allarmi
|
|
if (currAlarmData != null && currAlarmData.Count > 0)
|
|
{
|
|
foreach (var item in currAlarmData)
|
|
{
|
|
if (!currDynData.ContainsKey(item.Key))
|
|
{
|
|
// aggiungo!
|
|
currDynData.Add(item.Key, item.Value);
|
|
}
|
|
}
|
|
}
|
|
// verifico parametro sintesi... che ricalcolo ogni volta
|
|
if (!currDynData.ContainsKey("DYNDATA") && hasDynDataVal)
|
|
{
|
|
currDynData.Add("DYNDATA", $"{currDynData.Count}xVal");
|
|
}
|
|
// verifico se DynData sia abilitato IN GENERALE
|
|
if (!IOBConfFull.FluxLog.DisDynData)
|
|
{
|
|
try
|
|
{
|
|
string sVal = "";
|
|
// se richiesto send diretto...
|
|
if (IOBConfFull.FluxLog.ForceDynData)
|
|
{
|
|
// per ogni valore del dizionario mostro ed accodo!
|
|
foreach (var item in currDynData)
|
|
{
|
|
// controllo se vietato dynData
|
|
if (IOBConfFull.FluxLog.SendDynDataRec || item.Key != "DYNDATA")
|
|
{
|
|
// verifico NON sia un ND...
|
|
if (item.Key == "ND" || string.IsNullOrEmpty(item.Key))
|
|
{
|
|
// log anomalia...
|
|
lgTrace($"Errore in processDynData.01: item.key risulta ND! | item.key: {item.Key} | item.Value: {item.Value}");
|
|
}
|
|
else
|
|
{
|
|
sVal = string.Format("[DYNDATA]{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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// altrimenti verifico SE sia cambiato il valore dei DynData...
|
|
else if (hasDynDataVal && (lastDynDataCtrlVal == null || lastDynDataCtrlVal != currDynData["DYNDATA"]))
|
|
{
|
|
// salvo!
|
|
lastDynDataCtrlVal = currDynData["DYNDATA"];
|
|
// per ogni valore del dizionario mostro ed accodo!
|
|
foreach (var item in currDynData)
|
|
{
|
|
// controllo se vietato dynData
|
|
if (IOBConfFull.FluxLog.SendDynDataRec || item.Key != "DYNDATA")
|
|
{
|
|
// verifico NON sia un ND...
|
|
if (item.Key == "ND" || string.IsNullOrEmpty(item.Key))
|
|
{
|
|
// log anomalia...
|
|
lgTrace($"Errore in processDynData.02: item.key risulta ND! | item.key: {item.Key} | item.Value: {item.Value}");
|
|
}
|
|
else
|
|
{
|
|
sVal = string.Format("[DYNDATA]{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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// salvo array...
|
|
lastDynData = currDynData;
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError(exc, "Eccezione in processDynData");
|
|
}
|
|
}
|
|
// ora popolo prod data dai dynData...
|
|
foreach (var item in currDynData)
|
|
{
|
|
// se configurato x scrivere valori in WRITE PROD
|
|
if (IOBConfFull.FluxLog.CopyDyn2MemWrite)
|
|
{
|
|
// se presente nelle memorie write/read --> metto in curr data...
|
|
if (memMap != null && memMap.mMapWrite.ContainsKey(item.Key) && !string.IsNullOrEmpty(item.Value))
|
|
{
|
|
upsertKey(item.Key, item.Value);
|
|
}
|
|
}
|
|
// verifico area read x i lastProd...
|
|
if (memMap != null && memMap.mMapRead.ContainsKey(item.Key) && !string.IsNullOrEmpty(item.Value))
|
|
{
|
|
upsertKeyLP(item.Key, item.Value);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgError("Errore connessione mancante x getDynData");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processa gestione memoria x invio dati QUANDO IOB è disconnesso da CNC...
|
|
/// </summary>
|
|
public void processMemoryDiscon()
|
|
{
|
|
// init obj display
|
|
newDisplayData currDispData = new newDisplayData();
|
|
// controllo contatore invio "keepalive"... invio solo a scadenza
|
|
int disconnMaxSec = utils.CRI("disconMaxSec");
|
|
if (DateTime.Now.Subtract(lastDisconnCheck).TotalSeconds > disconnMaxSec)
|
|
{
|
|
// resetto tutti i valori BYTE IN/PREV/OUT... così invio macchina spenta...
|
|
B_input = 0;
|
|
B_output = 0;
|
|
B_previous = -1;
|
|
accodaSigIN(ref currDispData);
|
|
// update controllo
|
|
lastDisconnCheck = DateTime.Now;
|
|
lgInfo($"Send 00 | disconMaxSec: {disconnMaxSec}");
|
|
}
|
|
raiseRefresh(currDispData);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua processing mode/status (EDIT/MDI/...)
|
|
/// </summary>
|
|
public virtual void processMode()
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// Effettua processing ALTRI contatori/parametri (ed invia ad IO)
|
|
/// </summary>
|
|
public virtual void processOtherCounters()
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// Effettua processing del recupero delle OVERRIDE (spindle, feedrate, rapid)
|
|
/// </summary>
|
|
public virtual void processOverride()
|
|
{
|
|
bool enableByApp = utils.CRB("enableOverrides");
|
|
Dictionary<string, string> currOverride = new Dictionary<string, string>();
|
|
if (enableByApp || IOBConfFull.FluxLog.EnableOverrides)
|
|
{
|
|
lgInfo("Inizio processOverride");
|
|
if (connectionOk)
|
|
{
|
|
currOverride = getOverrides();
|
|
}
|
|
else
|
|
{
|
|
lgError("Errore connessione mancante x getOverrides");
|
|
}
|
|
|
|
// SE sono connesso...
|
|
if (connectionOk)
|
|
{
|
|
// se HO dei valori override...
|
|
if (currOverride.Count > 0)
|
|
{
|
|
// verifico SE sia cambiato il programma...
|
|
if (lastOverrideFS != currOverride["FEED_OVER"] || lastOverrideRapid != currOverride["RAPID_OVER"])
|
|
{
|
|
// salvo!
|
|
lastOverrideFS = currOverride["FEED_OVER"];
|
|
lastOverrideRapid = currOverride["RAPID_OVER"];
|
|
// per ogni valore del dizionario mostro ed accodo!
|
|
string sVal = "";
|
|
foreach (var item in currOverride)
|
|
{
|
|
// verifico NON sia un ND...
|
|
if (item.Key == "ND" || string.IsNullOrEmpty(item.Key))
|
|
{
|
|
// log anomalia...
|
|
lgTrace($"Errore in processOverride: item.key risulta ND! | item.key: {item.Key} | item.Value: {item.Value}");
|
|
}
|
|
else
|
|
{
|
|
sVal = string.Format("[OVERRIDES]{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 ciclo controllo richieste server
|
|
/// </summary>
|
|
public async Task processServerRequests()
|
|
{
|
|
// recupero elenco delle cose da fare
|
|
string resp = await getTask2exe("");
|
|
await ProcessResp(resp, "");
|
|
// se fosse multi --> eseguo task per le varie sub (Tavole/Pallet)
|
|
if (isMulti)
|
|
{
|
|
foreach (var item in IOBConfFull.Device.MultiIobList)
|
|
{
|
|
resp = await getTask2exe(item);
|
|
await ProcessResp(resp, item);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processa esecuzione task ricevuti
|
|
/// </summary>
|
|
/// <param name="task2exe"></param>
|
|
/// <param name="codTav"></param>
|
|
/// <returns></returns>
|
|
public async Task<Dictionary<string, string>> ProcessTask(Dictionary<string, string> task2exe, string codTav)
|
|
{
|
|
Dictionary<string, string> taskDone = new Dictionary<string, string>();
|
|
Dictionary<string, string> task2Add = new Dictionary<string, string>();
|
|
// eseguo realmente solo se NON disabilitata questa gestione (caso doppio PLC/HMI)...
|
|
if (!IOBConfFull.Device.DisabExeTask)
|
|
{
|
|
if (task2exe != null)
|
|
{
|
|
string logMsg = $"Task2Exe S01: {task2exe.Count} task ricevuti";
|
|
if (!string.IsNullOrEmpty(codTav))
|
|
{
|
|
logMsg += $" | codTav: {codTav}";
|
|
}
|
|
lgInfo(logMsg);
|
|
int idTask = 0;
|
|
foreach (var item in task2exe)
|
|
{
|
|
idTask++;
|
|
lgInfo($"[{idTask:00}] - {item.Key} --> {item.Value}");
|
|
// verifico SE il task abbia un duplo writeLink e nel caso lo aggiungo...
|
|
var linkVal = getOptWriteLink(item.Key);
|
|
if (!string.IsNullOrEmpty(linkVal))
|
|
{
|
|
// aggiungo a task2add SE manca...
|
|
if (!task2Add.ContainsKey(linkVal))
|
|
{
|
|
task2Add.Add(linkVal, item.Value);
|
|
}
|
|
lgInfo($"Aggiunta task linked: {linkVal} -> {item.Value}");
|
|
}
|
|
}
|
|
// se ho task Link da aggiungere li aggiungo!
|
|
if (task2Add.Count > 0)
|
|
{
|
|
foreach (var item in task2Add)
|
|
{
|
|
task2exe.Add(item.Key, item.Value);
|
|
}
|
|
}
|
|
// chiamo procedura esecutiva (diversa x ogni IOB)
|
|
taskDone = executeTasks(task2exe, codTav);
|
|
lgInfo($"Task2Exe S02: eseguiti {taskDone.Count} task");
|
|
// loggo tutti i task done...
|
|
foreach (var item in taskDone)
|
|
{
|
|
sendToTaskWatch(item.Key, item.Value, codTav);
|
|
}
|
|
// ora chiamo la cancellazione dei task eseguiti...
|
|
foreach (var item in taskDone)
|
|
{
|
|
await remTask2exe(item.Key, item.Value, codTav);
|
|
}
|
|
}
|
|
}
|
|
return taskDone;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Classe fittizia in caso di processing task in MsVHF
|
|
/// </summary>
|
|
public virtual void processVHF()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Classe fittizia in caso di processing watchdog data
|
|
/// </summary>
|
|
public virtual void processWhatchDog()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua rilettura del contapezzi dal server MP/IO
|
|
/// </summary>
|
|
/// <param name="forceCountRec">Forza rilettura da DB tempi ciclo rilevati</param>
|
|
public void pzCntReload(bool forceCountRec, string forceMach = "")
|
|
{
|
|
// se NON disabilitato contapezzi
|
|
if (!IOBConfFull.Device.EnabPzCount)
|
|
{
|
|
lgDebug("pzCntReload disabilitato da EnabPzCount");
|
|
}
|
|
else
|
|
{
|
|
// legge da IO server ULTIMO valore CONTPEZZI al riavvio...
|
|
string currServerCount = "";
|
|
string lastIdxODL = "";
|
|
string calcUrl = "";
|
|
if (!disableOdl)
|
|
{
|
|
var srvAlive = CheckServerAlive();
|
|
if (srvAlive)
|
|
{
|
|
if (isMulti)
|
|
{
|
|
// disabilitato per macchina MULTI, da riportare logica da OpcUa ...
|
|
}
|
|
else
|
|
{
|
|
// leggo PRIMA ODL ....
|
|
calcUrl = string.IsNullOrEmpty(forceMach) ? urlGetCurrODL : urlGetCurrODL.Replace(IOBConfFull.General.CodIOB, forceMach);
|
|
lastIdxODL = utils.callUrl(calcUrl);
|
|
lgTrace($"Lettura ODL dall'url {calcUrl} --> {lastIdxODL}");
|
|
// se ho valori in coda da trasmettere uso dati REDIS
|
|
if (forceCountRec)
|
|
{
|
|
// uso dati da TCiclo registrati...
|
|
calcUrl = string.IsNullOrEmpty(forceMach) ? urlGetPzCountRec : urlGetPzCountRec.Replace(IOBConfFull.General.CodIOB, forceMach);
|
|
currServerCount = utils.callUrl(calcUrl);
|
|
lgInfo($"Lettura contapezzi da TCiclo registrati dall'url {calcUrl} --> num pz: {currServerCount}");
|
|
}
|
|
else
|
|
{
|
|
// uso il contapezzi dichiarato dall'IOB stesso
|
|
calcUrl = string.IsNullOrEmpty(forceMach) ? urlGetPzCount : urlGetPzCount.Replace(IOBConfFull.General.CodIOB, forceMach);
|
|
currServerCount = utils.callUrl(calcUrl);
|
|
lgInfo($"Lettura contapezzi dall'url {calcUrl} --> {currServerCount}");
|
|
}
|
|
// controllo: SE NON HO ODL...
|
|
if (string.IsNullOrEmpty(lastIdxODL) || lastIdxODL == "0")
|
|
{
|
|
// NON AGGIORNO
|
|
contapezziIOB = contapezziPLC;
|
|
lgError($"Errore lettura ODL (vuoto) resta tutto invariato contapezzi: {contapezziIOB} | contapezziPLC {contapezziPLC}");
|
|
}
|
|
else
|
|
{
|
|
if (!string.IsNullOrEmpty(currServerCount))
|
|
{
|
|
// se "-1" resto a ultimo...
|
|
if (currServerCount != "-1")
|
|
{
|
|
int newVal = -1;
|
|
Int32.TryParse(currServerCount, out newVal);
|
|
contapezziIOB = newVal > -1 ? newVal : contapezziIOB;
|
|
lgInfo("Ricevuta conferma da server di {0} pezzi registrati per ODL", currServerCount);
|
|
}
|
|
else
|
|
{
|
|
// NON AGGIORNO
|
|
contapezziIOB = contapezziPLC;
|
|
lgError($"Errore lettura contapezzi (-1) - uso contapezziPLC --> {contapezziPLC}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// registro che ho UN NUOVO ODL
|
|
lgInfo($"Lettura ODL in pzCntReload, currIdxODL {currIdxODL} --> lastIdxODL {lastIdxODL}");
|
|
// se ODL differente e NUOVO è zero --> resetto!
|
|
if (currIdxODL.ToString() != lastIdxODL && lastIdxODL == "0")
|
|
{
|
|
// cambiato ODL quindi reset...
|
|
contapezziIOB = 0;
|
|
lgInfo("Nuovo ODL==0, RESET contapezzi (-->ZERO)");
|
|
}
|
|
}
|
|
// provo a salvare nuovo ODL
|
|
int.TryParse(lastIdxODL, out currIdxODL);
|
|
lgInfo($"ODL | currIdxODL: {currIdxODL}");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// se server NON pronto...
|
|
contapezziIOB = contapezziPLC;
|
|
lgError("Errore server NON pronto in pzCntReload");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fornisce il valore di flusso e valore in formato valido x messa in coda nel formato
|
|
/// dtEve#flusso#valore#cont <paramref name="flusso">Flusso dati</paramref><paramref
|
|
/// name="valore">Valore da salvare</paramref>
|
|
/// </summary>
|
|
public string qEncodeFLog(string flusso, string valore)
|
|
{
|
|
string answ = "";
|
|
// solo se valore !="", su DynData...
|
|
if (flusso != "DYNDATA" || !string.IsNullOrEmpty(valore))
|
|
{
|
|
try
|
|
{
|
|
answ = $"{DateTime.Now:yyyyMMddHHmmssfff}#{flusso}#{valore}#{counterULog}";
|
|
}
|
|
catch
|
|
{ }
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fornisce il valore di flusso e valore in formato valido x messa in coda nel formato
|
|
/// dtEve#flux#valReq#cont <paramref name="eventDT">DataOra evento
|
|
/// registrato</paramref><paramref name="flusso">Flusso dati</paramref><paramref
|
|
/// name="valore">Valore da salvare</paramref>
|
|
/// </summary>
|
|
public string qEncodeFLog(DateTime eventDT, string flusso, string valore)
|
|
{
|
|
string answ = "";
|
|
try
|
|
{
|
|
answ = $"{eventDT:yyyyMMddHHmmssfff}#{flusso}#{valore}#{counterFLog}";
|
|
}
|
|
catch
|
|
{ }
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fornisce il valore di UserLog e valore in formato valido x messa in coda nel formato:
|
|
/// dtEve#flusso#valReq#cont#matrOpr#label#valNum <paramref name="flusso">Flusso dati
|
|
/// (RC/RS/DI)</paramref><paramref name="valore">Valore da inviare
|
|
/// (valString</paramref><paramref name="matrOpr">Matricola operatore</paramref><paramref
|
|
/// name="label">Valore etichetta: causale scarto / tagCode</paramref><paramref
|
|
/// name="valNum">Valore numerico: esitoOk (0/1) / nuo scarti</paramref>
|
|
/// </summary>
|
|
public string qEncodeULog(string flusso, string valore, int matrOpr, string label, int valNum)
|
|
{
|
|
string answ = "";
|
|
try
|
|
{
|
|
answ = $"{DateTime.Now:yyyyMMddHHmmssfff}#{flusso}#{valore}#{matrOpr}#{label}#{valNum}#{counterULog}";
|
|
}
|
|
catch
|
|
{ }
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua lettura dati
|
|
/// <paramref name="currDispData">Parametri da aggiornare x display in form</paramref>
|
|
/// </summary>
|
|
public virtual void readAllData(ref newDisplayData currDispData)
|
|
{
|
|
if (currDispData == null)
|
|
{
|
|
currDispData = new newDisplayData();
|
|
}
|
|
try
|
|
{
|
|
if (DemoIn)
|
|
{
|
|
// segnalo che sono in Demo
|
|
currDispData.semIn = Semaforo.SV;
|
|
}
|
|
if (connectionOk)
|
|
{
|
|
if (!IOBConfFull.Device.DisabStateCh)
|
|
{
|
|
readSemafori(ref currDispData);
|
|
}
|
|
else
|
|
{
|
|
// forzo lettura OK
|
|
currDispData.semIn = Semaforo.SV;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgError("Errore connessione mancante x readSemafori");
|
|
}
|
|
|
|
nReadIN++;
|
|
// aggiorno valore mostrato...
|
|
displayRawData(ref currDispData);
|
|
}
|
|
catch
|
|
{
|
|
currDispData.semIn = Semaforo.SR;
|
|
}
|
|
raiseRefresh(currDispData);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua lettura semafori principale <paramref name="currDispData">Parametri da
|
|
/// aggiornare x display in form</paramref>
|
|
/// </summary>
|
|
public virtual void readSemafori(ref newDisplayData currDispData)
|
|
{
|
|
lastReadPLC = DateTime.Now;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Aggiunge ai dati da inviare alla parentform i valori di RawInput rilevati (32bit)
|
|
/// 2021.08.27: filtrati secondo i valori setup start/size RawDataInput
|
|
/// </summary>
|
|
public virtual void reportRawInput(ref newDisplayData currDispData)
|
|
{
|
|
// processo eventualmente aggiungendo ad elementi esistenti...
|
|
if (currDispData == null)
|
|
{
|
|
currDispData = new newDisplayData();
|
|
}
|
|
try
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.Append($"B_input --> {(short)B_input}{Environment.NewLine}");
|
|
sb.Append($"{baseUtils.binaryForm(B_input)}{Environment.NewLine}");
|
|
sb.Append($"{Environment.NewLine}");
|
|
sb.Append($"----------- RAW Data BankConf -----------{Environment.NewLine}");
|
|
// Do x scontato siano VALIDI i valori di RawDataInputStart / size --> filtro...
|
|
var filtRawData = new byte[RawDataInputSize];
|
|
Array.Copy(RawInput, RawDataInputStart, filtRawData, 0, RawDataInputSize);
|
|
int i = RawDataInputStart;
|
|
foreach (var item in filtRawData)
|
|
{
|
|
sb.Append($"B{i:00} --> {baseUtils.binaryForm(item)} = {(short)item}{Environment.NewLine}");
|
|
i++;
|
|
}
|
|
sb.Append("-------------------------------");
|
|
currDispData.currBitmap = sb.ToString();
|
|
}
|
|
catch
|
|
{ }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Metodo generico di reset contapezzi...
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public virtual bool resetContapezziPLC(string codTav)
|
|
{
|
|
lgInfo("Generic.resetContapezziPLC");
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua salvataggio in LUT del valore ricevuto (valori string)
|
|
/// - non serve calcolo medie o altro
|
|
/// - accoda in out val e basta
|
|
/// </summary>
|
|
/// <param name="outVal">Array eventi da popolare</param>
|
|
/// <param name="valore">valore da salvare</param>
|
|
/// <param name="chiave">ID/chiave di riferimento</param>
|
|
/// <returns></returns>
|
|
public virtual void saveAlarmString(ref Dictionary<string, string> outVal, string valore, string chiave)
|
|
{
|
|
DateTime adesso = DateTime.Now;
|
|
//check obj preliminare
|
|
if (outVal == null)
|
|
{
|
|
outVal = new Dictionary<string, string>();
|
|
}
|
|
// default invio: blindato a 120 sec SE non trova conf x fluxLogResendPeriod
|
|
int vetoSendAlarms = 120;
|
|
if (fluxLogReduce)
|
|
{
|
|
vetoSendAlarms = 60 * fluxLogResendPeriod;
|
|
}
|
|
// verifico se sia scaduto OVVERO variato rispetto a prima oppure oltre tempo
|
|
bool scaduto = !LastTSSSend.ContainsKey(chiave) || (LastTSSSend[chiave].AddSeconds(vetoSendAlarms) < adesso);
|
|
bool cambiato = !LastTSS.ContainsKey(chiave) || !LastTSS[chiave].Equals(valore);
|
|
if (scaduto || cambiato)
|
|
{
|
|
outVal.Add(chiave, valore);
|
|
LastTSS[chiave] = valore;
|
|
LastTSSSend[chiave] = adesso;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// metodo dummy x salvataggio aree memoria conf x CN
|
|
/// </summary>
|
|
/// <param name="tipo">tipo di DUMP</param>
|
|
public virtual void saveMemDump(dumpType tipo)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua salvataggio in LUT del valore ricevuto (valori numerici)
|
|
/// </summary>
|
|
/// <param name="outVal"></param>
|
|
/// <param name="valore"></param>
|
|
/// <param name="chiave"></param>
|
|
/// <returns></returns>
|
|
public virtual void saveValue(ref Dictionary<string, string> outVal, string chiave, double valore)
|
|
{
|
|
//check obj preliminare
|
|
if (outVal == null)
|
|
{
|
|
outVal = new Dictionary<string, string>();
|
|
}
|
|
bool scaduto = stackVal_TSVC(chiave, valore, DateTime.Now);
|
|
// recupero VC
|
|
valore = getVal_TSVC(chiave, scaduto);
|
|
|
|
// 2025.08.05: verifico se il valore SIA configurato come onlyIncr...
|
|
if (IOBConfFull.Memory.mMapRead.ContainsKey(chiave))
|
|
{
|
|
// in quel caso recupero ultimo valore
|
|
var dataRec = IOBConfFull.Memory.mMapRead[chiave];
|
|
// ....e se inferiore uso quello...
|
|
if (dataRec.onlyIncr && valore < LastTSVC[chiave])
|
|
{
|
|
valore = LastTSVC[chiave];
|
|
}
|
|
}
|
|
|
|
// 2023.11.15: gestione deduplica (opzionale) fluxlog... in caso invalida scaduto...
|
|
if (fluxLogReduce && scaduto)
|
|
{
|
|
/*-------------------------------
|
|
* logica processing:
|
|
* - confronto con valore precedente (se non c'è allora registro questo)
|
|
* - se variato OLTRE deadband --> nulla cambia
|
|
* - se NON variato x deadband --> accodo SOLO SE scaduto tempo resend
|
|
* ------------------------------ */
|
|
DateTime adesso = DateTime.Now;
|
|
// cerco tra i veto...
|
|
if (fluxLogReduceVeto.ContainsKey(chiave))
|
|
{
|
|
// per prima cosa verifico che sia ancora valido il veto...
|
|
if (adesso < fluxLogReduceVeto[chiave])
|
|
{
|
|
// verifico valore precedente + deadband x decidere se sia davvero scaduto
|
|
if (fluxLogReduceLast.ContainsKey(chiave))
|
|
{
|
|
scaduto = Math.Abs(fluxLogReduceLast[chiave] - valore) > fluxLogRedDeadBand;
|
|
if (scaduto)
|
|
{
|
|
lgTrace($"saveValue: deadBand superata | {chiave} | old: {fluxLogReduceLast[chiave]:F3} | {valore:F3} | DBand: {fluxLogRedDeadBand}");
|
|
}
|
|
}
|
|
}
|
|
// altrimenti creo un nuovo veto lasciando inalterata la scadenza...
|
|
else
|
|
{
|
|
fluxLogReduceVeto[chiave] = adesso.AddMinutes(fluxLogResendPeriod);
|
|
}
|
|
}
|
|
// se non ci fosse metto veto con periodo std...
|
|
else
|
|
{
|
|
fluxLogReduceVeto.Add(chiave, adesso.AddMinutes(fluxLogResendPeriod));
|
|
if (fluxLogReduceLast.ContainsKey(chiave))
|
|
{
|
|
fluxLogReduceLast[chiave] = valore;
|
|
}
|
|
else
|
|
{
|
|
fluxLogReduceLast.Add(chiave, valore);
|
|
}
|
|
}
|
|
}
|
|
if (scaduto)
|
|
{
|
|
/*--------------------------------------------------
|
|
* FIX gestione decimali e formati IT/EN
|
|
* - limite a 3 digit x valore float
|
|
* - culture invariant (
|
|
* - richiede CHECK i vari double/float parser...
|
|
*
|
|
* per decodificare usare ad esempio:
|
|
*
|
|
* bool success = double.TryParse(rawVal, NumberStyles.Any, CultureInfo.InvariantCulture, out cntDouble);
|
|
* */
|
|
outVal.Add(chiave, valore.ToString("F3", CultureInfo.InvariantCulture));
|
|
//outVal.Add(chiave, $"{valore:N3}");
|
|
LastTSVC[chiave] = valore;
|
|
fluxLogReduceLast[chiave] = valore;
|
|
lgDebug($"saveValue: valore accodato | {chiave}: {valore:F3}");
|
|
}
|
|
else
|
|
{
|
|
lgTrace($"saveValue: filtro attivo | {chiave}: {valore:F3}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua salvataggio in LUT del valore ricevuto (valori string)
|
|
/// - non serve calcolo medie o altro
|
|
/// - accoda in out val e basta
|
|
/// </summary>
|
|
/// <param name="outVal">Array eventi da popolare</param>
|
|
/// <param name="chiave">ID/chiave di riferimento</param>
|
|
/// <param name="valore">valore da salvare</param>
|
|
/// <returns></returns>
|
|
public virtual void saveValueString(ref Dictionary<string, string> outVal, string chiave, string valore)
|
|
{
|
|
DateTime adesso = DateTime.Now;
|
|
//check obj preliminare
|
|
if (outVal == null)
|
|
{
|
|
outVal = new Dictionary<string, string>();
|
|
}
|
|
int maxVetoSeconds = 60;
|
|
|
|
// cerco VC...
|
|
if (TSVC_Data.ContainsKey(chiave))
|
|
{
|
|
maxVetoSeconds = TSVC_Data[chiave].Period;
|
|
}
|
|
// se ho attivo il veto invio fluxLogReduce metto periodo a minuti indicati...
|
|
if (fluxLogReduce)
|
|
{
|
|
maxVetoSeconds = fluxLogResendPeriod * 60;
|
|
}
|
|
|
|
// verifico se sia scaduto OVVERO variato rispetto a prima oppure oltre tempo
|
|
bool scaduto = !LastTSSSend.ContainsKey(chiave) || (LastTSSSend[chiave].AddSeconds(maxVetoSeconds) < adesso);
|
|
bool cambiato = !LastTSS.ContainsKey(chiave) || !LastTSS[chiave].Equals(valore);
|
|
if (scaduto || cambiato)
|
|
{
|
|
outVal.Add(chiave, valore);
|
|
LastTSS[chiave] = valore;
|
|
LastTSSSend[chiave] = adesso;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invio la variazione dello stato allarmi (se avvenuta)
|
|
/// </summary>
|
|
/// <param name="memAddr">COD memoria allarmi</param>
|
|
/// <param name="index">Indice memoria allarmi</param>
|
|
/// <param name="lastStatus">ultimo stato rilevato</param>
|
|
/// <param name="currStatus">stato corrente</param>
|
|
/// <param name="AlarmList">Lista allarmi validi per l'area</param>
|
|
/// <returns></returns>
|
|
public bool sendAlarmVariations(string memAddr, int index, uint lastStatus, uint currStatus, List<string> AlarmList)
|
|
{
|
|
bool fatto = false;
|
|
if (lastStatus != currStatus)
|
|
{
|
|
List<string> ActiveAlarmList = new List<string>();
|
|
// invio GET del MemoryAddress, del banco e del valore currStatus
|
|
lastUrl = $"{urlSendAlarm}?memAddr={memAddr}&index={index}&currStatus={currStatus}";
|
|
if (currStatus == 0)
|
|
{
|
|
ActiveAlarmList.Add("ALL OK");
|
|
}
|
|
else
|
|
{
|
|
// calcolo quali allarmi mostrare dato currStatus ed elenco allarmi
|
|
string bitMap = Convert.ToString(currStatus, 2);
|
|
int bitLen = bitMap.Length;
|
|
// ciclo x associare tutti gli allarmi
|
|
for (int i = 0; i < bitLen; i++)
|
|
{
|
|
// se è 1 --> aggiungo!
|
|
if (bitMap[bitLen - i - 1] == '1')
|
|
{
|
|
// se sono entro limiti
|
|
if (AlarmList.Count >= i)
|
|
{
|
|
ActiveAlarmList.Add($"{i:000}-{AlarmList.ElementAt(i)}");
|
|
}
|
|
//else
|
|
//{
|
|
// ActiveAlarmList.Add($"Unknown Num.{i}");
|
|
//}
|
|
}
|
|
}
|
|
}
|
|
|
|
string rawData = JsonConvert.SerializeObject(ActiveAlarmList);
|
|
string resp = utils.callUrlNow(lastUrl, rawData);
|
|
//string resp = utils.callUrlAsync(lastUrl, rawData);
|
|
if (resp != null)
|
|
{
|
|
if (resp.ToUpper() == "OK")
|
|
{
|
|
// segnalo okReport
|
|
fatto = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgError($"Errore in invio richiesta registrazione allarme | resp: {resp} | URL: {lastUrl}{Environment.NewLine}Payload:{Environment.NewLine}{rawData}");
|
|
}
|
|
}
|
|
return fatto;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invia una LISTA di valori
|
|
/// </summary>
|
|
/// <param name="tipoUrl"></param>
|
|
/// <param name="queueVal"></param>
|
|
public async Task<bool> sendDataBlock(urlType tipoUrl, List<string> listQueueVal, bool force = false)
|
|
{
|
|
bool fatto = false;
|
|
// init obj display
|
|
newDisplayData currDispData = new newDisplayData();
|
|
if (listQueueVal != null)
|
|
{
|
|
try
|
|
{
|
|
// recupero e formatto URL dati da coda...
|
|
lastUrl = urlDataBlock(tipoUrl);
|
|
// in base al tipo di dato compongo il payload Json da inviare
|
|
string payload = jsonPayload(tipoUrl, listQueueVal);
|
|
// async a true SE FLog
|
|
bool doAsync = tipoUrl == urlType.FLog ? true : false;
|
|
// se NON sono in demo effettuo invio!
|
|
if (!DemoOut)
|
|
{
|
|
// SE server alive...
|
|
if (await CheckServerAliveAsync())
|
|
{
|
|
// chiamo URL!
|
|
string answ = await callUrlWithPayloadAsync(lastUrl, payload, doAsync);
|
|
|
|
// valutare invio REST alternativo...
|
|
#if false
|
|
// invio alternativo nuovo
|
|
if (string.IsNullOrEmpty(answ))
|
|
{
|
|
answ = utils.ExecCallPostPlain(lastUrl, payload);
|
|
}
|
|
#endif
|
|
|
|
// loggo!
|
|
lgInfo($"[SEND payload] TipoURL: {tipoUrl} | {listQueueVal.Count} records --> {answ}");
|
|
// se "OK" verde, altrimenti errore --> ROSSO
|
|
if (answ.Contains("OK"))
|
|
{
|
|
fatto = true;
|
|
currDispData.semOut = Semaforo.SV;
|
|
// se oltre 1 min NON era online --> check pezzi!
|
|
if (DateTime.Now.Subtract(lastIobOnline).TotalMinutes > 1 && !isMulti)
|
|
{
|
|
lgInfo($"sendDataBlock --> offline timeout ({lastIobOnline}) --> pzCntReload(true)");
|
|
pzCntReload(true);
|
|
}
|
|
lastIobOnline = DateTime.Now;
|
|
}
|
|
else
|
|
{
|
|
currDispData.semOut = Semaforo.SR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgInfo($"[SERVER KO] {listQueueVal.Count}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
currDispData.semOut = Semaforo.SV;
|
|
// loggo!
|
|
lgInfo($"{listQueueVal.Count} records --> [SIM]");
|
|
}
|
|
nSendOut += listQueueVal.Count;
|
|
// riporto cosa inviato
|
|
currDispData.newUrlCallData = lastUrl;
|
|
// aggiorno data ultimo watchdog...
|
|
lastWatchDog = DateTime.Now;
|
|
}
|
|
catch
|
|
{
|
|
currDispData.semOut = Semaforo.SR;
|
|
}
|
|
}
|
|
raiseRefresh(currDispData);
|
|
return fatto;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua invio a MoonPro del valore richiesto
|
|
/// </summary>
|
|
/// <param name="tipoUrl"></param>
|
|
/// <param name="queueVal">
|
|
/// Valore da trasmettere: es
|
|
/// INPUT: lo status rilevato in HEX
|
|
/// FLog: il valore da trasmettere per il flusso indicato
|
|
/// </param>
|
|
public async Task sendToMoonPro(urlType tipoUrl, string queueVal)
|
|
{
|
|
// controllo NON nullo..
|
|
if (queueVal != null)
|
|
{
|
|
// init obj display
|
|
newDisplayData currDispData = new newDisplayData();
|
|
try
|
|
{
|
|
// recupero e formatto URL dati da coda...
|
|
switch (tipoUrl)
|
|
{
|
|
case urlType.FLog:
|
|
lastUrl = urlFLog(queueVal);
|
|
break;
|
|
|
|
case urlType.SignIN:
|
|
lastUrl = urlInput(queueVal);
|
|
break;
|
|
|
|
default:
|
|
lastUrl = "";
|
|
break;
|
|
}
|
|
// se NON sono in demo effettuo invio!
|
|
if (!DemoOut)
|
|
{
|
|
// SE server alive...
|
|
if (await CheckServerAliveAsync())
|
|
{
|
|
// chiamo URL!
|
|
string answ = await callUrl(lastUrl, false);
|
|
// loggo!
|
|
lgDebug(string.Format("[SEND] {0} -> {1}", queueVal, answ));
|
|
// se oltre 1 min NON era online --> check pezzi!
|
|
if (DateTime.Now.Subtract(lastIobOnline).TotalMinutes > 1 && !isMulti)
|
|
{
|
|
lgInfo($"sendToMoonPro --> offline timeout ({lastIobOnline}) --> pzCntReload(true)");
|
|
pzCntReload(true);
|
|
}
|
|
// se richiesto effettuo refresh contapezzi
|
|
if (needRefreshPzCount && !isMulti)
|
|
{
|
|
pzCntReload(true);
|
|
needRefreshPzCount = false;
|
|
}
|
|
lastIobOnline = DateTime.Now;
|
|
// se "OK" verde, altrimenti errore --> ROSSO
|
|
if (answ == "OK")
|
|
{
|
|
currDispData.semOut = Semaforo.SV;
|
|
}
|
|
else
|
|
{
|
|
currDispData.semOut = Semaforo.SR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgError(string.Format("[SERVER KO] {0}", queueVal));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
currDispData.semOut = Semaforo.SV;
|
|
// loggo!
|
|
lgDebug(string.Format("{0} -> [SIM]", queueVal));
|
|
}
|
|
nSendOut++;
|
|
currDispData.newUrlCallData = lastUrl;
|
|
// aggiorno data ultimo watchdog...
|
|
lastWatchDog = DateTime.Now;
|
|
}
|
|
catch
|
|
{
|
|
currDispData.semOut = Semaforo.SR;
|
|
}
|
|
raiseRefresh(currDispData);
|
|
}
|
|
else
|
|
{
|
|
lgTrace($"Richiesto invio valore nullo, salto");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Metodo generico di IMPOSTAZIONE FORZATA del contapezzi...
|
|
/// </summary>
|
|
/// <param name="newPzCount">Pezzi richiesti</param>
|
|
/// <returns></returns>
|
|
public virtual bool setcontapezziPLC(int newPzCount, string codTav)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua impostazione del conteggio pezzi richiesti
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public virtual bool setPzComm(int pzReq)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processing di Variabili Campionarie x TimeSeries, accoda valori x la VC (se esiste) e
|
|
/// restituisce bool val se SCADUTO periodo controllo
|
|
/// </summary>
|
|
/// <param name="VCName">Nome della VC</param>
|
|
/// <param name="VCVal">Valore (nuovo) delal VC</param>
|
|
/// <returns></returns>
|
|
public bool stackVal_TSVC(string VCName, double VCVal, DateTime dtLimit)
|
|
{
|
|
bool answ = false;
|
|
// cerco VC...
|
|
if (TSVC_Data.ContainsKey(VCName))
|
|
{
|
|
TSVC_Data[VCName].dataArray.Add(VCVal);
|
|
// ora verifico scadenza...
|
|
if (TSVC_Data[VCName].DTStart.AddSeconds(TSVC_Data[VCName].Period) < dtLimit)
|
|
{
|
|
// 2026.01.20: solo se ho almeno 3 valori altrimenti rischio zeri...
|
|
answ = TSVC_Data[VCName].dataArray.Count > 2;
|
|
//answ = true;
|
|
}
|
|
lgTrace($"stackVal_TSVC | {VCName} | {VCVal} | scaduta: {answ}");
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Avvia l'adapter sulla porta richiesta
|
|
/// </summary>
|
|
/// <param name="resetQueue">indica se sia richiesto di SVUOTARE le code delle info</param>
|
|
public virtual void startAdapter(bool resetQueue)
|
|
{
|
|
DateTime adesso = DateTime.Now;
|
|
DateTime scaduto = adesso.AddMinutes(-10);
|
|
lgInfo("Starting adapter...");
|
|
maxJsonData = utils.CRI("maxJsonData");
|
|
maxJsonDataEv = utils.CRI("maxJsonDataEv");
|
|
parentForm.commPlcActive = false;
|
|
adpRunning = true;
|
|
dtAvvioAdp = adesso;
|
|
lastWatchDog = scaduto;
|
|
lastPING = scaduto;
|
|
lastReadPLC = scaduto;
|
|
lastDisconnCheck = scaduto;
|
|
TimingData.resetData();
|
|
// aggiungo altri defaults
|
|
setDefaults(resetQueue);
|
|
adpTryRestart = true;
|
|
// sistemo altri check avvio
|
|
dtVetoQueueIN = DateTime.Now.AddSeconds(vetoQueueIn);
|
|
queueInEnabCurr = false;
|
|
lgInfoStartup($"Impostato veto QUEUE-IN a {vetoQueueIn}s | fino alle {dtVetoQueueIN:yyyy.MM.dd HH:mm:ss}");
|
|
lgDebug($"startAdapter completed | veto queueIn impostato per {vetoQueueIn}s fino alle {dtVetoQueueIN:yyyy.MM.dd HH:mm:ss}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// ferma l'adapter...
|
|
/// </summary>
|
|
/// <param name="tryRestart">
|
|
/// indica se si debba tentare di riavviare l'adapter (con caduta connessione viene fermato
|
|
/// in automatico)
|
|
/// </param>
|
|
/// <param name="forceDequeue">indica se sia richiesto di SVUOTARE le code delle info</param>
|
|
public virtual async Task stopAdapter(bool tryRestart, bool forceDequeue)
|
|
{
|
|
// controllo che non ci sia redis queue altrimenti NON forza svuotamento...
|
|
if (forceDequeue && !IOBConfFull.General.EnabRedisQue)
|
|
{
|
|
// svuoto le code dei valori letti e non ancora trasmessi...
|
|
parentForm.displayTaskAndLog("[CLOSING] Svuotamento FORZATO coda segnali...", true);
|
|
while (QueueIN.Count > 0)
|
|
{
|
|
// INVIO COMUNQUE...!!!
|
|
string valore = "";
|
|
QueueIN.TryDequeue(out valore);
|
|
await sendToMoonPro(urlType.SignIN, valore);
|
|
}
|
|
parentForm.displayTaskAndLog("[CLOSING] Svuotamento FORZATO coda FluxLOG...", true);
|
|
// se ho + di 2 elementi in coda --> uso invio JSON in blocco...
|
|
if (QueueFLog.Count > minJsonData)
|
|
{
|
|
while (QueueFLog.Count > 0)
|
|
{
|
|
List<string> listaValori = new List<string>();
|
|
// se ho + di maxJsonData elementi --> invio un set di dati alla volta
|
|
if (QueueFLog.Count > maxJsonData)
|
|
{
|
|
string currVal = "";
|
|
// prendoi primi maxJsonDataValori
|
|
for (int i = 0; i < maxJsonData; i++)
|
|
{
|
|
QueueFLog.TryDequeue(out currVal);
|
|
listaValori.Add(currVal);
|
|
}
|
|
await sendDataBlock(urlType.FLog, listaValori);
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
// HO FINITO invio di FLog...
|
|
}
|
|
else
|
|
{
|
|
string currVal = "";
|
|
while (QueueFLog.Count > 0)
|
|
{
|
|
// INVIO COMUNQUE...!!!
|
|
QueueFLog.TryDequeue(out currVal);
|
|
await sendToMoonPro(urlType.FLog, currVal);
|
|
}
|
|
}
|
|
|
|
// svuoto coda ULog
|
|
while (QueueULog.Count > 0)
|
|
{
|
|
List<string> listaValori = new List<string>();
|
|
// se ho + di maxJsonData elementi --> invio un set di dati alla volta
|
|
if (QueueULog.Count > maxJsonData)
|
|
{
|
|
string currVal = "";
|
|
// prendoi primi maxJsonDataValori
|
|
for (int i = 0; i < maxJsonData; i++)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
parentForm.displayTaskAndLog("[STOP] Stopping adapter...", true);
|
|
adpTryRestart = false;
|
|
|
|
parentForm.displayTaskAndLog("[STOP] Stopping adapter - last periodic data read...", true);
|
|
|
|
// chiudo la connessione all'adapter...
|
|
tryDisconnect();
|
|
dtStopAdp = DateTime.Now;
|
|
adpTryRestart = tryRestart;
|
|
adpRunning = false;
|
|
// chiudo!
|
|
parentForm.displayTaskAndLog("Adapter Stopped.", true);
|
|
DateTime adesso = DateTime.Now;
|
|
lastReadPLC = adesso;
|
|
lastPING = adesso;
|
|
parentForm.commPlcActive = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processo la coda SignalIN...
|
|
/// </summary>
|
|
public async Task svuotaCodaSignInAsync()
|
|
{
|
|
// verifico SE la coda abbia dei valori...
|
|
if (QueueIN.Count > 0)
|
|
{
|
|
// invio pacchetto di dati (max da conf)
|
|
for (int i = 0; i < nMaxSend; i++)
|
|
{
|
|
if (QueueIN.Count > 0)
|
|
{
|
|
string currVal = "";
|
|
// se online provo
|
|
if (MPOnline)
|
|
{
|
|
if (IobOnline)
|
|
{
|
|
// se ho + di 2 elementi in coda --> uso invio JSON in blocco...
|
|
if (QueueIN.Count > 1)
|
|
{
|
|
List<string> listaValori = new List<string>();
|
|
// se ho + di maxJsonData elementi --> invio un set di dati alla volta
|
|
if (QueueIN.Count > maxJsonDataEv)
|
|
{
|
|
// prendoi primi maxJsonDataValori
|
|
for (int j = 0; j < maxJsonDataEv; j++)
|
|
{
|
|
QueueIN.TryDequeue(out currVal);
|
|
listaValori.Add(currVal);
|
|
}
|
|
await sendDataBlock(urlType.SignIN, listaValori);
|
|
}
|
|
else
|
|
{
|
|
// invio in blocco
|
|
listaValori = QueueIN.ToList();
|
|
// invio
|
|
await sendDataBlock(urlType.SignIN, listaValori);
|
|
// svuoto!
|
|
QueueIN = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueIN", IOBConfFull.General.EnabRedisQue, redisMan);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// INVIO SINGOLO...!!!
|
|
QueueIN.TryDequeue(out currVal);
|
|
await sendToMoonPro(urlType.SignIN, currVal);
|
|
}
|
|
// salvo come last signal in il currVal ultimo... SE !=""
|
|
if (!string.IsNullOrEmpty(currVal))
|
|
{
|
|
lastSignInVal = currVal;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Metodo base connessione...
|
|
/// </summary>
|
|
public virtual void tryConnect()
|
|
{
|
|
dtAvvioAdp = DateTime.Now;
|
|
queueInEnabCurr = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Metodo base disconnessione...
|
|
/// </summary>
|
|
public virtual void tryDisconnect()
|
|
{
|
|
queueInEnabCurr = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inserimento/aggiornamento chiavi/valore in currProdData + cache REDIS
|
|
/// </summary>
|
|
/// <param name="chiave"></param>
|
|
/// <param name="valore"></param>
|
|
/// <returns>True se modificato/inserito, false se INVARIATO</returns>
|
|
public bool upsertKey(string chiave, string valore)
|
|
{
|
|
bool done = false;
|
|
if (currProdData.ContainsKey(chiave))
|
|
{
|
|
// se variato inserisco...
|
|
if (currProdData[chiave] != valore)
|
|
{
|
|
currProdData[chiave] = valore;
|
|
done = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
currProdData.Add(chiave, valore);
|
|
done = true;
|
|
}
|
|
if (done)
|
|
{
|
|
// salvo in redis...
|
|
redisMan.redSaveHashDict(rKeyCurrProdData, currProdData);
|
|
}
|
|
return done;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inserimento/aggiornamento chiavi/valore in lastProdData
|
|
/// </summary>
|
|
/// <param name="chiave"></param>
|
|
/// <param name="valore"></param>
|
|
/// <returns>True se modificato/inserito, false se INVARIATO</returns>
|
|
public bool upsertKeyLP(string chiave, string valore)
|
|
{
|
|
bool done = false;
|
|
if (lastProdData.ContainsKey(chiave))
|
|
{
|
|
// se variato inserisco...
|
|
if (lastProdData[chiave] != valore)
|
|
{
|
|
lastProdData[chiave] = valore;
|
|
done = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lastProdData.Add(chiave, valore);
|
|
done = true;
|
|
}
|
|
return done;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Formatta URL x invio in DataBlock Json dei dati FLog / eventi
|
|
/// </summary>
|
|
/// <param name="tipoUrl"></param>
|
|
/// <returns></returns>
|
|
public virtual string urlDataBlock(urlType tipoUrl)
|
|
{
|
|
// verifico la parte di link "tipoComando"
|
|
string tipoComando = "";
|
|
switch (tipoUrl)
|
|
{
|
|
case urlType.FLog:
|
|
tipoComando = "flogJson";
|
|
break;
|
|
|
|
case urlType.SignIN:
|
|
tipoComando = "evListJson";
|
|
break;
|
|
|
|
case urlType.RawTransf:
|
|
tipoComando = "rawTransfJson";
|
|
break;
|
|
|
|
case urlType.ULog:
|
|
tipoComando = "ulogJson";
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
// URL base x input
|
|
string answ = $@"{urlCommandIob(tipoComando)}";
|
|
// se è disabilitato keepalive aggiungo opzione
|
|
if (IOBConfFull.MapoMes.DisabKeepAlive)
|
|
{
|
|
// se c'è già "?" aggiungo con "&" altrimenti
|
|
string sPar = answ.Contains("?") ? "&&" : "?";
|
|
answ += $@"{sPar}disabKA=true";
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fornisce URL di tipo FluxLog
|
|
/// </summary>
|
|
/// <param name="queueVal">valore salvato in coda nel formato dtEve#flux#valore#counter</param>
|
|
/// <returns></returns>
|
|
public string urlFLog(string queueVal)
|
|
{
|
|
// URL base x input
|
|
string answ = $@"{urlCommandIob("flog")}";
|
|
// decodifica valore!
|
|
string[] valori = qDecodeIN(queueVal);
|
|
// aggiungo flux e valore...
|
|
answ += $@"?flux={valori[1]}&&valore={valori[2]}";
|
|
// aggiondo dataOra evento e corrente + contatore...
|
|
answ += $@"&&dtEve={valori[0]}&&dtCurr={DateTime.Now:yyyyMMddHHmmssfff}&&cnt={valori[3]}";
|
|
// se è disabilitato keepalive aggiungo opzione
|
|
if (IOBConfFull.MapoMes.DisabKeepAlive)
|
|
{
|
|
;
|
|
answ += $"&&disabKA=true";
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// URL per recupero dati ODL alla data...
|
|
/// </summary>
|
|
public string urlGetOdlAtDate(DateTime dtRif)
|
|
{
|
|
string answ = $@"{urlCommandIob("getOdlAtDate")}?dateRif={dtRif:yyyyMMdd}";
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fornisce URL INPUT per i parametri richiesti
|
|
/// </summary>
|
|
/// <param name="queueVal">valore salvato in coda formato dtEve#valore#counter</param>
|
|
/// <returns></returns>
|
|
public string urlInput(string queueVal)
|
|
{
|
|
// URL base x input
|
|
string answ = $@"{urlCommandIob("input")}";
|
|
// decodifica valore!
|
|
string[] valori = qDecodeIN(queueVal);
|
|
// aggiungo macchina e valore...
|
|
answ += $@"?valore={valori[1]}";
|
|
// aggiondo dataOra evento e corrente + contatore...
|
|
answ += $@"&&dtEve={valori[0]}&&dtCurr={DateTime.Now:yyyyMMddHHmmssfff}&&cnt={valori[2]}";
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fornisce URL di tipo UserLog
|
|
/// </summary>
|
|
/// <param name="queueVal">valore salvato in coda nel formato dtEve#flux#valore#counter</param>
|
|
/// <returns></returns>
|
|
public string urlULog(string queueVal)
|
|
{
|
|
// URL base x input
|
|
string answ = $@"{urlCommandIob("ulog")}";
|
|
// decodifica valore!
|
|
string[] valori = qDecodeIN(queueVal);
|
|
// aggiungo macchina e valore...
|
|
answ += $@"?flux={valori[1]}&&valore={valori[2]}";
|
|
// aggiondo dataOra evento e corrente + contatore...
|
|
answ += $@"&&dtEve={valori[0]}&&dtCurr={DateTime.Now:yyyyMMddHHmmssfff}&&cnt={valori[3]}";
|
|
return answ;
|
|
}
|
|
|
|
#endregion Public Methods
|
|
|
|
#region Private Methods
|
|
|
|
/// <summary>
|
|
/// Processing di una risposta raw di task2exe
|
|
/// </summary>
|
|
/// <param name="resp">Risposta come string RAW</param>
|
|
/// <param name="codTav">Cod Tav (opzionale)</param>
|
|
private async Task ProcessResp(string resp, string codTav)
|
|
{
|
|
Dictionary<string, string> task2exe = new Dictionary<string, string>();
|
|
Dictionary<string, string> taskDone = new Dictionary<string, string>();
|
|
if (!string.IsNullOrEmpty(resp) && resp.Length > 2)
|
|
{
|
|
try
|
|
{
|
|
task2exe = JsonConvert.DeserializeObject<Dictionary<string, string>>(resp);
|
|
// se ho da fare chiamo esecuzione..
|
|
if (task2exe.Count > 0)
|
|
{
|
|
taskDone = await ProcessTask(task2exe, codTav);
|
|
}
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError($"Eccezione in processServerRequests.ProcessResp:{Environment.NewLine}{exc}");
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion Private Methods
|
|
}
|
|
} |