using IOB_UT;
using MapoSDK;
using Newtonsoft.Json;
using NLog;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace IOB_WIN
{
    public class IobGeneric
    {
        #region Protected Fields

        /// <summary>
        /// wrapper di log
        /// </summary>
        protected static Logger lg;

        protected bool _connOk = false;

        /// <summary>
        /// valore booleano di check se sia in fase di COMUNICAZIONE ATTIVA con il PLC/NC
        /// </summary>
        protected bool adpCommAct;

        /// <summary>
        /// porta x adapter (x restart)
        /// </summary>
        protected int adpPortNum;

        /// <summary>
        /// DataOra ultimo avvio adapter x watchdog
        /// </summary>
        protected DateTime adpStartRun;

        /// <summary>
        /// Vettore 32 BIT valori in ingresso al filtro
        /// </summary>
        protected int B_input;

        /// <summary>
        /// Vettore 32 BIT valori in uscita dal filtro
        /// </summary>
        protected int B_output;

        /// <summary>
        /// Vettore 32 BIT valori precedenti
        /// </summary>
        protected int B_previous;

        /// <summary>
        /// Dizionario valori impostati x produzione
        /// </summary>
        protected Dictionary<string, string> currProdData = new Dictionary<string, string>();

        /// <summary>
        /// Array dei contatori x segnali blinking
        /// </summary>
        protected int[] i_counters;

        /// <summary>
        /// Indica impianto IN SETUP (fino a quando SMETTE di esserlo...)
        /// </summary>
        protected bool inSetup = false;

        /// <summary>
        /// ultimo tentativo connessione...
        /// </summary>
        protected DateTime lastConnectTry;

        /// <summary>
        /// Ultimo invio contapezzi (x invio delayed)
        /// </summary>
        protected DateTime lastPzCountSend;

        /// <summary>
        /// Dizionario ultimi valori (double) delle TSVC
        /// </summary>
        protected Dictionary<string, double> LastTSVC = new Dictionary<string, double>();

        /// <summary>
        /// Ultima registrazione warning x ODL mancante (x scrivere solo ogni 15 secondi)
        /// </summary>
        protected DateTime lastWarnODL;

        /// <summary>
        /// indica se serva refresh parametri e quindi PLC...
        /// </summary>
        protected bool needRefresh = true;

        /// <summary>
        /// Form chiamante
        /// </summary>
        protected AdapterForm parentForm;

        /// <summary>
        /// TImeout x ping al server
        /// </summary>
        protected int pingServerMsTimeout = utils.CRI("pingMsTimeout");

        /// <summary>
        /// Ritardo minimo x invio contapezzi
        /// </summary>
        protected int pzCountDelay;

        /// <summary>
        /// Dizionario di VC da trattare come TimeSeries (con conf decodificata + processing successivo...)
        /// </summary>
        protected Dictionary<string, VCData> TSVC_Data = new Dictionary<string, VCData>();

        /// <summary>
        /// Dizionario di VARIABILI da trattare come eventi (da inviare quando cambiano oppure a scadenza periodo...)
        /// </summary>
        protected Dictionary<string, EVData> VarArray = new Dictionary<string, EVData>();

        #endregion Protected Fields

        #region Public Fields

        /// <summary>
        /// valore booleano di check se sia stato AVVIATO l'adapter (Running)
        /// </summary>
        public bool adpRunning = false;

        /// <summary>
        /// valore booleano di check se l'adapter STIA SALVANDO
        /// </summary>
        public bool adpSaving = false;

        /// <summary>
        /// valore booleano (richiesta di riavvio automatico)
        /// </summary>
        public bool adpTryRestart;

        /// <summary>
        /// Conf adapter corrente
        /// </summary>
        public IobConfiguration cIobConf;

        /// <summary>
        /// Conteggio ATTUALE ore macchina IN LAVORO
        /// </summary>
        public double contOreMaccLav;

        /// <summary>
        /// Conteggio ATTUALE ore macchina ON
        /// </summary>
        public double contOreMaccOn;

        /// <summary>
        /// contatore x simulazione valori input
        /// </summary>
        public int countSim = 0;

        /// <summary>
        /// ODL attualmente sulla macchina
        /// </summary>
        public Int32 currIdxODL = 0;

        /// <summary>
        /// Modo corrente (da classe ENUM)
        /// </summary>
        public CNC_MODE currMode;

        /// <summary>
        /// ODL corrente caricato sulla macchina (stringa, da chiamata MP/IO)
        /// </summary>
        public string currODL = "";

        /// <summary>
        /// Indica se sia richiesto campionamento memoria PERIODICO
        /// </summary>
        public bool doSampleMemory;

        /// <summary>
        /// Indica se si debba leggere e fare DUMP delle aree di memoria (1 volta solo all'avvio x debug...)
        /// </summary>
        public bool doStartMemDump;

        /// <summary>
        /// Data/ora ultimo avvio adapter
        /// </summary>
        public DateTime dtAvvioAdp = DateTime.Now;

        /// <summary>
        /// Data/ora ultimo spegnimento adapter
        /// </summary>
        public DateTime dtStopAdp = DateTime.Now;

        /// <summary>
        /// Indicazione VETO check status IOB x evitare loop troppo stretti...
        /// </summary>
        public DateTime dtVetoCheckIOB = DateTime.Now.AddDays(-1);

        /// <summary>
        /// Abilitazione lettura PrgName
        /// </summary>
        public bool enablePrgName = true;

        /// <summary>
        /// Abilitazione invio pezzi "in blocco" per recupero contapezzi
        /// </summary>
        public bool enableSendPzCountBlock = false;

        /// <summary>
        /// Determina se sia encessario convertire valori little/big endian (SIEMENS=true, OSAI=FALSE)
        /// </summary>
        public bool hasBigEndian = false;

        /// <summary>
        /// dataOra ultima verifica CNC disconnesso...
        /// </summary>
        public DateTime lastDisconnCheck;

        /// <summary>
        /// Data/ora ultima volta che IOB è stato dichairato online
        /// </summary>
        public DateTime lastIobOnline = DateTime.Now.AddHours(-1);

        /// <summary>
        /// dataOra ultimo log periodico...
        /// </summary>
        public DateTime lastPeriodicLog;

        /// <summary>
        /// dataOra ultimo PING inviato verso il PLC...
        /// </summary>
        public DateTime lastPING;

        /// <summary>
        /// DataOra ultima lettura da PLC
        /// </summary>
        public DateTime lastReadPLC;

        /// <summary>
        /// ULtimo valore inviato (in caso di disconnessione lo reinvia x garantire watchdog...)
        /// </summary>
        public string lastSignInVal = "";

        /// <summary>
        /// DateTime Ultimo valore simulazione generato
        /// </summary>
        public DateTime lastSim;

        /// <summary>
        /// dataOra ultimo segnale inviato...
        /// </summary>
        public DateTime lastWatchDog;

        /// <summary>
        /// Massimo numero di px da inviare in blocco
        /// </summary>
        public int maxSendPzCountBlock = 10;

        /// <summary>
        /// Struttura memoria PLC x lettura/scrittura da JSON file
        /// </summary>
        public plcMemMap memMap;

        /// <summary>
        /// Minimo numero di px da inviare in blocco
        /// </summary>
        public int minSendPzCountBlock = 5;

        /// <summary>
        /// Variabile booleana che indica se sia necessario fare refresh del contapezzi
        /// </summary>
        public bool needRefreshPzCount = true;

        /// <summary>
        /// Dizionario di persistenza per i valori da salvare da/su file
        /// </summary>
        public Dictionary<string, string> persistenceLayer;

        /// <summary>
        /// Determina se utilizzare blocchi di memoria IOT contigui (e quindi processing "monoblocco" semplificato"=
        /// </summary>
        public bool procIotMem = false;

        /// <summary>
        /// Coda valori ALLARMI ove gestiti...
        /// </summary>
        public ConcurrentQueue<string> QueueAlarm = new ConcurrentQueue<string>();

        /// <summary>
        /// Oggetto della coda degli elementi letti di tipo FluxLog (e non ancora trasmessi)
        /// </summary>
        public ConcurrentQueue<string> QueueFLog = new ConcurrentQueue<string>();

        /// <summary>
        /// Oggetto della coda degli elementi letti (e non ancora trasmessi)
        /// </summary>
        public ConcurrentQueue<string> QueueIN = new ConcurrentQueue<string>();

        /// <summary>
        /// Coda valori MESSAGGI/EVENTI (da non sottocampionare come samples)...
        /// </summary>
        public ConcurrentQueue<string> QueueMessages = new ConcurrentQueue<string>();

        /// <summary>
        /// alias booleano false = R
        /// </summary>
        public bool R = false;

        /// <summary>
        /// 32 byte input base (es strobe, 8 word da 32 bit di flags...)
        /// </summary>
        public byte[] RawInput = new byte[32];

        /// <summary>
        /// 32 byte output base (es ack, 8 word da 32 bit di flags...)
        /// </summary>
        public byte[] RawOutput = new byte[32];

        /// <summary>
        /// Oggetto connessione REDIS
        /// </summary>
        public RedisIobCache redisMan;

        /// <summary>
        /// Oggetto cronometro x campionamento durate chiamate
        /// </summary>
        public Stopwatch stopwatch = new Stopwatch();

        /// <summary>
        /// Oggetto gestione TempiCiclo e contapezzi
        /// </summary>
        public TCMan tcMan = new TCMan(0.5, 1.3, 5);

        /// <summary>
        /// Imposta veto chiamata split (durante chiamata, per 60 sec)
        /// </summary>
        public DateTime vetoSplit = DateTime.Now.AddMinutes(1);

        /// <summary>
        /// alias booleano true = W
        /// </summary>
        public bool W = true;

        #endregion Public Fields

        #region Public Constructors

        /// <summary>
        /// inizializzo l'oggetto sulla form SULLA BASE DEL FILE DI CONFIGURAZIONE letto
        /// </summary>
        /// <param name="caller"></param>
        /// <param name="adpConf"></param>
        public IobGeneric(AdapterForm caller, IobConfiguration IOBConf)
        {
            if (IOBConf != null)
            {
                // init oggetto redis...
                redisMan = new RedisIobCache(IOBConf.serverData.MPIP, IOBConf.codIOB);

                // initi oggetto TCMan
                tcMan = new TCMan(IOBConf.TCLambda, IOBConf.TCMaxDelayFactor, IOBConf.TCMaxIncrPz);

                // salvo il form chiamante
                parentForm = caller;
                // configurazione...
                cIobConf = IOBConf;

                lastConnectTry = DateTime.Now;

                // aggiungo nel logger IDX Macchina
                lg = LogManager.GetCurrentClassLogger();

                lgInfo("Avvio preliminare AdapterGeneric");
                // aggiungo altri defaults
                setDefaults(true);

                setParamPlc();

                // checkLogDir x shrink!
                checkShrinkDir();

                // concluso!
                lgInfo("Istanziata classe preliminare IOBGeneric");
            }
            else
            {
                lgError("Error: IobCOnf is null!");
            }
        }

        #endregion Public Constructors

        #region Public Events

        /// <summary>
        /// Evento Iob ha subito un refresh
        /// </summary>
        public event EventHandler<iobRefreshedEventArgs> eh_refreshed;

        #endregion Public Events

        #region Private Properties

        /// <summary>
        /// Verifica se la IOB sia ENABLED (da server o Demo)
        /// </summary>
        private bool checkIobEnabled
        {
            get
            {
                bool answ = false;
                // controllo se ho veto al check...
                if (dtVetoCheckIOB < DateTime.Now)
                {
                    if (DemoOut)
                    {
                        answ = (QueueIN.Count + QueueFLog.Count >= nMaxSend);
                    }
                    else
                    {
                        try
                        {
                            // chiamo URL, se restituisce "OK" è enabled!
                            string callResp = callUrl(urlIobEnabled, true);
                            answ = (callResp == "OK");
                            // attesa casuale se necessario
                            var rand = new Random();
                            // primi 2 test
                            int maxTry = 2;
                            while (maxTry > 0 && !answ)
                            {
                                Thread.Sleep(rand.Next(250, 500));
                                callResp = callUrl(urlIobEnabled, true);
                                answ = (callResp == "OK");
                                maxTry--;
                            }
                            // se NON OK riprovo ANCORA 1 volta...
                            if (!answ)
                            {
                                resetWebClients();
                                Thread.Sleep(rand.Next(250, 1000));
                                callResp = callUrl(urlIobEnabled, false);
                                answ = (callResp == "OK");
                            }
                            // altri 2
                            maxTry = 2;
                            while (maxTry > 0 && !answ)
                            {
                                Thread.Sleep(rand.Next(250, 500));
                                callResp = callUrl(urlIobEnabled, false);
                                answ = (callResp == "OK");
                                maxTry--;
                            }
                            // salvo status...
                            IobOnline = answ;
                            // se online imposto veto check a 5 x tempo reinvio...
                            if (answ)
                            {
                                lastIobOnline = DateTime.Now;
                            }
                            dtVetoCheckIOB = DateTime.Now.AddMilliseconds(baseUtils.nextPauseSendMSec * 5);
                        }
                        catch
                        { }
                    }
                    // verifico SE è variato stato online/offline...
                    if (IobOnline != answ)
                    {
                        // se ORA sono online riporto...
                        if (answ)
                        {
                            lgInfo("IOB ONLINE for server MP/IO");
                        }
                        else
                        {
                            lgInfo("IOB OFFLINE for server MP/IO");
                        }
                    }
                    // fix colore
                    if (answ)
                    {
                        parentForm.commSrvActive = 2;
                    }
                    else
                    {
                        parentForm.commSrvActive = 1;
                    }
                }
                else
                {
                    // altrimenti passo ultimo valore noto
                    answ = IobOnline;
                }
                return answ;
            }
        }

        /// <summary>
        /// test ping all'indirizzo impostato nei parametri
        /// </summary>
        /// <returns></returns>
        private IPStatus testPingServer
        {
            get
            {
                IPStatus answ = IPStatus.Unknown; ;
                IPAddress address;
                PingReply reply;
                using (Ping pingSender = new Ping())
                {
                    address = IPAddress.Loopback;
                    int maxRetry = maxPingRetry + 1;
                    int numRetry = 1; ;
                    string ipAdrr = cIobConf.serverData.MPIP.Replace("http://", "").Replace("https://", "");
                    IPAddress.TryParse(ipAdrr, out address);
                    reply = pingSender.Send(address, pingServerMsTimeout);
                    // se ho timeout riprovo...
                    while (reply.Status != IPStatus.Success && numRetry < maxRetry)
                    {
                        lgInfo($"Ping KO | reply: {reply.Status} --> retry");
                        reply = pingSender.Send(address, pingServerMsTimeout * numRetry / 2);
                        numRetry++;
                        if (reply.Status == IPStatus.Success)
                        {
                            lgInfo("PING OK!");
                            break;
                        }
                    }
                }
                answ = reply.Status;
                return answ;
            }
        }

        #endregion Private Properties

        #region Protected Properties

        /// <summary>
        /// Valore del num max invii consecutivi da coda...
        /// </summary>
        protected static int nMaxSend
        {
            get
            {
                int answ = 5;
                try
                {
                    answ = utils.CRI("nMaxSend");
                }
                catch
                { }
                return answ;
            }
        }

        /// <summary>
        /// Variabile di appoggio GLOBALE x indicare macchina RUN (es x gestione su reset pezzi)
        /// </summary>
        protected bool isRunning { get; set; } = false;

        /// <summary>
        /// Valore limite MASSIMO di invio di dati come array Json
        /// </summary>
        protected int maxJsonData { get; set; } = utils.CRI("maxJsonData");

        /// <summary>
        /// Valore limite MASSIMO di invio di dati come array Json x EVENTI
        /// </summary>
        protected int maxJsonDataEv { get; set; } = utils.CRI("maxJsonDataEv");

        /// <summary>
        /// Max tentativi ping permessi (default: 5)
        /// </summary>
        protected int maxPingRetry { get; set; } = 5;

        /// <summary>
        /// Coda massima ammessa per FLog (se <=0 disattivata...)
        /// </summary>
        protected int maxQueueFLog { get; set; } = utils.CRI("maxQueueFLog");

        /// <summary>
        /// Valore MINIMO limite x decidere invio di dati come array Json
        /// </summary>
        protected int minJsonData { get; set; } = utils.CRI("minJsonData");

        /// <summary>
        /// Numero letture IN da avvio
        /// </summary>
        protected int nReadFilt { get; set; }

        /// <summary>
        /// Numero letture IN da avvio
        /// </summary>
        protected int nReadIN { get; set; }

        /// <summary>
        /// Numero invii OUT (svuotamento coda)
        /// </summary>
        protected int nSendOut { get; set; }

        /// <summary>
        /// Numero simulazioni ammesse...
        /// </summary>
        protected int numSim { get; set; }

        /// <summary>
        /// Indica se sia stato resettato un contapezzi
        /// </summary>
        protected bool pzCountResetted { get; set; } = false;

        /// <summary>
        /// Fornisce il valore letto da BITMAP in formato valido x messa in coda nel formato dtEve#value#cont
        /// </summary>
        protected string qEncodeIN
        {
            get
            {
                string answ = "";
                try
                {
                    answ = string.Format("{0:yyyyMMddHHmmssfff}#{1:X2}#{2}", DateTime.Now, B_output, counterSigIN);
                }
                catch
                { }
                return answ;
            }
        }

        /// <summary>
        /// test ping all'indirizzo PLC/CNC impostato nei parametri
        /// </summary>
        /// <returns></returns>
        protected IPStatus testPingMachine
        {
            get
            {
                IPStatus answ = IPStatus.Unknown;
                // se disabilitato salto...
                if (pingDisabled)
                {
                    answ = IPStatus.Success;
                }
                else
                {
                    IPAddress address;
                    PingReply reply;
                    using (Ping pingSender = new Ping())
                    {
                        address = IPAddress.Loopback;
                        IPAddress.TryParse(cIobConf.cncIpAddr, out address);
                        int pingMsTimeout = cIobConf.pingMsTimeout;
                        reply = pingSender.Send(address, pingMsTimeout);
                        answ = reply.Status;
                    }
                }
                return answ;
            }
        }

        /// <summary>
        /// Secondi standard x veto check status e log
        /// </summary>
        protected int vetoSeconds { get; set; } = utils.CRI("vetoSeconds");

        #endregion Protected Properties

        #region Public Properties

        /// <summary>
        /// Verifica se sia in modalità DEMO avanzata (campionamento da set di valori ammessi...)
        /// </summary>
        public static bool DemoInSample
        {
            get
            {
                return baseUtils.CRB("DemoInSample");
            }
        }

        /// <summary>
        /// Verifica se sia in modalità DEMO x dati OUTPUT
        /// </summary>
        public static bool DemoOut
        {
            get
            {
                return utils.CRB("DemoOut");
            }
        }

        /// <summary>
        /// Indicazione VETO PING a server sino alla data-ora indicata
        /// </summary>
        public static DateTime dtVetoPing
        {
            get
            {
                return utils.dtVetoPing;
            }
            set
            {
                utils.dtVetoPing = value;
            }
        }

        /// <summary>
        /// Indicazione VETO invio a server sino alla data-ora indicata
        /// </summary>
        public static DateTime dtVetoSend
        {
            get
            {
                return utils.dtVetoSend;
            }
            set
            {
                utils.dtVetoSend = value;
            }
        }

        /// <summary>
        /// stato Online/Offline del server MP IO (su REDIS)
        /// </summary>
        public static bool MPOnline
        {
            get
            {
                return utils.MPIO_Online;
            }
            set
            {
                utils.MPIO_Online = value;
            }
        }

        /// <summary>
        /// Verifica se il server sia ALIVE (tramite PING)
        /// </summary>
        public bool checkServerAlive
        {
            get
            {
                bool answ = false;
                // controllo se ho un VETO all'invio...
                if (dtVetoPing < DateTime.Now)
                {
                    if (DemoOut)
                    {
                        answ = false;
                    }
                    else
                    {
                        IPStatus pingStatus = testPingServer;
                        // se passa il ping faccio il resto...
                        if (pingStatus == IPStatus.Success)
                        {
                            string callResp = "";
                            try
                            {
                                // chiamo URL, se restituisce "OK" è alive!
                                callResp = callUrl(urlAlive, false);
                                answ = (callResp == "OK");
                            }
                            catch (Exception exc)
                            {
                                lgError("Errore in checkServerAlive:{0}{1}", Environment.NewLine, exc);
                            }
                            // attesa casuale se necessario
                            var rand = new Random();
                            // primi 3 test
                            int maxTry = 3;
                            while (maxTry > 0 && !answ)
                            {
                                try
                                {
                                    Thread.Sleep(rand.Next(150, 500));
                                    callResp = callUrl(urlAlive, false);
                                    answ = (callResp == "OK");
                                    maxTry--;
                                }
                                catch
                                { }
                            }
                            // se NON OK riprovo ANCORA 1 volta...
                            if (!answ)
                            {
                                resetWebClients();
                                Thread.Sleep(rand.Next(500, 1000));
                                callResp = callUrl(urlAlive, false);
                                answ = (callResp == "OK");
                            }
                            // altri 3
                            maxTry = 3;
                            while (maxTry > 0 && !answ)
                            {
                                try
                                {
                                    Thread.Sleep(rand.Next(150, 500));
                                    callResp = callUrl(urlAlive, false);
                                    answ = (callResp == "OK");
                                    maxTry--;
                                }
                                catch
                                { }
                            }
                            // verifico SE è variato stato online/offline...
                            if (MPOnline != answ)
                            {
                                // se ORA sono online riporto...
                                if (answ)
                                {
                                    lgInfo("SERVER ONLINE in checkServerAlive");
                                    parentForm.commSrvActive = 1;
                                    dtVetoPing = DateTime.Now.AddMilliseconds(baseUtils.nextPauseSendMSec);
                                }
                                else
                                {
                                    lgInfo("SERVER OFFLINE in checkServerAlive");
                                    parentForm.commSrvActive = 0;
                                }
                                // salvo nuovo status...
                                MPOnline = answ;
                            }
                            else
                            {
                                // allungo periodo controllo...
                                dtVetoPing = DateTime.Now.AddMilliseconds(baseUtils.nextPauseSendMSec * 3);
                            }
                        }
                        else
                        {
                            lgInfo($"SERVER NOT RESPONDING (PING at {cIobConf.serverData.MPIP})");
                            MPOnline = false;
                            // imposto veto a 10 volte reinvio dati standard...
                            dtVetoPing = DateTime.Now.AddMilliseconds(baseUtils.nextPauseSendMSec * 3);
                            utils.dtVetoSend = dtVetoPing;
                        }
                    }
                }
                else
                {
                    // altrimenti passo ultimo valore noto...
                    answ = MPOnline;
                }
                return answ;
            }
        }

        /// <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 SignalIN
        /// </summary>
        public int counterSigIN { get; set; }

        /// <summary>
        /// Verifica se sia in modalità DEMO --> da tipo IOB SIMULA...
        /// </summary>
        public bool DemoIn
        {
            get
            {
                return (cIobConf.tipoIob == tipoAdapter.SIMULA);// baseUtils.CRB("DemoIn");
            }
        }

        /// <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
            {
                bool answ = false;
                if (cIobConf.optPar.Count > 0)
                {
                    // cerco con chiave reale IS_MULTI
                    string keyName = "IS_MULTI";
                    if (!cIobConf.optPar.ContainsKey(keyName))
                    {
                        // legacy: accetto anche SIM_MULTI...
                        keyName = "SIM_MULTI";
                    }
                    // vera verifica su chaive...
                    if (cIobConf.optPar.ContainsKey(keyName))
                    {
                        string SIM_MULTI = getOptPar(keyName);
                        answ = SIM_MULTI == "1";
                    }
                }
                return answ;
            }
        }

        /// <summary>
        /// Log verboso da configurazione (SOLO CHAIVE "verbose"...
        /// </summary>
        public bool isVerboseLog { get; set; } = utils.CRB("verbose");

        /// <summary>
        /// Ultimo Alarm letto
        /// </summary>
        public string lastAlarm { get; set; }

        /// <summary>
        /// Ultimo ARRAY DynData letto
        /// </summary>
        public Dictionary<string, string> lastDynData { get; set; } = new Dictionary<string, string>();

        /// <summary>
        /// Ultimo DynData (sunto) letto
        /// </summary>
        public string lastDynDataCtrlVal { get; set; }

        /// <summary>
        /// Ultimo Override set letto
        /// </summary>
        public string lastOverrideFS { get; set; }

        /// <summary>
        /// Ultimo Override set letto
        /// </summary>
        public string lastOverrideRapid { get; set; }

        /// <summary>
        /// Ultimo programma letto
        /// </summary>
        public string lastPrgName { get; set; }

        /// <summary>
        /// Ultimo SysInfo letto
        /// </summary>
        public string lastSysInfo { get; set; }

        /// <summary>
        /// Ultimo URL
        /// </summary>
        public string lastUrl { get; set; }

        /// <summary>
        /// Valore massimo accettato x incremento pezzi letti dal PLC (valore moltiplicato per 100... 200% --> 200)
        /// </summary>
        public int maxPzDeltaPerc
        {
            get
            {
                int answ = 250;
                if (cIobConf.optPar.Count > 0)
                {
                    // cerco con chiave MAX_PZ_INCR_PERC
                    string keyName = "MAX_PZ_INCR_PERC";
                    // vera verifica su chaive...
                    if (cIobConf.optPar.ContainsKey(keyName))
                    {
                        string MAX_PZ_INCR_PERC = getOptPar(keyName);
                        if (!int.TryParse(MAX_PZ_INCR_PERC, out answ))
                        {
                            answ = 300;
                        }
                    }
                }
                return answ;
            }
        }

        /// <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>
        /// indica se ping disabilitato da optPar
        /// </summary>
        public bool pingDisabled
        {
            get
            {
                bool answ = false;
                bool.TryParse(getOptPar("NO_PING"), out answ);
                return answ;
            }
        }

        /// <summary>
        /// DataOra dell'ultima lettura variabile contapezzi da CNC in secondi
        /// </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>
        /// URL per INVIO IN BLOCCO di un INCREMENTO x contapezzi (quelli della macchina: PLC/CNC)...
        /// </summary>
        public string urlAddPzCount
        {
            get
            {
                string answ = "";
                try
                {
                    answ = string.Format(@"http://{0}{1}{2}/savePzCountInc/{3}?qty=", cIobConf.serverData.MPIP, cIobConf.serverData.MPURL, cIobConf.serverData.CMDALIVE, cIobConf.codIOB);
                }
                catch (Exception exc)
                {
                    lgError(exc, "Errore in composizione urlAddPzCount");
                }
                return answ;
            }
        }

        /// <summary>
        /// URL per check alive...
        /// </summary>
        public string urlAlive
        {
            get
            {
                return string.Format(@"http://{0}{1}{2}", cIobConf.serverData.MPIP, cIobConf.serverData.MPURL, cIobConf.serverData.CMDALIVE);
            }
        }

        /// <summary>
        /// URL per forzare split ODL...
        /// </summary>
        public string urlForceSplit
        {
            get
            {
                return string.Format(@"http://{0}{1}{2}{3}?doConfirm=true&qtyFromLast=true&roundStep=500", cIobConf.serverData.MPIP, cIobConf.serverData.MPURL, cIobConf.serverData.CMD_FORCLE_SPLIT_ODL, cIobConf.codIOB);
            }
        }

        /// <summary>
        /// URL per salvataggio contapezzi...
        /// </summary>
        public string urlGetCurrODL
        {
            get
            {
                string answ = "";
                try
                {
                    answ = string.Format(@"http://{0}{1}{2}/getCurrODL/{3}", cIobConf.serverData.MPIP, cIobConf.serverData.MPURL, cIobConf.serverData.CMDALIVE, cIobConf.codIOB);
                }
                catch (Exception exc)
                {
                    lgError(exc, "Errore in composizione urlGetCurrODL");
                }
                return answ;
            }
        }

        /// <summary>
        /// URL per richiamo parametri da scrivere...
        /// </summary>
        public string urlGetParams2Write
        {
            get
            {
                string answ = "";
                try
                {
                    answ = string.Format(@"http://{0}{1}{2}/getObjItems2Write/{3}", cIobConf.serverData.MPIP, cIobConf.serverData.MPURL, cIobConf.serverData.CMDALIVE, cIobConf.codIOB);
                }
                catch (Exception exc)
                {
                    lgError(exc, "Errore in composizione urlGetParams2Write");
                }
                return answ;
            }
        }

        /// <summary>
        /// URL per recupero contapezzi...
        /// </summary>
        public string urlGetPzCount
        {
            get
            {
                string answ = "";
                try
                {
                    answ = $@"http://{cIobConf.serverData.MPIP}{cIobConf.serverData.MPURL}{cIobConf.serverData.CMDALIVE}/getCounter/{cIobConf.codIOB}";
                }
                catch (Exception exc)
                {
                    lgError(exc, "Errore in composizione urlGetPzCount");
                }
                return answ;
            }
        }

        /// <summary>
        /// URL per recupero contapezzi REGISTRATI da TC...
        /// </summary>
        public string urlGetPzCountRec
        {
            get
            {
                string answ = "";
                try
                {
                    answ = $@"http://{cIobConf.serverData.MPIP}{cIobConf.serverData.MPURL}{cIobConf.serverData.CMDALIVE}/getCounterTCRec/{cIobConf.codIOB}";
                }
                catch (Exception exc)
                {
                    lgError(exc, "Errore in composizione urlGetPzCountRec");
                }
                return answ;
            }
        }

        /// <summary>
        /// URL per richiamo task da eseguire...
        /// </summary>
        public string urlGetTask2Exe
        {
            get
            {
                string answ = "";
                try
                {
                    answ = string.Format(@"http://{0}{1}{2}/getTask2Exe/{3}", cIobConf.serverData.MPIP, cIobConf.serverData.MPURL, cIobConf.serverData.CMDALIVE, cIobConf.codIOB);
                }
                catch (Exception exc)
                {
                    lgError(exc, "Errore in composizione urlGetTask2Exe");
                }
                return answ;
            }
        }

        /// <summary>
        /// URL per recupero idle time IOB...
        /// </summary>
        public string urlIdleTime
        {
            get
            {
                return string.Format(@"http://{0}{1}{2}{3}", cIobConf.serverData.MPIP, cIobConf.serverData.MPURL, cIobConf.serverData.CMD_IDLE_TIME, cIobConf.codIOB);
            }
        }

        /// <summary>
        /// URL per recupero inizio ODL...
        /// </summary>
        public string urlInizioOdlIob
        {
            get
            {
                return string.Format(@"http://{0}{1}{2}{3}", cIobConf.serverData.MPIP, cIobConf.serverData.MPURL, cIobConf.serverData.CMD_ODL_STARTED, cIobConf.codIOB);
            }
        }

        /// <summary>
        /// URL per check se abilitato...
        /// </summary>
        public string urlIobEnabled
        {
            get
            {
                return string.Format(@"http://{0}{1}{2}{3}", cIobConf.serverData.MPIP, cIobConf.serverData.MPURL, cIobConf.serverData.CMDENABLED, cIobConf.codIOB);
            }
        }

        /// <summary>
        /// URL per segnalazione reboot...
        /// </summary>
        public string urlReboot
        {
            get
            {
                string answ = "";
                try
                {
                    answ = string.Format(@"http://{0}{1}{2}{3}&mac={4}", cIobConf.serverData.MPIP, cIobConf.serverData.MPURL, cIobConf.serverData.CMDREBO, cIobConf.codIOB, GetMACAddress());
                }
                catch
                {
                    answ = string.Format(@"http://{0}{1}{2}{3}", cIobConf.serverData.MPIP, cIobConf.serverData.MPURL, cIobConf.serverData.CMDREBO, cIobConf.codIOB);
                }
                return answ;
            }
        }

        /// <summary>
        /// URL per richiamo task da eseguire...
        /// </summary>
        public string urlRemTask2Exe
        {
            get
            {
                string answ = "";
                try
                {
                    answ = string.Format(@"http://{0}{1}{2}/remTask2Exe/{3}?taskName=", cIobConf.serverData.MPIP, cIobConf.serverData.MPURL, cIobConf.serverData.CMDALIVE, cIobConf.codIOB);
                }
                catch (Exception exc)
                {
                    lgError(exc, "Errore in composizione urlRemTask2Exe");
                }
                return answ;
            }
        }

        /// <summary>
        /// URL per salvataggio dati PARAMETRI IOB...
        /// </summary>
        public string urlSaveAllParams
        {
            get
            {
                string answ = "";
                try
                {
                    string machineName = Environment.MachineName;
                    string apiCall = "setObjItems";
                    answ = string.Format(@"http://{0}{1}{2}/{3}/{4}", cIobConf.serverData.MPIP, cIobConf.serverData.MPURL, cIobConf.serverData.CMDALIVE, apiCall, cIobConf.codIOB);
                }
                catch (Exception exc)
                {
                    lgError(exc, "Errore in composizione urlSaveMemConf");
                }
                return answ;
            }
        }

        /// <summary>
        /// URL per salvataggio dati conf memoria IOB...
        /// </summary>
        public string urlSaveMemMap
        {
            get
            {
                string answ = "";
                try
                {
                    string machineName = Environment.MachineName;
                    answ = string.Format(@"http://{0}{1}{2}/saveConf/{3}", cIobConf.serverData.MPIP, cIobConf.serverData.MPURL, cIobConf.serverData.CMDALIVE, cIobConf.codIOB);
                }
                catch (Exception exc)
                {
                    lgError(exc, "Errore in composizione urlSaveMemConf");
                }
                return answ;
            }
        }

        /// <summary>
        /// URL per salvataggio dati associazione Machine 2 IOB...
        /// </summary>
        public string urlSetM2IOB
        {
            get
            {
                string answ = "";
                try
                {
                    string machineName = Environment.MachineName;
                    answ = string.Format(@"http://{0}{1}{2}/setM2IOB/{3}?IOB_name={4}", cIobConf.serverData.MPIP, cIobConf.serverData.MPURL, cIobConf.serverData.CMDALIVE, cIobConf.codIOB, machineName);
                }
                catch (Exception exc)
                {
                    lgError(exc, "Errore in composizione urlSetM2IOB");
                }
                return answ;
            }
        }

        /// <summary>
        /// URL per salvataggio VALORI opzionali...
        /// </summary>
        public string urlSetOptVal
        {
            get
            {
                string answ = "";
                try
                {
                    answ = string.Format(@"http://{0}{1}{2}/addOptPar/{3}?", cIobConf.serverData.MPIP, cIobConf.serverData.MPURL, cIobConf.serverData.CMDALIVE, cIobConf.codIOB);
                }
                catch (Exception exc)
                {
                    lgError(exc, "Errore in composizione urlSetOptVal");
                }
                return answ;
            }
        }

        /// <summary>
        /// URL per salvataggio contapezzi...
        /// </summary>
        public string urlSetPzCount
        {
            get
            {
                string answ = "";
                try
                {
                    answ = $@"http://{cIobConf.serverData.MPIP}{cIobConf.serverData.MPURL}{cIobConf.serverData.CMDALIVE}/setCounter/{cIobConf.codIOB}?counter=";
                }
                catch (Exception exc)
                {
                    lgError(exc, "Errore in composizione urlSetPzCount");
                }
                return answ;
            }
        }

        /// <summary>
        /// URL per salvataggio contapezzi (quelli della macchina: PLC/CNC)...
        /// </summary>
        public string urlSetPzCountMAC
        {
            get
            {
                string answ = "";
                try
                {
                    answ = $@"http://{cIobConf.serverData.MPIP}{cIobConf.serverData.MPURL}{cIobConf.serverData.CMDALIVE}/setCounter/MAC_{cIobConf.codIOB}?counter=";
                }
                catch (Exception exc)
                {
                    lgError(exc, "Errore in composizione urlSetPzCountMAC");
                }
                return answ;
            }
        }

        /// <summary>
        /// URL per salvataggio in UPSERT dei PARAMETRI IOB scritti...
        /// </summary>
        public string urlUpdateWriteParams
        {
            get
            {
                string answ = "";
                try
                {
                    string machineName = Environment.MachineName;
                    string apiCall = "upsertObjItems";
                    answ = string.Format(@"http://{0}{1}{2}/{3}/{4}", cIobConf.serverData.MPIP, cIobConf.serverData.MPURL, cIobConf.serverData.CMDALIVE, apiCall, cIobConf.codIOB);
                }
                catch (Exception exc)
                {
                    lgError(exc, "Errore in composizione urlSaveMemConf");
                }
                return answ;
            }
        }

        /// <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 Private Methods

        /// <summary>
        /// Verifica e se necessario comprime directory log...
        /// </summary>
        private void checkShrinkDir()
        {
            string path = string.Format("{0}logs\\{1}", AppDomain.CurrentDomain.BaseDirectory, cIobConf.codIOB);
            baseUtils.shrinkDir(path);
        }

        /// <summary>
        /// Mostra i dati grezzi letti in esadecimale
        /// <paramref name="currDispData">Parametri da aggiornare x display in form</paramref>
        /// </summary>
        private void displayRawData(ref newDisplayData currDispData)
        {
            // mostro update...
            string newString = string.Format("{0:X}", B_input);
            currDispData.newInData = newString;
#if false
      // salvo coda debug...
      QueueDebug.Enqueue(B_input);
#endif
        }

        /// <summary>
        /// Esegue filtraggio dati x bit blinking!!!
        /// </summary>
        private void filterData()
        {
            // effettuo filtraggio dei valori letti... inizializzo OUT!
            B_output = 0;
            // in primis verifico SE ci siano bit blinkng... se non ci sono OUT=IN...
            if (cIobConf.BLINK_FILT == 0)
            {
                B_output = B_input;
            }
            else
            {
                // incomincio con i valori NON blinking: questi "passano invariati", inizio a sommare nel valore OUT...
                B_output = B_input & ~cIobConf.BLINK_FILT;
                // calcolo il valore dei BIT che "passano la maschera"
                int iBlink = B_input & cIobConf.BLINK_FILT;
                // ...aggiungo i "bit che passano"
                B_output += iBlink;

                // calcolo QUALI valori (tra quelli blink) siano PASSATI da 0 a 1 --> init counters...
                BitArray bBlinkStart = new BitArray(new byte[] { Convert.ToByte(iBlink) });
                int[] bitsUp = bBlinkStart.Cast<bool>().Select(bit => bit ? 1 : 0).ToArray();
                for (int i = 0; i < bitsUp.Length; i++)
                {
                    // SE 1... impostiamo contatori al MAX
                    if (bitsUp[i] == 1)
                    {
                        // se era zero indico START blink...
                        if (i_counters[i] == 0)
                        {
                            lgInfo("START BLINK: B{0}", i);
                        }
                        // imposto comunque contatore al cambio fronte...
                        i_counters[i] = cIobConf.MAX_COUNTER_BLINK;
                    }
                }

                // quelli che sono zero... LI RECUPERO E LI PROCESSO...
                int iZero = ~B_input & cIobConf.BLINK_FILT;
                BitArray bBlinkEnd = new BitArray(new byte[] { Convert.ToByte(iZero) });
                int[] bitsDown = bBlinkEnd.Cast<bool>().Select(bit => bit ? 1 : 0).ToArray();
                for (int i = 0; i < bitsDown.Length; i++)
                {
                    // se era a zero (invertito...)
                    if (bitsDown[i] == 1)
                    {
                        // SE è in corso il conteggio...
                        if (i_counters[i] > 0)
                        {
                            // decremento!
                            i_counters[i] -= 1;
                            // se è zero NON faccio nulla, altrimenti SOMMO...
                            if (i_counters[i] > 0)
                            {
                                B_output += 1 << i;
                            }
                            else
                            {
                                lgInfo("END BLINK: B{0}", i);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Imposta eventuali altri valori default
        /// </summary>
        private void fixDefaultPar()
        {
            // parametro max tentativi PING...
            string s_maxPingRetry = getOptPar("MAX_PING_RETRY");
            if (!string.IsNullOrEmpty(s_maxPingRetry))
            {
                int numRetry = 5;
                int.TryParse(s_maxPingRetry, out numRetry);
                maxPingRetry = numRetry;
            }
        }

        /// <summary>
        /// recupera valore salvato in persistence layer (se non c'è crea...)
        /// </summary>
        /// <param name="keyVal"></param>
        /// <returns></returns>
        private string getStoredVal(string keyVal)
        {
            string value = "";
            try
            {
                if (persistenceLayer != null)
                {
                    if (!persistenceLayer.TryGetValue(keyVal, out value))
                    {
                        persistenceLayer.Add(keyVal, "0");
                    }
                }
            }
            catch (Exception exc)
            {
                lgError(string.Format("Eccezione in getStoredVal: {0}{1}", Environment.NewLine, exc));
            }
            return value;
        }

        /// <summary>
        /// recupera valore salvato in persistence layer (se non c'è crea...) come double
        /// </summary>
        /// <param name="keyVal"></param>
        /// <returns></returns>
        private double getStoredValDouble(string keyVal)
        {
            double answ = 0;
            try
            {
                answ = Convert.ToDouble(getStoredVal(keyVal));
            }
            catch (Exception exc)
            {
                lgError(string.Format("Eccezione in getStoredValDouble: {0}{1}", Environment.NewLine, exc));
            }
            answ = (answ < (double.MaxValue / 10 * 9)) ? answ : 0;
            return answ;
        }

        /// <summary>
        /// recupera valore salvato in persistence layer (se non c'è crea...) come INT
        /// </summary>
        /// <param name="keyVal"></param>
        /// <returns></returns>
        private long getStoredValLong(string keyVal)
        {
            long answ = 0;
            try
            {
                answ = Convert.ToInt64(getStoredVal(keyVal));
            }
            catch
            { }
            // verifico che il valore sia minore di 9/10 del valore massimo...
            answ = (answ < (long.MaxValue / 10 * 9)) ? answ : 0;
            return answ;
        }

        /// <summary>
        /// recupera valore salvato in persistence layer (se non c'è crea...) come UINT
        /// </summary>
        /// <param name="keyVal"></param>
        /// <returns></returns>
        private uint getStoredValUInt(string keyVal)
        {
            uint answ = 0;
            try
            {
                answ = Convert.ToUInt32(getStoredVal(keyVal));
            }
            catch (Exception exc)
            {
                lgError(string.Format("Eccezione in getStoredValUInt: {0}{1}", Environment.NewLine, exc));
            }
            // verifico che il valore sia minore di 9/10 del valore massimo...
            answ = (answ < (uint.MaxValue / 10 * 9)) ? answ : 0;
            return answ;
        }

        /// <summary>
        /// Classe fittizia in caso di processing GLOBALE di tutto in 1 solo colpo...
        /// </summary>
        private void processAllMemory()
        {
            // init obj display
            newDisplayData currDispData = new newDisplayData();
            // in primis SALVO valori previous/precedenti
            B_previous = B_output;
            // poi faccio lettura NUOVI valori
            readAllData(ref currDispData);
            // eseguo il filtering dei valori (per i bit "blinking")
            filterData();
            // effettuo confronto valori vecchi/nuovi... SE trovo variazione OPPURE se è passato + di un timeout di controllo...
            if (B_output != B_previous)
            {
                accodaSigIN(ref currDispData);
            }
            raiseRefresh(currDispData);
        }

        /// <summary>
        /// Effettua gestioen programma: legge e mostra su display...
        /// </summary>
        private void processProgram()
        {
            string currPrgName = "";
            // se abilitata lettura prgName
            if (enablePrgName)
            {
                if (connectionOk)
                {
                    currPrgName = getPrgName();
                }
                else
                {
                    lgError("Errore connessione mancante x getPrgName");
                }
            }
            else
            {
                currPrgName = lastPrgName;
            }
            // verifico SE sia cambiato il programma...
            if (lastPrgName != currPrgName)
            {
                // salvo!
                lastPrgName = currPrgName;
                string sVal = string.Format("[PROG]{0}", currPrgName);

                // chiamo accodamento...
                accodaFLog(sVal, qEncodeFLog("PROG", currPrgName));
            }
        }

        /// <summary>
        /// Processo lettura dati sysinfo
        /// </summary>
        private void processSysInfo()
        {
            if (utils.CRB("enableSysInfo"))
            {
                Dictionary<string, string> currSysInfo = new Dictionary<string, string>();

                if (connectionOk)
                {
                    currSysInfo = getSysInfo();
                }
                else
                {
                    lgError("Errore connessione mancante x getSysInfo");
                }
                // verifico SE sia cambiato il programma...
                if (lastSysInfo != currSysInfo["SYSINFO"])
                {
                    // salvo!
                    lastSysInfo = currSysInfo["SYSINFO"];
                    // per ogni valore del dizionario mostro ed accodo!
                    string sVal = "";
                    foreach (var item in currSysInfo)
                    {
                        sVal = string.Format("[SYSINFO]{0}|{1}", item.Key, item.Value);
                        // chiamo accodamento...
                        accodaFLog(sVal, qEncodeFLog(item.Key, item.Value));
                    }
                }
            }
        }

        private void reportDataProc()
        {
            // update valori visualizzazione...
            parentForm.dataProcLabel = string.Format("RAW: {0} --> IN: {1} --> OUT: {2}", nReadIN, nReadFilt, nSendOut);
        }

        /// <summary>
        /// Imposto alcuni valori di default
        /// </summary>
        /// <param name="resetQueue">indica se sia richeisto di SVUOTARE le code delel info</param>
        private void setDefaults(bool resetQueue)
        {
            numSim = utils.CRI("numSim");
            lastPrgName = "";
            nReadIN = 0;
            nReadFilt = 0;
            nSendOut = 0;
            currMode = 0;
            lastAlarm = "";
            doStartMemDump = utils.CRB("doStartMemDump");
            doSampleMemory = utils.CRB("doSampleMemory");
            // svuoto code se richiesto
            if (resetQueue)
            {
                QueueIN = new ConcurrentQueue<string>();
                QueueFLog = new ConcurrentQueue<string>();
                QueueAlarm = new ConcurrentQueue<string>();
                QueueMessages = new ConcurrentQueue<string>();
            }
            // imposto contatori blink a zero...
            i_counters = new int[32];
            lastPeriodicLog = DateTime.Now;
            // fix parametri generali...
            enablePrgName = true;
        }

        private void svuotaCodaContapezzi()
        {
            // permetto al max 2 tentativi infruttuosi...
            int maxTry = 2;
            int oldContapezzi = contapezziIOB;
            // se ho contapezzi OLTRE limite...
            while ((MPOnline) && (contapezziPLC > contapezziIOB + minSendPzCountBlock))
            {
                lgInfo($"Ciclo svuotaCodaContapezzi --> contapezziPLC: {contapezziPLC} | contapezziIOB: {contapezziIOB}");
                if (!isMulti)
                {
                    pzCntReload(true);
                }
                // provo invio
                trySendPzCountBlock();
                // verifica per evitare loop infinito invio fallito
                if (oldContapezzi == contapezziIOB)
                {
                    maxTry--;
                }
                else
                {
                    maxTry = 2;
                    oldContapezzi = contapezziIOB;
                }
                // verifico maxTry: se li ho esauriti esco!
                if (maxTry <= 0)
                {
                    return;
                }
                // aspetto x dare tempo calcolo
                Thread.Sleep(400);
            }
        }

        /// <summary>
        /// Processo la coda FLog...
        /// </summary>
        private void svuotaCodaFLog()
        {
            //controllo se è passato oltre watchdog e non ho inviato nulla --> RE-INVIO (ultimo inviato)!!!!
            if (DateTime.Now.Subtract(lastWatchDog).TotalSeconds > utils.CRI("watchdogMaxSec"))
            {
                string wdStatus = "elapsed";
                string sVal = string.Format("[WDST]{0}", wdStatus);
                // chiamo accodamento...
                accodaFLog(sVal, qEncodeFLog("WDST", wdStatus));
                lastWatchDog = DateTime.Now;
            }
            // verifico SE la coda abbia dei valori...
            if (QueueFLog.Count > 0)
            {
                // invio pacchetto di dati (max da conf)
                for (int i = 0; i < nMaxSend; i++)
                {
                    // SE ho qualcosa in coda...
                    if (QueueFLog.Count > 0)
                    {
                        string currVal = "";
                        if (MPOnline)
                        {
                            if (IobOnline)
                            {
                                // se ho + di 2 elementi in coda --> uso invio JSON in blocco...
                                if (QueueFLog.Count > 1)
                                {
                                    List<string> listaValori = new List<string>();
                                    // se ho + di maxJsonData elementi --> invio un set di dati alla volta
                                    if (QueueFLog.Count > maxJsonData)
                                    {
                                        // prendoi primi maxJsonDataValori
                                        for (int j = 0; j < maxJsonData; j++)
                                        {
                                            QueueFLog.TryDequeue(out currVal);
                                            listaValori.Add(currVal);
                                        }
                                        sendDataBlock(urlType.FLog, listaValori);
                                    }
                                    else
                                    {
                                        // invio in blocco
                                        listaValori = QueueFLog.ToList();
                                        // invio
                                        sendDataBlock(urlType.FLog, listaValori);
                                        // svuoto!
                                        QueueFLog = new ConcurrentQueue<string>();
                                    }
                                }
                                else
                                {
                                    // INVIO SINGOLO...!!!
                                    QueueFLog.TryDequeue(out currVal);
                                    sendToMoonPro(urlType.FLog, currVal);
                                }
                            }
                            else
                            {
                                break;
                            }
                        }
                        else
                        {
                            break;
                        }
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// Cerca di inviare su un altro thread i vari dati accumulati...
        /// </summary>
        private void trySendValues()
        {
            // init obj display
            newDisplayData currDispData = new newDisplayData();
            try
            {
                // verifico se risponde il server...
                if (checkServerAlive)
                {
                    bool iobOk = false;
                    if (utils.CRB("sendDataByThread"))
                    {
                        Task taskCheck = TaskEx.Run(() => iobOk = checkIobEnabled);
                    }
                    else
                    {
                        iobOk = checkIobEnabled;
                    }
                    // verifico SE posso inviare dati
                    if (iobOk)
                    {
                        currDispData.semOut = Semaforo.SV;
                        // verificare come gestire il task secondario senza interferenza (chiamate update su FORM da thread secondari danno errori)
                        if (utils.CRB("sendDataByThread"))
                        {
                            // invio con thread separato...
                            Task taskSigIN = TaskEx.Run(() => svuotaCodaSignIN());
                            Task taskFlog = TaskEx.Run(() => svuotaCodaFLog());
                        }
                        else
                        {
                            // gestione queue SignalIN (invio, display)
                            svuotaCodaSignIN();
                            currDispData.counter = contapezziIOB;
                            raiseRefresh(currDispData);
                            // provo a svuotare coda contapezzi
                            svuotaCodaContapezzi();
                            currDispData.counter = contapezziIOB;
                            raiseRefresh(currDispData);
                            // gestione queue FluxLog (invio, display)
                            svuotaCodaFLog();
                            raiseRefresh(currDispData);
                        }
                    }
                    else
                    {
                        // mostro VETO-SEND x invio... GIALLO
                        currDispData.semOut = Semaforo.SG;
                        if (periodicLog)
                        {
                            lgInfo("IOB - VETO SEND");
                        }
                    }
                }
                else
                {
                    // mostro SERVER KO x invio... ROSSO
                    currDispData.semOut = Semaforo.SR;
                    if (periodicLog)
                    {
                        lgInfo("IOB - SERVER NOT READY");
                    }
                }
            }
            catch (Exception exc)
            {
                lgError($"Errore in fase trySendValues{Environment.NewLine}{exc}");
                currDispData.semOut = Semaforo.SR;
            }
            raiseRefresh(currDispData);
        }

        /// <summary>
        /// Aggiorna un valore del dizionario in INCREMENTO e lo restituisce
        /// </summary>
        /// <param name="i"></param>
        /// <param name="delta"></param>
        /// <param name="searchString"></param>
        /// <returns>Nuovo valore incrementato</returns>
        private double updateValDoubleByIncr(int i, double delta, string searchString)
        {
            // stringa da cercare..
            string keyVal = string.Format(searchString, i + 1);
            // recupero valore precedente...
            double contAct = getStoredValDouble(keyVal);
            // nuovo valore...
            contAct += delta;
            // salvo in ram!
            persistenceLayer[keyVal] = contAct.ToString();
            // rendo il valore!
            return contAct;
        }

        /// <summary>
        /// Aggiorna un valore del dizionario in INCREMENTO e lo restituisce
        /// </summary>
        /// <param name="i"></param>
        /// <param name="delta"></param>
        /// <param name="searchString"></param>
        /// <returns>Nuovo valore incrementato</returns>
        private long updateValLongByIncr(int i, long delta, string searchString)
        {
            // stringa da cercare..
            string keyVal = string.Format(searchString, i + 1);
            // recupero valore precedente...
            long contAct = getStoredValLong(keyVal);
            // nuovo valore...
            contAct += delta;
            // salvo in ram!
            persistenceLayer[keyVal] = contAct.ToString();
            // rendo il valore!
            return contAct;
        }

        /// <summary>
        /// Aggiorna un valore del dizionario in SOSTITUZIONE
        /// </summary>
        /// <param name="i"></param>
        /// <param name="newVal"></param>
        /// <param name="searchString"></param>
        /// <returns>Nuovo valore incrementato</returns>
        private void updateValString(int i, string newVal, string searchString)
        {
            // stringa da cercare..
            string keyVal = string.Format(searchString, i + 1);
            // salvo in ram!
            persistenceLayer[keyVal] = newVal;
        }

        /// <summary>
        /// Aggiorna un valore del dizionario in SOSTITUZIONE e lo restituisce
        /// </summary>
        /// <param name="i"></param>
        /// <param name="newVal"></param>
        /// <param name="searchString"></param>
        /// <returns>Nuovo valore incrementato</returns>
        private void updateValUInt(int i, uint newVal, string searchString)
        {
            // stringa da cercare..
            string keyVal = string.Format(searchString, i + 1);
            // salvo in ram!
            persistenceLayer[keyVal] = newVal.ToString();
        }

        /// <summary>
        /// Aggiorna un valore del dizionario in INCREMENTO e lo restituisce
        /// </summary>
        /// <param name="i"></param>
        /// <param name="delta"></param>
        /// <param name="searchString"></param>
        /// <returns>Nuovo valore incrementato</returns>
        private uint updateValUIntByIncr(int i, uint delta, string searchString)
        {
            // stringa da cercare..
            string keyVal = string.Format(searchString, i + 1);
            // recupero valore precedente...
            uint contAct = getStoredValUInt(keyVal);
            // nuovo valore...
            contAct += delta;
            // salvo in ram!
            persistenceLayer[keyVal] = contAct.ToString();
            // rendo il valore!
            return contAct;
        }

        #endregion Private Methods

        #region Protected Methods

        /// <summary>
        /// Decodifica file MAP (caso <paramref name="ByteNum"/>.bit)
        /// </summary>
        /// <param name="linea"></param>
        /// <param name="separator"></param>
        /// <param name="ByteNum">indirizzo Byte: indirizzo di partenza memoria</param>
        /// <param name="memSize">dimensione singolo slot in byte</param>
        /// <param name="BitNum">indirizzo bit: numero riga x calcolo indice bit</param>
        /// <returns></returns>
        protected static otherData decodeBitData(string linea, char separator, int ByteNum, int memSize, int BitNum)
        {
            if (linea != null)
            {
                string[] valori = linea.Split(separator);
                int shift = 0;
                try
                {
                    shift = Convert.ToInt32(valori[0]) - 1;
                }
                catch
                { }
                int resto = 0;
                Math.DivRem(BitNum, 8, out resto);
                string memAddr = string.Format("{0}.{1}", ByteNum + shift * memSize, resto);
                return new otherData(valori[0], memAddr, valori[1].Trim(), valori[2].Trim());
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Decodifica file MAP generico
        /// </summary>
        /// <param name="linea"></param>
        /// <param name="separator"></param>
        /// <param name="memPre"></param>
        /// <param name="baseAddr"></param>
        /// <param name="memSize"></param>
        /// <returns></returns>
        protected static otherData decodeOtherData(string linea, char separator, string memPre, int baseAddr, int memSize)
        {
            if (linea != null)
            {
                string[] valori = linea.Split(separator);
                int shift = 0;
                try
                {
                    shift = Convert.ToInt32(valori[0]) - 1;
                }
                catch
                { }
                string memAddr = string.Format("{0}{1}", memPre, baseAddr + shift * memSize);
                return new otherData(valori[0], memAddr, valori[1].Trim(), valori[2].Trim());
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Decodifica valore della coda IN nel formato
        /// answ[0]=dtEve
        /// answ[1]=valore
        /// answ[2]=counter
        /// </summary>
        /// <param name="queueVal">dtEve + '#' + value + '#' + cont</param>
        /// <returns></returns>
        protected static string[] qDecodeIN(string queueVal)
        {
            string[] answ = null;
            if (!string.IsNullOrEmpty(queueVal))
            {
                try
                {
                    answ = queueVal.Split('#');
                }
                catch
                { }
            }
            return answ;
        }

        /// <summary>
        /// Stringa raw dei parametri da scrivere...
        /// </summary>
        /// <returns></returns>
        protected string getParams2write()
        {
            string answ = "";
            string url2call = $"{urlGetParams2Write}";
            if (verboseLog)
            {
                lgInfo("chiamata URL " + url2call);
            }
            answ = utils.callUrlNow(url2call);
            // se vuoto faccio seconda prova...
            if (string.IsNullOrEmpty(answ))
            {
                answ = utils.callUrlNow(url2call);
            }
            return answ;
        }

        /// <summary>
        /// Chiede elenco dei task da eseguire
        /// - formato Json
        /// - array di KVP / Dictionary
        /// - formato definito da API x MP/IO/:
        ///   - KEY: task
        ///   - VALUE: array JSon KVP
        /// </summary>
        protected string getTask2exe()
        {
            string answ = "";
            if (checkServerAlive)
            {
                string url2call = $"{urlGetTask2Exe}";
                if (verboseLog)
                {
                    lgInfo("chiamata URL " + url2call);
                }
                answ = utils.callUrlNow(url2call);
            }
            return answ;
        }

        /// <summary>
        /// Effettua logging ERROR corretto impostanto anche la variabile IOB prima di scrivere...
        /// </summary>
        /// <param name="message"></param>
        protected void lgError(string message, bool sendToForm = true)
        {
            lg.Factory.Configuration.Variables["codIOB"] = cIobConf.codIOB;
            lg.Error(message);
            if (sendToForm)
            {
                sendToLogWatch("ERROR", message);
            }
        }

        /// <summary>
        /// Effettua logging ERROR corretto impostanto anche la variabile IOB prima di scrivere...
        /// </summary>
        /// <param name="message"></param>
        /// <param name="args"></param>
        protected void lgError(string message, params object[] args)
        {
            lg.Factory.Configuration.Variables["codIOB"] = cIobConf.codIOB;
            lg.Error(message, args);
            sendToLogWatch("ERROR", message, args);
        }

        /// <summary>
        /// Effettua logging ERROR corretto impostanto anche la variabile IOB prima di scrivere...
        /// </summary>
        /// <param name="exception"></param>
        /// <param name="message"></param>
        /// <param name="args"></param>
        protected void lgError(Exception exception, string message, params object[] args)
        {
            lg.Factory.Configuration.Variables["codIOB"] = cIobConf.codIOB;
            lg.Error(exception, message, args);
            sendToLogWatch("ERROR", message, exception, args);
        }

        /// <summary>
        /// Effettua logging FATAL corretto impostanto anche la variabile IOB prima di scrivere...
        /// </summary>
        /// <param name="message"></param>
        protected void lgFatal(string message, bool sendToForm = true)
        {
            lg.Factory.Configuration.Variables["codIOB"] = cIobConf.codIOB;
            lg.Fatal(message);
            if (sendToForm)
            {
                sendToLogWatch("FATAL", message);
            }
        }

        /// <summary>
        /// Effettua logging FATAL corretto impostanto anche la variabile IOB prima di scrivere...
        /// </summary>
        /// <param name="message"></param>
        /// <param name="args"></param>
        protected void lgFatal(string message, params object[] args)
        {
            lg.Factory.Configuration.Variables["codIOB"] = cIobConf.codIOB;
            lg.Fatal(message, args);
            sendToLogWatch("FATAL", message, args);
        }

        /// <summary>
        /// Effettua logging FATAL corretto impostanto anche la variabile IOB prima di scrivere...
        /// </summary>
        /// <param name="exception"></param>
        /// <param name="message"></param>
        /// <param name="args"></param>
        protected void lgFatal(Exception exception, string message, params object[] args)
        {
            lg.Factory.Configuration.Variables["codIOB"] = cIobConf.codIOB;
            lg.Fatal(exception, message, args);
            sendToLogWatch("FATAL", message, exception, args);
        }

        /// <summary>
        /// Effettua logging INFO corretto impostanto anche la variabile IOB prima di scrivere...
        /// </summary>
        /// <param name="message"></param>
        protected void lgInfo(string message, bool sendToForm = true)
        {
            lg.Factory.Configuration.Variables["codIOB"] = cIobConf.codIOB;
            lg.Info(message);
            if (sendToForm)
            {
                sendToLogWatch("INFO", message);
            }
        }

        /// <summary>
        /// Effettua logging INFO corretto impostanto anche la variabile IOB prima di scrivere...
        /// </summary>
        /// <param name="message"></param>
        /// <param name="args"></param>
        protected void lgInfo(string message, params object[] args)
        {
            lg.Factory.Configuration.Variables["codIOB"] = cIobConf.codIOB;
            lg.Info(message, args);
            sendToLogWatch("INFO", message, args);
        }

        /// <summary>
        /// Legge il file di conf di una MAP di informazioni da gestire con lettura set memoria
        /// </summary>
        /// <param name="vettoreConf">nome vettore memoria</param>
        /// <param name="nomeFile">file origine</param>
        /// <param name="memSize">dimensione (in byte) della memoria</param>
        /// <param name="numVett">dimensione (in byte) della memoria</param>
        protected void loadConfFile(ref otherData[] vettoreConf, string nomeFile, int memSize, ref int numVett)
        {
            otherData lastData = new otherData();
            int totRighe = 0;
            string linea;
            totRighe = File.ReadLines(nomeFile).Count();
            // creo un vettore della dimensione corretta... conta anche commenti tanto poi riduco...
            vettoreConf = new otherData[File.ReadLines(nomeFile).Count()];
            // carica da file...
            StreamReader file = new StreamReader(nomeFile);
            // leggo 1 linea alla volta...
            int numRiga = 0;
            int bitNum = 0;
            int byteNum = 0;
            while ((linea = file.ReadLine()) != null)
            {
                // SE non è un commento...
                if (linea.Substring(0, 1) != "#")
                {
                    // se finisce per BIT allora processo bit-a-bit...
                    if (linea.EndsWith("BOOL"))
                    {
                        try
                        {
                            string[] memIdx = linea.Split(utils.CRC("testCharSep"))[0].Split('.');
                            // calcolo bit e byte number...
                            int.TryParse(memIdx[0], out byteNum);
                            if (memIdx.Length > 1)
                            {
                                int.TryParse(memIdx[1], out bitNum);
                            }
                            else
                            {
                                bitNum = 0;
                            }
                        }
                        catch
                        {
                            byteNum = 0;
                            bitNum = 0;
                        }
                        lastData = decodeBitData(linea, utils.CRC("testCharSep"), byteNum, 1, bitNum);
                        vettoreConf[numRiga] = lastData;
                    }
                    else
                    {
                        lastData = decodeOtherData(linea, utils.CRC("testCharSep"), "", 1, memSize);
                        vettoreConf[numRiga] = lastData;
                    }
                    numRiga++;
                }
            }
            // salvo lunghezza file...
            try
            {
                numVett = Convert.ToInt32(lastData.memAddr) + 1;
            }
            catch
            {
                numVett = numRiga + 1;
            }
            // chiudo file
            file.Close();
            // ora trimmo vettore al solo numero VERO dei valori caricati...
            Array.Resize<otherData>(ref vettoreConf, numRiga);

            if (isVerboseLog)
            {
                lgInfo(string.Format("Fine caricamento vettore di {0} variabili per file {1}", numRiga, nomeFile));
            }
        }

        /// <summary>
        /// Lettura memorie conf speciali (json)
        /// ...SE inserito in [OPTPAR] come PARAM_CONF=nome.json
        /// </summary>
        protected virtual void loadMemConf()
        {
            lgInfo("BEGIN loadMemConf");
            // variabili x gestione send contapezzi in blocco
            string currPar = getOptPar("ENABLE_SEND_PZC_BLOCK");
            if (!string.IsNullOrEmpty(currPar))
            {
                bool.TryParse(currPar, out enableSendPzCountBlock);
                // se abilitato leggo num pezzi da reinviare in blocco
                if (enableSendPzCountBlock)
                {
                    int.TryParse(getOptPar("MAX_SEND_PZC_BLOCK"), out maxSendPzCountBlock);
                    int.TryParse(getOptPar("MIN_SEND_PZC_BLOCK"), out minSendPzCountBlock);
                }
            }
            else
            {
                lgError("loadMemConf: parametro ENABLE_SEND_PZC_BLOCK non trovato, verificare anche MAX_SEND_PZC_BLOCK e MIN_SEND_PZC_BLOCK");
            }
            // inizializzo LUT decodifica
            string jsonConf = getOptPar("PARAM_CONF");
            if (!string.IsNullOrEmpty(jsonConf))
            {
                string jsonFileName = $"{Application.StartupPath}/DATA/CONF/{jsonConf}";
                lgInfo($"Apertura file {jsonFileName}");
                StreamReader reader = new StreamReader(jsonFileName);
                string jsonData = reader.ReadToEnd();
                if (!string.IsNullOrEmpty(jsonData))
                {
                    lgInfo($"File json composto da {jsonData.Length} caratteri");
                    try
                    {
                        memMap = JsonConvert.DeserializeObject<plcMemMap>(jsonData);
                        lgInfo($"Decodifica aree memMap: trovati {memMap.mMapRead.Count} valori TSVC");
                        lgInfo($"Decodifica aree memMap: trovati {memMap.mMapWrite.Count} parametri ");
                        // se ho variabili read --> genero dati TSVC...
                        if (memMap.mMapRead.Count > 0)
                        {
                            TSVC_Data.Clear();
                            LastTSVC.Clear();
                            VCData currConf;
                            int periodo = 0;
                            VC_func funz = VC_func.POINT;
                            // accodo nella conf...
                            foreach (var item in memMap.mMapRead)
                            {
                                funz = item.Value.func;
                                periodo = item.Value.period;
                                currConf = new VCData()
                                {
                                    Funzione = funz,
                                    Period = periodo,
                                    DTStart = DateTime.Now.AddHours(-1),
                                    dataArray = new List<double>()
                                };
                                TSVC_Data.Add(item.Key, currConf);
                            }
                            // documento...
                            foreach (var item in TSVC_Data)
                            {
                                lgInfo($"TSVC: {item.Key} | periodo: {item.Value.Period} | funz: {item.Value.Funzione}");
                                // salvo i valori PREC...
                                LastTSVC.Add(item.Key, 0);
                            }
                        }
                        // infine se obj memoria valido salvo in MP-IO x sue applicazioni
                        if (memMap != null)
                        {
                            // invio su cloud conf memoria...
                            string rawData = JsonConvert.SerializeObject(memMap);
                            utils.callUrlNow($"{urlSaveMemMap}", rawData);
                            // salvo ANCHE come parametri i valori...
                            objItem currItem = new objItem();
                            List<objItem> allParam = new List<objItem>();
                            // valori WRITE
                            foreach (var item in memMap.mMapWrite)
                            {
                                currItem = new objItem()
                                {
                                    uid = item.Value.name,
                                    name = !string.IsNullOrEmpty(item.Value.description) ? item.Value.description : item.Value.name,
                                    writable = true
                                };
                                allParam.Add(currItem);
                            }
                            // valori READ
                            foreach (var item in memMap.mMapRead)
                            {
                                currItem = new objItem()
                                {
                                    uid = item.Value.name,
                                    name = !string.IsNullOrEmpty(item.Value.description) ? item.Value.description : item.Value.name,
                                    writable = false
                                };
                                allParam.Add(currItem);
                            }
                            // invio su cloud parametri!
                            rawData = JsonConvert.SerializeObject(allParam);
                            utils.callUrl($"{urlSaveAllParams}", rawData);
                        }
                    }
                    catch (Exception exc)
                    {
                        lgError($"Eccezione in decodifica conf json{Environment.NewLine}{exc}");
                    }
                }
                else
                {
                    lgError("Errore in loadMemConf: file json vuoto!");
                }
                reader.Dispose();
            }
            else
            {
                lgInfo("loadMemConf: non trovata opzione PARAM_CONF in file INI");
            }
            // loggo
            lgInfo("DONE loadMemConf");
        }

        /// <summary>
        /// Metodo da overridare x scrivere DAVVERO i parametri sul PLC
        /// </summary>
        /// <param name="updatedPar"></param>
        protected virtual void plcWriteParams(List<objItem> updatedPar)
        {
            // non faccio nulla di base...
        }

        /// <summary>
        /// Processa le richieste di scrittura memoria
        /// </summary>
        /// <returns></returns>
        protected string processMemWriteRequests()
        {
            string answ = "";
            // li salvo nei parametri in memoria locale (ogni adapter DOVREBBE salvare POI sul VERO PLC)
            List<objItem> writeList = new List<objItem>();
            List<objItem> updatedPar = new List<objItem>();
            // recupero elenco delle cose da fare
            string resp = getParams2write();
            if (!string.IsNullOrEmpty(resp))
            {
                try
                {
                    writeList = JsonConvert.DeserializeObject<List<objItem>>(resp);
                    // se ho da fare chiamo esecuzione..
                    if (writeList.Count > 0)
                    {
                        foreach (var item in writeList)
                        {
                            // scrivo in memoria
                            if (memMap.mMapWrite.ContainsKey(item.uid))
                            {
                                memMap.mMapWrite[item.uid].value = item.reqValue;
                                // accodo in stringa taskVal...
                                answ += $" | Parameter {item.uid} --> {item.reqValue}";
                                // sistemo valori
                                item.value = item.reqValue;
                                lgInfo($"Effettuato update parametro: actVal = {item.value} | reqVal = {item.reqValue}");
                                item.reqValue = "";
                                // salvo in lista da ritrasmettere
                                updatedPar.Add(item);
                            }
                            else
                            {
                                answ += $" | Error: parameter {item.uid} not found";
                            }
                        }
                        // richiamo scrittura parametri su PLC
                        plcWriteParams(updatedPar);
                        // invio su cloud parametri!
                        string rawData = JsonConvert.SerializeObject(updatedPar);
                        utils.callUrl($"{urlUpdateWriteParams}", rawData);
                    }
                }
                catch (Exception exc)
                {
                    lgError($"Eccezione in processMemWriteRequests:{Environment.NewLine}{exc}");
                }
            }
            else
            {
                lgError("Non è stata ricevuta risposta x task da eseguire");
            }
            return answ;
        }

        protected void raiseRefresh(newDisplayData currDispData)
        {
            if (currDispData != null)
            {
                if (currDispData.hasData)
                {
                    // segnalo refresh!
                    if (eh_refreshed != null)
                    {
                        eh_refreshed(this, new iobRefreshedEventArgs(currDispData));
                    }
                }
            }
        }

        /// <summary>
        /// Cancella dal server i task eseguiti
        /// </summary>
        /// <param name="taskName"></param>
        /// <param name="esitoTask"></param>
        /// <returns></returns>
        protected string remTask2exe(string taskName, string esitoTask)
        {
            string answ = "";
            if (checkServerAlive)
            {
                string url2call = $"{urlRemTask2Exe}{taskName}";
                lgInfo($"Task2Exe | {esitoTask} | chiamata URL {url2call}");
                answ = utils.callUrlNow(url2call);
            }
            return answ;
        }

        /// <summary>
        /// Invia informazioni associazione IOB 2 machine
        /// </summary>
        protected void sendM2IOB()
        {
            if (checkServerAlive)
            {
                lgInfo("chiamata URL " + urlSetM2IOB);
                utils.callUrlNow(urlSetM2IOB);
            }
        }

        /// <summary>
        /// Invia al server IO i valori dei parametri opzionali (es counters)
        /// </summary>
        /// <param name="paramName">Nome parametro</param>
        /// <param name="paramValue">Valore parametro</param>
        protected void sendOptVal(string paramName, string paramValue)
        {
            if (checkServerAlive)
            {
                string url2call = $"{urlSetOptVal}pName={paramName}&pValue={paramValue}";
                lgInfo("chiamata URL " + url2call);
                utils.callUrlNow(url2call);
            }
        }

        /// <summary>
        /// Invia al server IO i valori dei parametri opzionali (es counters)
        /// </summary>
        /// <param name="paramName">Nome parametro</param>
        /// <param name="paramValueInt">Valore parametro INT</param>
        protected void sendOptVal(string paramName, int paramValueInt)
        {
            // override!
            sendOptVal(paramName, paramValueInt.ToString());
        }

        /// <summary>
        /// Invia messaggio a logWatcher
        /// </summary>
        /// <param name="messType"></param>
        /// <param name="message"></param>
        protected void sendToLogWatch(string messType, string message)
        {
            newDisplayData currDispData = new newDisplayData();
            currDispData.newLiveLogData = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} | {messType} | {message}";
            parentForm.updateFormDisplay(currDispData);
        }

        /// <summary>
        /// Invia messaggio a logWatcher
        /// </summary>
        /// <param name="messType"></param>
        /// <param name="message"></param>
        /// <param name="args"></param>
        protected void sendToLogWatch(string messType, string message, params object[] args)
        {
            try
            {
                string expString = string.Format(message, args);
                newDisplayData currDispData = new newDisplayData();
                currDispData.newLiveLogData = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} | {messType} | {expString}";
                parentForm.updateFormDisplay(currDispData);
            }
            catch
            { }
        }

        /// <summary>
        /// Invia messaggio a logWatcher
        /// </summary>
        /// <param name="messType"></param>
        /// <param name="exception"></param>
        /// <param name="message"></param>
        /// <param name="args"></param>
        protected void sendToLogWatch(string messType, string message, Exception exception, params object[] args)
        {
            try
            {
                string expString = string.Format(message, args);
                newDisplayData currDispData = new newDisplayData();
                currDispData.newLiveLogData = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} | {messType} | {expString}{Environment.NewLine}{exception}";
                parentForm.updateFormDisplay(currDispData);
            }
            catch
            { }
        }

        /// <summary>
        /// Invia messaggio a logWatcher
        /// </summary>
        /// <param name="messType"></param>
        /// <param name="message"></param>
        protected void sendToTaskWatch(string messType, string message)
        {
            parentForm.taskWatcher = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} | {messType} | {message}";
        }

        /// <summary>
        /// Impostazioni parametri PLC
        /// </summary>
        protected virtual void setParamPlc()
        {
            loadMemConf();
            fixDefaultPar();
        }

        #endregion Protected Methods

        #region Public Methods

        /// <summary>
        /// Effettua chiamata URL e restituisce risultato
        /// </summary>
        /// <param name="URL"></param>
        /// <param name="doAsync">invio in modalità async (NON GARANTITO ordine...)</param>
        /// <returns></returns>
        public static string callUrl(string URL, bool doAsync)
        {
            string answ = "";
            // Chiamata ASINCRONA
            if (doAsync)
            {
                //Task<string> resp = utils.callUrlAsync(URL);
                //answ = resp.Result;
                answ = utils.callUrlAsync(URL);
            }
            // chiamata SOLO NORMALE SINCRONA...
            else
            {
                answ = utils.callUrl(URL);
            }
            return answ;
        }

        /// <summary>
        /// Effettua chiamata URL e restituisce risultato
        /// </summary>
        /// <param name="URL"></param>
        /// <param name="payload"></param>
        /// <param name="doAsync">invio in modalità async (NON GARANTITO ordine...)</param>
        /// <returns></returns>
        public static string callUrlWithPayload(string URL, string payload, bool doAsync)
        {
            string answ = "";
            // Chiamata ASINCRONA
            if (doAsync)
            {
                answ = utils.callUrlAsync(URL, payload);
            }
            // chiamata SOLO NORMALE SINCRONA...
            else
            {
                answ = utils.callUrl(URL, payload);
            }
            return answ;
        }

        /// <summary>
        /// processa dataLayer e se necessario salva/mostra
        /// </summary>
        public static void checkSavePersDataLayer()
        {
        }

        public static string GetMACAddress()
        {
            NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
            String sMacAddress = string.Empty;
            foreach (NetworkInterface adapter in nics)
            {
                if (string.IsNullOrEmpty(sMacAddress))// only return MAC Address from first card
                {
                    IPInterfaceProperties properties = adapter.GetIPProperties();
                    //sMacAddress = adapter.GetPhysicalAddress().ToString();
                    sMacAddress = string.Join(":", (from z in adapter.GetPhysicalAddress().GetAddressBytes() select z.ToString("X2")).ToArray());
                }
            }
            return sMacAddress;
        }

        public static void resetDebugConsole()
        {
        }

        /// <summary>
        /// Reset dei webclients
        /// </summary>
        public static void resetWebClients()
        {
            utils.resetWebClients();
        }

        /// <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...
        /// </summary>
        /// <param name="val">VALORE RAW (x display)</param>
        /// <param name="encodedVal">VALORE già processato con qEncodeFLog(...)</param>
        public void accodaFLog(string val, string 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 ca coda FLog per superamento maxLengh: {currVal}");
                    }
                }
            }
            // loggo!
            lgInfo(string.Format("[QUEUE-FLOG] {0}", encodedVal));
            counterFLog++;
            if (counterFLog > 9999)
            {
                counterFLog = 0;
            }
        }

        /// <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, trimmo e aggiorno display
            string strOtherData = limitLine2show(string.Format("{0}{1}{2}", newLine, Environment.NewLine, parentForm.dataMonitor_3));
            //parentForm.dataMonitor_3 = strOtherData;
            parentForm.WriteTextSafe(strOtherData);
        }

        /// <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)
        {
            // mostro dati variati letti...
            displayInData(ref currDispData);
            // --> accodo (valore già formattato)!
            QueueIN.Enqueue(qEncodeIN);
            // loggo!
            lgInfo(string.Format("[QUEUE-IN] {0}", qEncodeIN));
            // aggiorno counters ed eventuale reset
            nReadFilt++;
            if (nReadFilt > int.MaxValue - 1)
            {
                nReadFilt = 0; // per evitare buffer overflow...
            }

            counterSigIN++;
            if (counterSigIN > 9999)
            {
                counterSigIN = 0;
            }
        }

        /// <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.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"></param>
        public virtual Dictionary<string, string> executeTasks(Dictionary<string, string> task2exe)
        {
            // Verificare il protocollo: dovrebbe togliere SOLO i task eseguiti...
            Dictionary<string, string> taskDone = new Dictionary<string, string>();
            if (task2exe != null)
            {
                bool taskOk = false;
                string taskVal = "";
                // cerco task specifici: se ho startSetup --> imposto bit DBB701.DBB0.4
                foreach (var item in task2exe)
                {
                    try
                    {
                        taskOk = false;
                        taskVal = "";
                        // converto richiesta in enum...
                        taskType tName = taskType.nihil;
                        Enum.TryParse(item.Key, out tName);
                        // controllo sulla KEY...
                        switch (tName)
                        {
                            case taskType.nihil:
                            case taskType.fixStopSetup:
                            case taskType.forceSetPzCount:
                            case taskType.sendWatchDogMes2Plc:
                                taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | SKIPPED | NO EXEC";
                                lgInfo($"Chiamata senza processing: taskOk: {taskOk} | taskVal: {taskVal}");
                                break;

                            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(item.Key))
                                    {
                                        dataConf currMem = memMap.mMapWrite[item.Key];
                                        string addr = currMem.memAddr;
                                        taskVal = $"SET task: {item.Key} --> {item.Value} | mem: {currMem.memAddr} - {currMem.size} byte";
                                        // salvo il nuovo valore nella memoria... così prox invio lo trasmetterà
                                        memMap.mMapWrite[item.Key].value = item.Value;
                                    }
                                    else
                                    {
                                        taskVal = $"NO DATA MEM, SET task: {item.Key} --> {item.Value}";
                                    }
                                }
                                else
                                {
                                    taskVal = $"NO MemMap found, SET task: {item.Key} --> {item.Value}";
                                }
                                break;

                            case taskType.forceResetPzCount:
                                // reset contapezzi inizio setup
                                taskOk = resetcontapezziPLC();
                                taskVal = taskOk ? "RESET PZ COUNT OK" : "PZ RESET DISABLED | NO EXEC";
                                lgInfo($"Chiamata forceResetPzCount: taskOk: {taskOk} | taskVal: {taskVal}");
                                break;

                            case taskType.startSetup:
                                // reset contapezzi inizio setup
                                taskOk = resetcontapezziPLC();
                                taskVal = taskOk ? "RESET: SETUP START" : "PZ RESET DISABLED | NO EXEC";
                                lgInfo($"Chiamata startSetup: taskOk: {taskOk} | taskVal: {taskVal}");
                                break;

                            case taskType.stopSetup:
                                // reset contapezzi fine setup SE ESPLICITAMENTE IMPOSTATO
                                if (cIobConf.optPar.Count > 0 && getOptPar("ENABLE_PZ_RESET_stopSetup") == "TRUE")
                                {
                                    taskOk = resetcontapezziPLC();
                                }
                                taskVal = taskOk ? "RESET: SETUP END" : "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 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;

                            default:
                                taskVal = "SKIPPED | NO EXEC";
                                lgInfo($"Chiamata default senza processing: taskOk: {taskOk} | taskVal: {taskVal}");
                                break;
                        }
                        // aggiungo task!
                        taskDone.Add(item.Key, taskVal);
                    }
                    catch(Exception exc)
                    {
                        lgError($"Eccezione in executeTasks:{Environment.NewLine}{exc}");
                        lgError($"Dettaglio eccezione: item.key {item.Key} | item.Val {item.Value}");
                    }
                }
            }
            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 (cIobConf.optPar.Count > 0)
                {
                    // ciclo su tutti e cerco occorrenze che INIZINO...
                    foreach (var item in cIobConf.optPar)
                    {
                        if (item.Key.StartsWith(keyStartSearch))
                        {
                            answ.Add(item.Key, item.Value);
                        }
                    }
                }
            }
            return answ;
        }

        /// <summary>
        /// Effettua chiamata x split ODL
        /// </summary>
        /// <returns></returns>
        public bool forceSplitOdl()
        {
            bool fatto = false;
            if (vetoSplit < DateTime.Now)
            {
                // 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 = "";
                    string IOB_MULTI_CNAME = "";
                    string[] elencoMulti = null;

                    try
                    {
                        /***************************************************
                        * Descrizione procedura (OK X SIMULATORI...)
                        *
                        * - 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 fatto 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 fatto reset contapezzi sui follower (+ altre operazioni opzionali, ES imposstazione nome commessa, quantità, articolo...)
                        * - viene fatto 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
                        *
                        * ***************************************************/

                        if (isMulti)
                        {
                            // devo chiamare cambio ODL x OGNI tavola: mi servono i parametri opzionali...
                            IOB_MULTI_CNAME = getOptPar("IOB_MULTI_CNAME");
                            elencoMulti = IOB_MULTI_CNAME.Split(',');
                        }
                        // se normale splitto!
                        if (!isMulti)
                        {
                            // invio chiamata URL x reset ODL su macchina
                            rawSplit = callUrl(urlForceSplit, false);
                            fatto = (rawSplit != "KO") ? true : false;
                        }
                        // se multi gestisco il bit delle tavole...
                        else
                        {
                            foreach (string item in elencoMulti)
                            {
                                // invio chiamata URL x reset ODL su macchina, ATTENZIONE scriviamo | al posto di "#" che in URL sarebbe filtrato...
                                fullUrl = $"{urlForceSplit}|{item}";
                                rawSplit = callUrl(fullUrl, false);
                            }
                            fatto = (rawSplit == "OK") ? true : false;
                        }
                    }
                    catch (Exception exc)
                    {
                        lgError($"Eccezione in forceSplitOdl{Environment.NewLine}{exc}");
                    }
                    // se fatto --> 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>
        ///  effettua recupero dati ed invio valori modificati...
        /// </summary>
        /// <param name="ciclo"></param>
        public void getAndSend(gatherCycle ciclo)
        {
            // init obj display
            newDisplayData currDispData = new newDisplayData();
            // IN OGNI CASO a prima di tutto EFFETTUO GESTIONE INVII dati da code!!!
            try
            {
                trySendValues();
            }
            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();
                                processMode();
                            }
                            else if (ciclo == gatherCycle.MF)
                            {
                                processServerRequests();
                                processOverride();
                                processContapezzi();
                                processCncAlarms();
                                processDynData();
                            }
                            else if (ciclo == gatherCycle.LF)
                            {
                                processOtherCounters();
                                processProgram();
                                // verifico se devo gestire cambio ODL in modo automatico
                                processAutoOdl();
                            }
                            else if (ciclo == gatherCycle.VLF)
                            {
                                if (utils.CRB("enableContapezzi"))
                                {
                                    // rilettura contapezzi da server...
                                    lgInfo("Ciclo VLF: pzCntReload(true)");
                                    if (!isMulti)
                                    {
                                        pzCntReload(true);
                                    }
                                    // refresh associazione Macchina - IOB
                                    sendM2IOB();
                                }
                                // recupero dati SETUP (sysinfo) e li invio/mostro se variati...
                                processSysInfo();
                                // checkLogDir x shrink!
                                checkShrinkDir();
                                // eventuale log!
                                if (utils.CRB("recTime"))
                                {
                                    logTimeResults();
                                }
                            }
                            // 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)
                        {
                            lgInfo("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
            {
                // provo a riconnettere SE abilitato tryRestart...
                if (adpTryRestart && !connectionOk)
                {
                    // controllo se sia scaduto periodi di veto al tryConnect...
                    int waitRecMSec = utils.CRI("waitRecMSec") * 2;
                    DateTime dtVeto = lastConnectTry.AddMilliseconds(waitRecMSec);
                    if (DateTime.Now > dtVeto)
                    {
                        lgInfo($"Retry Time Elapsed (waited for {waitRecMSec} ms)--> NOW tryConnect");
                        lastConnectTry = DateTime.Now;
                        tryConnect();
                    }
                }
                currDispData.semIn = Semaforo.SR;
            }
            raiseRefresh(currDispData);
        }

        /// <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 e lo restituisce
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public string getOptPar(string key)
        {
            string answ = "";
            if (cIobConf.optPar.Count > 0)
            {
                // controllo SE HO il parametro
                if (cIobConf.optPar.ContainsKey(key))
                {
                    answ = cIobConf.optPar[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 = 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 = item > answ ? item : answ;
                }
                // 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)
            {
                if (tipoUrl == urlType.FLog)
                {
                    flogData currData = new flogData();
                    flogJsonPayload fullObj = new flogJsonPayload();
                    fullObj.fluxData = new List<flogData>();
                    string[] valori;
                    int counter = 0;
                    DateTime dtEve = DateTime.Now;
                    // inizio processando ogni valore
                    foreach (var item in elencoValori)
                    {
                        valori = qDecodeIN(item);
                        //DateTime.TryParse(valori[0], out dtEve);
                        CultureInfo provider = CultureInfo.InvariantCulture;
                        DateTime.TryParseExact(valori[0], "yyyyMMddHHmmssfff", provider, DateTimeStyles.AssumeLocal, out dtEve);
                        int.TryParse(valori[3], out counter);
                        currData = new flogData()
                        {
                            flux = valori[1],
                            valore = valori[2],
                            dtEve = dtEve,
                            dtCurr = DateTime.Now,
                            cnt = counter
                        };
                        fullObj.fluxData.Add(currData);
                    }
                    // conversione finale
                    try
                    {
                        answ = JsonConvert.SerializeObject(fullObj);
                    }
                    catch (Exception exc)
                    {
                        lgError($"Errore in costruzione jsonPayload:{Environment.NewLine}{exc}");
                    }
                }
                else
                {
                    evData currData = new evData();
                    evJsonPayload fullObj = new evJsonPayload();
                    fullObj.eventList = new List<evData>();
                    string[] valori;
                    int counter = 0;
                    DateTime dtEve = DateTime.Now;
                    // inizio processando ogni valore
                    foreach (var item in elencoValori)
                    {
                        valori = qDecodeIN(item);
                        //DateTime.TryParse(valori[0], out dtEve);
                        CultureInfo provider = CultureInfo.InvariantCulture;
                        DateTime.TryParseExact(valori[0], "yyyyMMddHHmmssfff", provider, DateTimeStyles.AssumeLocal, out dtEve);
                        int.TryParse(valori[2], out counter);
                        currData = new evData()
                        {
                            valore = valori[1],
                            dtEve = dtEve,
                            dtCurr = DateTime.Now,
                            cnt = counter
                        };
                        fullObj.eventList.Add(currData);
                    }
                    // conversione finale
                    try
                    {
                        answ = JsonConvert.SerializeObject(fullObj);
                    }
                    catch (Exception exc)
                    {
                        lgError($"Errore in costruzione jsonPayload:{Environment.NewLine}{exc}");
                    }
                }
            }
            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 == cIobConf.codIOB)
                    {
                        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, cIobConf.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, cIobConf.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 ODL automatica
        /// </summary>
        public void processAutoOdl()
        {
            lgInfo("Richiesta processAutoOdl");
            bool fatto = false;
            if (!string.IsNullOrEmpty(getOptPar("AUTO_CHANGE_ODL")))
            {
                string IOB_MULTI_CNAME = "";
                string[] elencoMulti = null;
                string fullUrl = "";
                if (isMulti)
                {
                    // devo chiamare cambio ODL x OGNI tavola: mi servono i parametri opzionali...
                    IOB_MULTI_CNAME = getOptPar("IOB_MULTI_CNAME");
                    elencoMulti = IOB_MULTI_CNAME.Split(',');
                }
                // controllo SIA abilitato...
                bool doProc = false;
                DateTime adesso = DateTime.Now;
                bool.TryParse(getOptPar("AUTO_CHANGE_ODL"), out doProc);
                if (doProc)
                {
                    lgInfo("AUTO_CHANGE_ODL abilitato");
                    bool callChangeODL = false;
                    // modalità change ODL
                    string CHANGE_ODL_MODE = "TIME";
                    if (!string.IsNullOrEmpty(getOptPar("CHANGE_ODL_MODE")))
                    {
                        CHANGE_ODL_MODE = getOptPar("CHANGE_ODL_MODE");
                    }
                    if (CHANGE_ODL_MODE == "PZCOUNT_RESET")
                    {
                        /* verifico se sia "armato" il reset cambio ODL, DEVO essere :
                         * - NON in produzione
                         * - contapezzi ACT < contapezzi LAST
                         * */
                        lgInfo("processAutoOdl: caso PZCOUNT_RESET");
                        if (!isRunning && pzCountResetted)
                        {
                            callChangeODL = true;
                            lgInfo("Attivato cambio ODL da PZCOUNT_RESET");
                        }
                        else
                        {
                            lgInfo($"isRunning: {isRunning} | pzCountResetted: {pzCountResetted} | contapezziIOB: {contapezziIOB} | contapezziPLC: {contapezziPLC}");
                        }
                    }
                    else if (CHANGE_ODL_MODE == "TIME")
                    {
                        // carico i parametri di configurazione x reset ODL...
                        string CHANGE_ODL_HOURS = getOptPar("CHANGE_ODL_HOURS");
                        string CHANGE_ODL_IDLE_MIN = getOptPar("CHANGE_ODL_IDLE_MIN");
                        if (!string.IsNullOrEmpty(CHANGE_ODL_HOURS) && !string.IsNullOrEmpty(CHANGE_ODL_IDLE_MIN))
                        {
                            int minOdlDurHours = -1;
                            int minPlcIdelMin = -1;
                            int.TryParse(CHANGE_ODL_HOURS, out minOdlDurHours);
                            int.TryParse(CHANGE_ODL_IDLE_MIN, out minPlcIdelMin);
                            // controllo parametri validi
                            if (minOdlDurHours > 0 && minPlcIdelMin >= 0)
                            {
                                // leggo da server inizio ODL... se non multi 1 solo...
                                DateTime inizioOdl = DateTime.Now;
                                string rawDataInizio = "";
                                if (!isMulti)
                                {
                                    rawDataInizio = callUrl(urlInizioOdlIob, false);
                                    DateTime.TryParse(rawDataInizio, out inizioOdl);
                                }
                                else
                                {
                                    DateTime tmpData = DateTime.Now;
                                    // prendo il + vecchio...
                                    foreach (var item in elencoMulti)
                                    {
                                        fullUrl = $"{urlInizioOdlIob}|{item}";
                                        rawDataInizio = callUrl(fullUrl, false);
                                        DateTime.TryParse(rawDataInizio, out tmpData);
                                        inizioOdl = (tmpData < inizioOdl) ? tmpData : inizioOdl;
                                    }
                                }
                                // verifico se sia scaduto...
                                if (inizioOdl.AddHours(minOdlDurHours) < adesso)
                                {
                                    string rawIdle = "";
                                    int idlePeriod = 0;
                                    if (!isMulti)
                                    {
                                        // controllo SE sono fermo (spento o in manuale) per il periodo minimo richiesto...
                                        rawIdle = callUrl(urlIdleTime, false);
                                        int.TryParse(rawIdle, out idlePeriod);
                                    }
                                    else
                                    {
                                        int tmpIdle = 0;
                                        // prendo il + grande...
                                        foreach (var item in elencoMulti)
                                        {
                                            fullUrl = $"{urlIdleTime}|{item}";
                                            rawIdle = callUrl(fullUrl, false);
                                            int.TryParse(rawIdle, out tmpIdle);
                                            idlePeriod = tmpIdle > idlePeriod ? tmpIdle : idlePeriod;
                                        }
                                    }
                                    if (idlePeriod >= minPlcIdelMin)
                                    {
                                        callChangeODL = true;
                                    }
                                }
                            }
                        }
                    }
                    if (callChangeODL)
                    {
                        lgInfo("Chiamata: processAutoOdl --> forceSplitODL");
                        fatto = forceSplitOdl();
                        pzCountResetted = false;
                        lgInfo("Esecuzione processAutoOdl completata --> pzCountResetted = false");
                    }
                }
                else
                {
                    lgInfo("AUTO_CHANGE_ODL DISABILITATO");
                }
            }
            // loggo se fatto
            if (fatto)
            {
                lgInfo("Effettuato processAutoOdl");
            }
        }

        /// <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
                    {
                        if (lastAlarm != currAlarms["CNC_ALARM"])
                        {
                            // salvo!
                            lastAlarm = currAlarms["CNC_ALARM"];
                            // per ogni valore del dizionario mostro ed accodo!
                            string sVal = "";
                            foreach (var item in currAlarms)
                            {
                                sVal = string.Format("[CNC_ALARM]{0}|{1}", item.Key, item.Value);
                                // chiamo accodamento...
                                accodaFLog(sVal, qEncodeFLog(item.Key, item.Value));
                            }
                        }
                    }
                    catch (Exception exc)
                    {
                        lgError("Eccezione in processCncAlarms{0}{1}", Environment.NewLine, exc);
                    }
                }
            }
        }

        /// <summary>
        /// Effettua processing contapezzi (ed eventualmente alza il bit di contapezzo...)
        /// </summary>
        public virtual void processContapezzi()
        { }

        /// <summary>
        /// Effettua processing del recupero delle speed (RPM, feedrate) degli assi
        /// </summary>
        public void processDynData()
        {
            bool enableByApp = utils.CRB("enableDynData");
            bool enableByIob = (getOptPar("ENABLE_DYN_DATA") == "TRUE");
            bool forceSendByIob = (getOptPar("FORCE_DYN_DATA") == "TRUE");
            Dictionary<string, string> currDynData = new Dictionary<string, string>();

            if (enableByApp || enableByIob)
            {
                lgInfo("Inizio processDynData");
                if (connectionOk)
                {
                    currDynData = getDynData();
                }
                else
                {
                    lgError("Errore connessione mancante x getDynData");
                }
                try
                {
                    string sVal = "";
                    // se richiesto send diretto...
                    if (forceSendByIob)
                    {
                        // per ogni valore del dizionario mostro ed accodo!
                        foreach (var item in currDynData)
                        {
                            sVal = string.Format("[DYNDATA]{0}|{1}", item.Key, item.Value);
                            // chiamo accodamento...
                            accodaFLog(sVal, qEncodeFLog(item.Key, item.Value));
                        }
                    }
                    // altrimenti verifico SE sia cambiato il valore dei DynData...
                    else if (lastDynDataCtrlVal != currDynData["DYNDATA"])
                    {
                        // salvo!
                        lastDynDataCtrlVal = currDynData["DYNDATA"];
                        // per ogni valore del dizionario mostro ed accodo!
                        foreach (var item in currDynData)
                        {
                            sVal = string.Format("[DYNDATA]{0}|{1}", item.Key, item.Value);
                            // chiamo accodamento...
                            accodaFLog(sVal, qEncodeFLog(item.Key, item.Value));
                        }
                    }
                    // salvo array...
                    lastDynData = currDynData;
                }
                catch (Exception exc)
                {
                    lgError(exc, "Eccezione in processDynData");
                }
            }
        }

        /// <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
            if (DateTime.Now.Subtract(lastDisconnCheck).TotalSeconds > utils.CRI("disconMaxSec"))
            {
                // resetto tutti i vlaori BYTE IN/PREV/OUT... così invio macchina spenta...
                B_input = 0;
                B_output = 0;
                B_previous = 0;
                accodaSigIN(ref currDispData);
                // update controllo
                lastDisconnCheck = DateTime.Now;
            }
            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");
            bool enableByIob = (getOptPar("ENABLE_OVERRIDES") == "TRUE");
            Dictionary<string, string> currOverride = new Dictionary<string, string>();
            if (enableByApp || enableByIob)
            {
                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)
                            {
                                sVal = string.Format("[OVERRIDES]{0}|{1}", item.Key, item.Value);
                                // chiamo accodamento...
                                accodaFLog(sVal, qEncodeFLog(item.Key, item.Value));
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Effettua ciclo controllo richieste server
        /// </summary>
        public void processServerRequests()
        {
            Dictionary<string, string> task2exe = new Dictionary<string, string>();
            Dictionary<string, string> taskDone = new Dictionary<string, string>();
            // recupero elenco delle cose da fare
            string resp = getTask2exe();
            if (!string.IsNullOrEmpty(resp))
            {
                try
                {
                    task2exe = JsonConvert.DeserializeObject<Dictionary<string, string>>(resp);
                    // se ho da fare chiamo esecuzione..
                    if (task2exe.Count > 0)
                    {
                        taskDone = processTask(task2exe);
                    }
                }
                catch (Exception exc)
                {
                    lgError($"Eccezione in processServerRequests:{Environment.NewLine}{exc}");
                }
            }
        }

        public Dictionary<string, string> processTask(Dictionary<string, string> task2exe)
        {
            Dictionary<string, string> taskDone = new Dictionary<string, string>();
            if (task2exe != null)
            {
                lgInfo($"Task2Exe: trovati {task2exe.Count} task da eseguire, procedo");
                // chiamo procedura esecutiva (diversa x ogni IOB)
                taskDone = executeTasks(task2exe);
                // loggo tutti i task done...
                foreach (var item in taskDone)
                {
                    sendToTaskWatch(item.Key, item.Value);
                }
                // ora chiamo la cancellazione dei task eseguiti...
                foreach (var item in taskDone)
                {
                    remTask2exe(item.Key, item.Value);
                }
            }
            return taskDone;
        }

        /// <summary>
        /// Classe fittizia in caso di processing task in VHF
        /// </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)
        {
            // legge da IO server ULTIMO valore CONTPEZZI al riavvio...
            string currServerCount = "";
            string lastIdxODL = "";
            if (checkServerAlive)
            {
                // leggo PRIMA ODL ....
                lastIdxODL = utils.callUrl(urlGetCurrODL);
                lgInfo("Lettura ODL dall'url {0} --> {1}", urlGetCurrODL, lastIdxODL);
                // se ho valori in coda da trasmettere uso dati REDIS
                if (forceCountRec)
                {
                    // uso dati da TCiclo registrati...
                    currServerCount = utils.callUrl(urlGetPzCountRec);
                    lgInfo("Lettura contapezzi da TCiclo dall'url {0} --> num pz: {1}", urlGetPzCountRec, currServerCount);
                }
                else
                {
                    // uso il contapezzi dichiarato dall'IOB stesso
                    currServerCount = utils.callUrl(urlGetPzCount);
                    lgInfo("Lettura contapezzi dall'url {0}", urlGetPzCount);
                }
                // controllo: SE NON HO ODL...
                if (string.IsNullOrEmpty(lastIdxODL) || lastIdxODL == "0")
                {
                    // NON AGGIORNO
                    contapezziIOB = contapezziPLC;
                    lgInfo($"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;
                            lgInfo($"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);
                    }
                }
            }
            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#flux#value#cont
        /// <paramref name="flusso">Flusso dati</paramref>
        /// <paramref name="valore">Valore da salvare</paramref>
        /// </summary>
        public string qEncodeFLog(string flusso, string valore)
        {
            string answ = "";
            try
            {
                answ = string.Format("{0:yyyyMMddHHmmssfff}#{1}#{2}#{3}", DateTime.Now, flusso, valore, counterFLog);
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Fornisce il valore di flusso e valore in formato valido x messa in coda nel formato dtEve#flux#value#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 = string.Format("{0:yyyyMMddHHmmssfff}#{1}#{2}#{3}", eventDT, flusso, valore, counterFLog);
            }
            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)
                {
                    readSemafori(ref currDispData);
                    lastReadPLC = DateTime.Now;
                }
                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
        /// </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 Memory -----------{Environment.NewLine}");
                int i = 0;
                foreach (var item in RawInput)
                {
                    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()
        {
            return false;
        }

        /// <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>
        /// Salva valori indicati in prod data
        /// </summary>
        /// <param name="item">Item KVP di cui salvare i dati in currProdData come chiave/valore</param>
        /// <returns></returns>
        public void saveProdData(KeyValuePair<string, string> item)
        {
            // imposto i valori...
            if (currProdData.ContainsKey(item.Key))
            {
                currProdData[item.Key] = item.Value;
            }
            else
            {
                currProdData.Add(item.Key, item.Value);
            }
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="outVal"></param>
        /// <param name="valore"></param>
        /// <param name="chiave"></param>
        /// <returns></returns>
        public void saveValue(ref Dictionary<string, string> outVal, double valore, string chiave)
        {
            //check obj preliminare
            if (outVal == null)
            {
                outVal = new Dictionary<string, string>();
            }
            bool scaduto = stackVal_TSVC(chiave, valore);
            // recupero VC
            valore = getVal_TSVC(chiave, scaduto);
            if (scaduto)
            {
                outVal.Add(chiave, $"{valore}");
            }
            LastTSVC[chiave] = valore;
        }

        /// <summary>
        /// Invia una LISTA di valori
        /// </summary>
        /// <param name="tipoUrl"></param>
        /// <param name="queueVal"></param>
        public void sendDataBlock(urlType tipoUrl, List<string> listQueueVal)
        {
            // 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 (checkServerAlive)
                        {
                            // chiamo URL!
                            string answ = callUrlWithPayload(lastUrl, payload, doAsync);
                            // loggo!
                            lgInfo($"[SEND payload] TipoURL: {tipoUrl} | {listQueueVal.Count} records --> {answ}");
                            // se "OK" verde, altrimenti errore --> ROSSO
                            if (answ.Contains("OK"))
                            {
                                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);
        }

        /// <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 void sendToMoonPro(urlType tipoUrl, string queueVal)
        {
            // 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 (checkServerAlive)
                    {
                        // chiamo URL!
                        string answ = callUrl(lastUrl, false);
                        // loggo!
                        lgInfo(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 timeaout ({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
                    {
                        lgInfo(string.Format("[SERVER KO] {0}", queueVal));
                    }
                }
                else
                {
                    currDispData.semOut = Semaforo.SV;
                    // loggo!
                    lgInfo(string.Format("{0} -> [SIM]", queueVal));
                }
                nSendOut++;
                // riporto cosa inviato
#if false
        displayOutData();
#endif
                currDispData.newUrlCallData = lastUrl;
                // aggiorno data ultimo watchdog...
                lastWatchDog = DateTime.Now;
            }
            catch
            {
                currDispData.semOut = Semaforo.SR;
            }
            raiseRefresh(currDispData);
        }

        /// <summary>
        /// Metodo generico di IMPOSTAZIONE FORZATA del contapezzi...
        /// </summary>
        /// <param name="newPzCount">Pezzi richiesti</param>
        /// <returns></returns>
        public virtual bool setcontapezziPLC(int newPzCount)
        {
            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)
        {
            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) < DateTime.Now)
                {
                    answ = true;
                }
            }
            return answ;
        }

        /// <summary>
        /// Avvia l'adapter sulla porta richiesta
        /// </summary>
        /// <param name="resetQueue">indica se sia richeisto di SVUOTARE le code delel info</param>
        public virtual void startAdapter(bool resetQueue)
        {
            lgInfo("Starting adapter...");
            maxJsonData = utils.CRI("maxJsonData");
            maxJsonDataEv = utils.CRI("maxJsonDataEv");
            parentForm.commPlcActive = false;
            adpRunning = true;
            dtAvvioAdp = DateTime.Now;
            lastWatchDog = dtAvvioAdp;
            lastPING = dtAvvioAdp;
            lastReadPLC = dtAvvioAdp.AddMinutes(-1);
            lastDisconnCheck = dtAvvioAdp;
            TimingData.resetData();
            // aggiungo altri defaults
            setDefaults(resetQueue);
            adpTryRestart = true;
            parentForm.displayTaskAndLog("Adapter Started!");
        }

        /// <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 richeisto di SVUOTARE le code delel info</param>
        public virtual void stopAdapter(bool tryRestart, bool forceDequeue)
        {
            if (forceDequeue)
            {
                // svuoto le code dei valori letti e non ancora trasmessi...
                parentForm.displayTaskAndLog("[CLOSING] Svuotamento FORZATO coda segnali...");
                while (QueueIN.Count > 0)
                {
                    // INVIO COMUNQUE...!!!
                    string valore = "";
                    QueueIN.TryDequeue(out valore);
                    sendToMoonPro(urlType.SignIN, valore);
                }
                parentForm.displayTaskAndLog("[CLOSING] Svuotamento FORZATO coda FluxLOG...");
                // 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);
                            }
                            sendDataBlock(urlType.FLog, listaValori);
                        }
                        else
                        {
                            // invio in blocco
                            listaValori = QueueFLog.ToList();
                            // invio
                            sendDataBlock(urlType.FLog, listaValori);
                            // svuoto!
                            QueueFLog = new ConcurrentQueue<string>();
                        }
                    }
                    // HO FINITO invio di FLog...
                }
                else
                {
                    string currVal = "";
                    while (QueueFLog.Count > 0)
                    {
                        // INVIO COMUNQUE...!!!
                        QueueFLog.TryDequeue(out currVal);
                        sendToMoonPro(urlType.FLog, currVal);
                    }
                }
            }
            parentForm.displayTaskAndLog("[STOP] Stopping adapter...");
            adpTryRestart = false;

            parentForm.displayTaskAndLog("[STOP] Stopping adapter - last periodic data read...");

            // chiudo la connessione all'adapter...
            tryDisconnect();
            dtStopAdp = DateTime.Now;
            adpTryRestart = tryRestart;
            adpRunning = false;
            // chiudo!
            parentForm.displayTaskAndLog("Adapter Stopped.");
            parentForm.commPlcActive = false;
        }

        /// <summary>
        /// Processo la coda SignalIN...
        /// </summary>
        public void svuotaCodaSignIN()
        {
            // 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);
                                        }
                                        sendDataBlock(urlType.SignIN, listaValori);
                                    }
                                    else
                                    {
                                        // invio in blocco
                                        listaValori = QueueIN.ToList();
                                        // invio
                                        sendDataBlock(urlType.SignIN, listaValori);
                                        // svuoto!
                                        QueueIN = new ConcurrentQueue<string>();
                                    }
                                }
                                else
                                {
                                    // INVIO SINGOLO...!!!
                                    QueueIN.TryDequeue(out currVal);
                                    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;
        }

        /// <summary>
        /// Metodo base disconnessione...
        /// </summary>
        public virtual void tryDisconnect()
        {
        }

        /// <summary>
        /// Effettua verifica se abilitato invio pezzi in blocco e nel caso
        /// - invio in blocco pezzi
        /// - aggiornamento del contapezzi (passato come ref) x nuovo valore post invio
        /// </summary>
        public void trySendPzCountBlock()
        {
            // in primis HA SENSO procedere SOLO SE server MP è Online...
            if (MPOnline)
            {
                // SOLO SE online la macchina...
                if (IobOnline)
                {
                    int numIncr = 0;
                    int qtyAdded = 0;
                    // verifico se la funzione SIA abilitata
                    if (enableSendPzCountBlock)
                    {
                        int delta = contapezziPLC - contapezziIOB;
                        // se è abilitata verifico differenza: se ho DELTA > minSendPzCountBlock --> invio un blocco <= maxSendPzCountBlock
                        if (delta > minSendPzCountBlock)
                        {
                            // init obj display
                            newDisplayData currDispData = new newDisplayData();
                            // resta indietro di ALMENO minSendPzCountBlock pezzi x recuperare 1:1...
                            numIncr = delta > maxSendPzCountBlock + minSendPzCountBlock ? maxSendPzCountBlock : delta - minSendPzCountBlock;
                            // invio il num max di pezzi ammesso in blocco!
                            lastUrl = $"{urlAddPzCount}{numIncr}";
                            string resp = utils.callUrlNow(lastUrl);
                            if (!string.IsNullOrEmpty(resp))
                            {
                                // dalla risposta (come numero) capisco SE ha aggiunto i pezzi (e quanti)
                                int.TryParse(resp, out qtyAdded);
                                if (qtyAdded > 0)
                                {
                                    // aggiorno IL MIO contapezzi...
                                    contapezziIOB += qtyAdded;
                                    lgInfo($"Inviato incremento contapezzi: send: {numIncr} | resp: {qtyAdded} | contapezziIOB: {contapezziIOB}");
                                    // invio conferma contapezzi..
                                    string retVal = utils.callUrl($"{urlSetPzCount}{contapezziIOB}");
                                    // verifica se tutto OK
                                    if (retVal != contapezziIOB.ToString())
                                    {
                                        // errore salvataggio contapezzi
                                        lgInfo($"trySendPzCountBlock: errore salvataggio contapezzi: contapezziIOB {contapezziIOB} | risposta: {retVal}");
                                    }
                                }
                                else
                                {
                                    lgError($"Richiesto incremento {numIncr} ma NON registrato su server MP-IO");
                                }
                            }
                            currDispData.newUrlCallData = lastUrl;
                            currDispData.counter = contapezziIOB;
                            currDispData.semOut = Semaforo.SV;
                            raiseRefresh(currDispData);
                        }
                    }
                }
                else
                {
                    lgInfo("Impossibile trySendPzCountBlock: IobOnline è false");
                }
            }
            else
            {
                lgInfo("Impossibile trySendPzCountBlock: MPOnline è false");
            }
        }

        /// <summary>
        /// Formatta URL x invio in DataBlock Json dei dati FLog / eventi
        /// </summary>
        /// <param name="tipoUrl"></param>
        /// <returns></returns>
        public string urlDataBlock(urlType tipoUrl)
        {
            // verifico la parte di link "tipoComando"
            string tipoComando = tipoUrl == urlType.FLog ? cIobConf.serverData.CMDFLOG_JSON : cIobConf.serverData.CMDBASE_JSON;
            // URL base x input
            string answ = string.Format(@"http://{0}{1}{2}{3}", cIobConf.serverData.MPIP, cIobConf.serverData.MPURL, tipoComando, cIobConf.codIOB);
            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 = string.Format(@"http://{0}{1}{2}", cIobConf.serverData.MPIP, cIobConf.serverData.MPURL, cIobConf.serverData.CMDFLOG);
            // decodifica valore!
            string[] valori = qDecodeIN(queueVal);
            // aggiungo macchina e valore...
            answ += string.Format(@"{0}?flux={1}&&valore={2}", cIobConf.codIOB, valori[1], valori[2]);
            // aggiondo dataOra evento e corrente + contatore...
            answ += string.Format(@"&&dtEve={0}&&dtCurr={1:yyyyMMddHHmmssfff}&&cnt={2}", valori[0], DateTime.Now, valori[3]);
            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 = string.Format(@"http://{0}{1}{2}", cIobConf.serverData.MPIP, cIobConf.serverData.MPURL, cIobConf.serverData.CMDBASE);
            // decodifica valore!
            string[] valori = qDecodeIN(queueVal);
            // aggiungo macchina e valore...
            answ += string.Format(@"{0}?valore={1}", cIobConf.codIOB, valori[1]);
            // aggiondo dataOra evento e corrente + contatore...
            answ += string.Format(@"&&dtEve={0}&&dtCurr={1:yyyyMMddHHmmssfff}&&cnt={2}", valori[0], DateTime.Now, valori[2]);
            return answ;
        }

        #endregion Public Methods
    }

    /// <summary>
    /// Evento per incapsulare dati x refresh pagina
    /// </summary>
    public class iobRefreshedEventArgs : EventArgs
    {
        #region Private Fields

        /// <summary>
        /// classe obj privata
        /// </summary>
        private readonly newDisplayData _newDisplayData;

        #endregion Private Fields

        #region Public Constructors

        /// <summary>
        /// salvataggio obj
        /// </summary>
        /// <param name="newObject"></param>
        public iobRefreshedEventArgs(newDisplayData newObject)
        {
            _newDisplayData = newObject;
        }

        #endregion Public Constructors

        #region Public Properties

        /// <summary>
        /// Proprietà lettura displayData aggiornato
        /// </summary>
        public newDisplayData DisplayDataObject
        {
            get { return _newDisplayData; }
        }

        #endregion Public Properties
    }
}