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

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 --&gt; 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% --&gt; 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
}
}