using EasyModbus;
using IOB_UT_NEXT;
using IOB_UT_NEXT.Config;
using MapoSDK;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Threading;
using System.Windows.Forms;

namespace IOB_WIN_MBUS.IobModbusTCP
{
    /* --------------------------------------------------------------------------------
    *  Controlli ModBusTCP (primo: COMECA)
    *  - protocollo ModBus TCP
    *
    * -------------------------------------------------------------------------------- */

    public class ModbusTCP : Iob.GenericNext
    {
        #region Public Constructors

        /// <summary>
        /// Classe base con i metodi x ModBusTCP
        /// </summary>
        /// <param name="caller">Form chiamante</param>
        /// <param name="IOBConf">Configurazione (legacy)</param>
        /// <param name="IobConfFull">Configurazione (v 4.x)</param>
        public ModbusTCP(AdapterFormNext caller, IobConfTree IobConfFull) : base(caller, IobConfFull)
        {
            lgInfo("NEW IOB ModBus TCP");

            DateTime adesso = DateTime.Now;
            memMap = new plcMemMapExt();
            if (IOBConfFull != null)
            {
                // gestione invio ritardato contapezzi

                lastPzCountSend = adesso;
                lastWarnODL = adesso;
                // inizializzo parametri...
                parametri = new ModBusTcpParamConf();
#if false
                {
                    ipAdrr = "127.0.0.1",
                    port = 502,
                    pingMsTimeout = IOBConfFull.Device.Connect.PingMsTimeout,
                    holdRegBaseAddr = 40001,
                    memAddrRead = "40001",
                    memAddrWrite = "41001",
                    memSizeRead = 0,
                    memSizeWrite = 0
                        }; 
#endif
                setParamPlc();
                //
                string sMinWait = getOptPar("minWait");
                if (!string.IsNullOrEmpty(sMinWait))
                {
                    int.TryParse(sMinWait, out minWait);
                }
                string sMaxWait = getOptPar("maxWait");
                if (!string.IsNullOrEmpty(sMaxWait))
                {
                    int.TryParse(sMaxWait, out maxWait);
                }

                // salvo info su conf IOB...
                string iobConfSer = "";
                try
                {
                    iobConfSer = JsonConvert.SerializeObject(IOBConfFull, Formatting.Indented);
                }
                catch
                { }
                // finito!
                lgInfoStartup($"Init IOB, con {iobConfSer}");
            }
            else
            {
                lgError("Impossibile avviare, IOBConf nullo/non valido!");
            }
        }
        /// <summary>
        /// Min attesa chiamate consecutive ModBus
        /// </summary>
        private int minWait = 40;
        /// <summary>
        /// Max attesa chiamate consecutive ModBus
        /// </summary>
        private int maxWait = 60;
        #endregion Public Constructors

        #region Public Methods

        /// <summary>
        /// Metodo dispose x il currPLC contenuto
        /// </summary>
        public void Dispose()
        {
            currPLC.Disconnect();
        }

        /// <summary>
        /// Processo i task richiesti e li elimino dalla coda 1:1
        /// </summary>
        /// <param name="task2exe"></param>
        public override Dictionary<string, string> executeTasks(Dictionary<string, string> task2exe, string codTav)
        {
            lgInfo($"Chiamata executeTasks specifica ModBus TCP: {task2exe.Count} task ricevuti");
            // Verificare il protocollo: dovrebeb togliere SOLO i task eseguiti...
            Dictionary<string, string> taskDone = new Dictionary<string, string>();
            string taskVal = "";
            if (task2exe != null)
            {
                // cerco task specifici
                foreach (var item in task2exe)
                {
                    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.forceResetPzCount:
                        case taskType.forceSetPzCount:
                        case taskType.sendWatchDogMes2Plc:
                        case taskType.startSetup:
                        case taskType.stopSetup:
                            taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | SKIPPED | NO EXEC";
                            break;

                        case taskType.setProg:
                        case taskType.setPzComm:
                        case taskType.setArt:
                        case taskType.setComm:
                            // cerco se ho configurato parametri x invio...
                            if (memMap.mMapWrite.ContainsKey(item.Key))
                            {
                                memMap.mMapWrite[item.Key].value = item.Value;
                                // preparo obj oparametro da scrivere...
                                List<objItem> updatedPar = new List<objItem>();
                                objItem newParam = new objItem()
                                {
                                    uid = item.Key,
                                    name = item.Key,
                                    value = item.Value,
                                    reqValue = "",
                                    writable = true,
                                    lastRequest = DateTime.Now,
                                    displOrdinal = memMap.mMapWrite[item.Key].displOrdinal
                                };
                                updatedPar.Add(newParam);
                                // richiamo scrittura parametri su PLC
                                plcWriteParams(ref updatedPar);
                                taskVal = $"REQUEST SET val: {item.Key} --> {item.Value}";
                            }
                            else
                            {
                                taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | NOT FOUND IN mMapWrite List | NO EXEC";
                            }
                            break;

                        case taskType.setParameter:
                            // richiedo da URL i parametri WRITE da popolare
                            lgDebug("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;

                        case taskType.processOtherInfo:
                            bool okProc = ProcessOtherInfo(item.Key, item.Value);
                            taskVal = okProc ? $"OK ProcessOtherInfoAsync | {item.Key} | {item.Value}" : $"ERROR ProcessOtherInfoAsync | {item.Key} | {item.Value}";
                            break;

                        case taskType.syncDbData:
                            ProcessDataSync();
                            break;

                        default:
                            taskVal = "SKIPPED | NO EXEC";
                            break;
                    }
                    // aggiungo task!
                    taskDone.Add(item.Key, taskVal);
                }
            }
            return taskDone;
        }

        /// <summary>
        /// Decodifica tipo indirizzo dal codice vedere ad esempio https://www.fernhillsoftware.com/help/drivers/modbus/data-address-format.html
        /// </summary>
        /// <param name="memAddr"></param>
        /// <returns></returns>
        public modBusAddrType getAddrType(string memAddr)
        {
            modBusAddrType answ = modBusAddrType.Coil;
            // leggo prima cifra...
            answ = (modBusAddrType)Enum.Parse(typeof(modBusAddrType), memAddr.Substring(0, 1));
            return answ;
        }

        /// <summary>
        /// Recupero dati dinamici... ATTENZIONE factor usato come FONDOSCALA
        ///
        /// Esempio:
        /// - RealVal : 0 - 28000
        /// - ReadVal : 0 - 100
        /// - Factor : 28000 / 100 = 280
        /// </summary>
        public override Dictionary<string, string> getDynData()
        {
            Random rnd = new Random();
            
            // valore non presente in vers default... se gestito fare override
            Dictionary<string, string> outVal = new Dictionary<string, string>();
            if (utils.CRB("enableTSVC"))
            {
                // processing SOLO SE ho in memoria abbastanza dati...
                if (RawInput.Length < parametri.memSizeRead)
                {
                    lgError($"Impossibile processare getDynData x ModBus TCP PLC, vettore memoria troppo piccolo: {RawInput.Length} byte / {parametri.memSizeRead} byte presenti/richiesti)");
                }
                else
                {
                    Dictionary<string, dataConfTSVC> readErrorList = new Dictionary<string, dataConfTSVC>();
                    Dictionary<string, dataConfTSVC> readErrorListRepeat = new Dictionary<string, dataConfTSVC>();
                    bool useLUT = memSetR != null && memSetR.Count > 0;
                    // se configurato leggo IN BLOCCHI da memoria...
                    if (useLUT)
                    {
                        foreach (var item in memSetR)
                        {
                            // verifico il tipo di dati da leggere e salvare... FIXME TODO FARE !!!
                            readMemoryBlock(item.Key, item.Value);
                            int waitTime = rnd.Next(minWait, maxWait);
                            Thread.Sleep(waitTime);
                        }
                    }
                    // procedo ...
                    try
                    {
                        // processo x ogni valore configurato...
                        if (memMap.mMapRead.Count > 0)
                        {
                            var memItemList = memMap.mMapRead;

                            readErrorList = getDataDictionary(memItemList, useLUT, ref outVal);
                            // se qualcosa è andato storto riprovo a caricare SOLO gli errori... 1
                            // sola volta
                            if (readErrorList.Count > 0)
                            {
                                lgDebug($"Attesa prima di rilettura | LUT: {useLUT}");
                                // attendo 3 sec
                                Thread.Sleep(3000);
                                lgDebug($"Effettuo rilettura per {readErrorList.Count} variabili | LUT: {useLUT}");
                                readErrorListRepeat = getDataDictionary(readErrorList, useLUT, ref outVal);
                            }
                            // se avessi ancora errori --> disconnetto
                            if (readErrorListRepeat.Count > maxErrorRead)
                            {
                                lgError($"Trovati valori non validi al secondo tentativo | LUT: {useLUT} --> invalido valori letti e resetto adapter con tryDisconnect!");
                                tryDisconnect();
                                // invalido output
                                outVal = new Dictionary<string, string>();
                                tryConnect();
                            }
                            else
                            {
                                lastReadPLC = DateTime.Now;
                            }
                        }
                        else
                        {
                            lgDebug($"getDynData: {memMap.mMapRead.Count} record in mMapRead | LUT: {useLUT}");
                        }
                    }
                    catch (Exception exc)
                    {
                        lgError(exc, $"Errore in getDynData x ModBus TCP PLC --> ciclo disconnect/reconnect | LUT: {useLUT}{Environment.NewLine}{exc}");
                        tryDisconnect();
                        outVal = new Dictionary<string, string>();
                        tryConnect();
                    }
                }
            }
            else
            {
                lgDebug($"Non processo getDynData: enableTSVC = false");
            }
            if (periodicLog || outVal.Count > 0)
            {
                lgDebug($"Esito getDynData: {outVal.Count} valori VALIDI in outVal");
            }
            return outVal;
        }

        /// <summary>
        /// Gestione contapezzi
        /// </summary>
        public override void processContapezzi()
        {
            if (enablePzCountByApp)
            {
                if (IOBConfFull.OptPar.Count > 0 && !string.IsNullOrWhiteSpace(IOBConfFull.Device.PzCountMode))
                {
                    // cerco in lastDynData...
                    string pzCountKey = IOBConfFull.Device.PzCountMode.Replace("STD.", "");
                    if (lastDynData.ContainsKey(pzCountKey))
                    {
                        string rawVal = lastDynData[pzCountKey];
                        double cntDouble = -1;
                        if (!string.IsNullOrEmpty(rawVal))
                        {
                            //double.TryParse(rawVal, out cntDouble);
                            bool success = double.TryParse(rawVal, NumberStyles.Any, CultureInfo.InvariantCulture, out cntDouble);
                            if (success)
                            {
                                contapezziPLC = (int)cntDouble;
                            }
                        }
                    }
                }
            }
            else
            {
                lgTrace($"processContapezzi escluso: enablePzCountByApp = {enablePzCountByApp} | disablePzCountByIob: {disablePzCountByIob}");
            }
        }

        /// <summary>
        /// Lettura valori Coils (1...)
        /// </summary>
        /// <param name="startAddr"></param>
        /// <param name="qty"></param>
        /// <returns></returns>
        public bool[] readCoil(int startAddr, int qty)
        {
            bool[] answ = new bool[1];
            try
            {
                if (currPLC.Connected)
                {
                    answ = currPLC.ReadCoils(startAddr, qty);
                    trackReadData(qty / 8, sw.Elapsed.TotalMilliseconds / 1000);
                }
            }
            catch (Exception exc)
            {
                lgError($"Errore in readCoil{Environment.NewLine}{exc}");
            }
            return answ;
        }

        /// <summary>
        /// Lettura valori DiscreteInputs (2...)
        /// </summary>
        /// <param name="startAddr"></param>
        /// <param name="qty"></param>
        /// <returns></returns>
        public bool[] readDiscrInputs(int startAddr, int qty)
        {
            bool[] answ = new bool[1];
            try
            {
                if (currPLC.Connected)
                {
                    answ = currPLC.ReadDiscreteInputs(startAddr, qty);
                    trackReadData(qty / 8, sw.Elapsed.TotalMilliseconds / 1000);
                }
            }
            catch (Exception exc)
            {
                lgError($"Errore in readDiscrInputs{Environment.NewLine}{exc}");
            }
            return answ;
        }

        /// <summary>
        /// Lettura valori Holding Register (3...)
        /// </summary>
        /// <param name="startAddr"></param>
        /// <param name="qty"></param>
        /// <returns></returns>
        public int[] readHoldReg(int startAddr, int qty)
        {
            int[] answ = new int[2];
            try
            {
                if (currPLC.Connected)
                {
                    answ = currPLC.ReadHoldingRegisters(startAddr, qty);
                    trackReadData(qty * 2, sw.Elapsed.TotalMilliseconds / 1000);
                }
            }
            catch (Exception exc)
            {
                lgError($"Errore in readHoldReg{Environment.NewLine}{exc}");
            }
            return answ;
        }

        /// <summary>
        /// Lettura valori Input Register (4...)
        /// </summary>
        /// <param name="startAddr"></param>
        /// <param name="qty"></param>
        /// <returns></returns>
        public int[] readInputReg(int startAddr, int qty)
        {
            int[] answ = new int[2];
            try
            {
                if (currPLC.Connected)
                {
                    answ = currPLC.ReadInputRegisters(startAddr, qty);
                    trackReadData(qty * 2, sw.Elapsed.TotalMilliseconds / 1000);
                }
            }
            catch (Exception exc)
            {
                lgError($"Errore in readInputReg{Environment.NewLine}{exc}");
            }
            return answ;
        }

        /// <summary>
        /// Effettua lettura semafori principale <paramref name="currDispData">Parametri da
        /// aggiornare x display in form</paramref>
        /// </summary>
        public override void readSemafori(ref newDisplayData currDispData)
        {
            if (!IOBConfFull.Device.DisabStateCh)
            {
                // NON eseguo x NON indicare data ora ultima lettura se NON fatta davvero
                //base.readSemafori(ref currDispData);
                byte[] MemBlock = new byte[2];
                try
                {
                    currDispData.semIn = Semaforo.SV;

                    if (verboseLog)
                    {
                        lgDebug("inizio read semafori");
                    }

                    if (verboseLog)
                    {
                        lgTrace(string.Format("RawInput[0]: {0}", utils.binaryForm(RawInput[0])));
                    }

                    // salvo il solo BYTE dell'input decifrando il semaforo...
                    decodeToBaseBitmap();
                    decodeOtherData();
                    // riporto bitmap...
                    reportRawInput(ref currDispData);
                }
                catch (Exception exc)
                {
                    currDispData.semIn = Semaforo.SR;
                    lgError($"Eccezione in readSemafori{Environment.NewLine}{exc}");
                }
            }
        }

        /// <summary>
        /// Override connessione
        /// </summary>
        public override void tryConnect()
        {
            bool doLog = (verboseLog || periodicLog);
            lgInfo("ModBus TCP: tryConnect step 01");
            if (!connectionOk)
            {
                // SE è necessario refresh...
                if (needRefresh)
                {
                    lgInfo("ModBus TCP: tryConnect step 02");

                    // reimposto parametri PLC se necessario...
                    setParamPlc();
                }
                lgInfo("ModBus TCP: tryConnect step 03");

                // controllo che il ping sia stato tentato almeno pingTestSec fa...
                if (DateTime.Now.Subtract(lastPING).TotalSeconds > utils.CRI("pingTestSec"))
                {
                    if (doLog)
                    {
                        lgInfo("ModBus TCP: ConnKO - tryConnect");
                    }
                    lgInfo("ModBus TCP: tryConnect step 04");

                    // in primis salvo data ping...
                    lastPING = DateTime.Now;
                    // se passa il ping faccio il resto...
                    if (testPingMachine == IPStatus.Success)
                    {
                        string szStatusConnection = "ND";
                        try
                        {
                            // ora provo connessione...
                            parentForm.commPlcActive = true;
                            currPLC.Connect();
                            szStatusConnection = "OPEN";
                            parentForm.commPlcActive = false;
                            connectionOk = currPLC.Connected;
                            lgInfo($"StatusConnection: {szStatusConnection}");
                            // refresh stato allarmi!!!
                            if (connectionOk)
                            {
                                //queueInEnabCurr = true;
                                lgInfo($"Connessione OK: {connectionOk}");
                                if (adpRunning)
                                {
                                    lgInfo($"AdpRunning: {adpRunning}");
                                }
                            }
                            else
                            {
                                lgError("Impossibile procedere, connessione mancante...");
                            }
                        }
                        catch (Exception exc)
                        {
                            lgFatal($"Errore in TryConnect adapter ModBusTCP | szStatusConnection {szStatusConnection}{Environment.NewLine}{exc}");
                            connectionOk = false;
                            needRefresh = true;
                        }
                    }
                    else
                    {
                        // loggo no risposta ping ...
                        connectionOk = false;
                        if (doLog)
                        {
                            lgError($"Attenzione: ModBusTCP controllo PING fallito per IP {IOBConfFull.Device.Connect.PingIpAddr}");
                        }
                    }
                }
            }
            // se non è ancora connesso faccio procesisng memoria caso disconnesso...
            if (!connectionOk)
            {
                // processo semafori ed invio...
                processMemoryDiscon();
            }
        }

        /// <summary>
        /// Override disconnessione
        /// </summary>
        public override void tryDisconnect()
        {
            if (currPLC != null && currPLC.Connected)
            {
                lgInfo("Richiesta disconnessione adapter ModBusTCP!");
                try
                {
                    if (currPLC.Connected)
                    {
                        currPLC.Disconnect();
                    }
                    lgInfo("Effettuata disconnessione adapter ModBusTCP!");
                }
                catch (Exception exc)
                {
                    lgFatal($"Errore nella disconnessione dall'adapter ModBusTCP{Environment.NewLine}{exc}");
                }
            }
            connectionOk = false;
            queueInEnabCurr = false;
            // resetto last ping...
            lastPING = DateTime.Now.AddMinutes(-1);
        }

        /// <summary>
        /// Scrittura di un singolo valore COIL (1...)
        /// </summary>
        /// <param name="startAddr"></param>
        /// <param name="currValue"></param>
        /// <returns></returns>
        public bool writeCoil(int startAddr, bool currValue)
        {
            bool answ = false;
            if (currPLC.Connected)
            {
                try
                {
                    currPLC.WriteSingleCoil(startAddr, currValue);
                    answ = true;
                }
                catch
                { }
            }
            return answ;
        }

        /// <summary>
        /// Scrittura di un valore Holding Register (4...)
        /// </summary>
        /// <param name="startAddr"></param>
        /// <param name="currRegVal">Valore in formato INT da registri</param>
        /// <returns></returns>
        public bool writeHoldingReg(int startAddr, int[] currRegVal)
        {
            bool answ = false;
            if (currPLC.Connected)
            {
                try
                {
                    currPLC.WriteMultipleRegisters(startAddr + deltaBase + indexLutCorr, currRegVal);
                    answ = true;
                }
                catch
                { }
            }
            return answ;
        }

        #endregion Public Methods

        #region Protected Fields

        /// <summary>
        /// Copia locale dei valori in COIL (OUT) | 0.000/09.999, oppure 0.000/65.536 = 0xFFFF, come
        /// array chiave (int) valori int[] letti tramite ModBus, da convertire secondo tipo
        /// </summary>
        protected Dictionary<int, bool> CoilLUT = new Dictionary<int, bool>();

        protected ModbusClient currPLC;

        /// <summary>
        /// Copia locale dei valori in Discrete Inputs (IN) | 10.000/19.999, oppure 100.000/165.536
        /// = 1xFFFF, come array chiave (int) valori int[] letti tramite ModBus, da convertire
        /// secondo tipo
        /// </summary>
        protected Dictionary<int, bool> DiscreteInputLUT = new Dictionary<int, bool>();

        /// <summary>
        /// Copia locale dei valori in Holding Registers | 40.000/49.999, oppure 400.000/465.536 =
        /// 4xFFFF, come array chiave (int) valori int[] letti tramite ModBus, da convertire secondo tipo
        /// </summary>
        protected Dictionary<int, int[]> HoldingRegisterLUT = new Dictionary<int, int[]>();

        /// <summary>
        /// Copia locale dei valori in Input Registers | 30.000/39.999, oppure 300.000/365.536 =
        /// 3xFFFF, come array chiave (int) valori int[] letti tramite ModBus, da convertire secondo tipo
        /// </summary>
        protected Dictionary<int, int[]> InputRegisterLUT = new Dictionary<int, int[]>();

        /// <summary>
        /// Ultimo controllo ping x evitare ping flood...
        /// </summary>
        protected DateTime lastPingConn = DateTime.Now.AddMinutes(-10);

        /// <summary>
        /// Esito ultimo ping
        /// </summary>
        protected bool lastPingOk = false;

        /// <summary>
        /// num max di errori lettura permessi prima di disconnettere
        /// </summary>
        protected int maxErrorRead = 10;

        /// <summary>
        /// Setup blocchi memorie read (indirizzo inizio, size)
        /// </summary>
        protected Dictionary<int, int> memSetR = new Dictionary<int, int>();

        /// <summary>
        /// parametri di connessione
        /// </summary>
        protected ModBusTcpParamConf parametri;

        #endregion Protected Fields

        #region Protected Properties

        /// <summary>
        /// Dizionario delle ultime operazioni di scrittura per OGNI memoria (in modo che fa log
        /// ogni x sec...)
        /// </summary>
        protected Dictionary<string, DateTime> lastMemWrite { get; set; } = new Dictionary<string, DateTime>();

        #endregion Protected Properties

        #region Protected Methods

        /// <summary>
        /// Classe di base implementazione traduzione di una LUT da memoria come valore BIT
        /// (multipli) a valore esplicito x FLog
        /// </summary>
        protected override void checkTranslateBit()
        {
            // verifico se devo processare decodifica di qualche valore...
            if (OptVar2TranslBit != null && OptVar2TranslBit.Count > 0)
            {
                Dictionary<string, int> valUpdated = new Dictionary<string, int>();
                // ciclo x ogni valore da tradurre
                foreach (var item in OptVar2TranslBit)
                {
                    int reqAddr = 0;
                    // traduco in int...
                    int.TryParse(item.Key, out reqAddr);
                    int lutAddr = reqAddr + indexLutCorr;
                    int valInt = 0;
                    int[] listInt = new int[2];
                    // cerco valore nella LUT... se è pari cerco "direttamente" altrimenti dal pari
                    // inferiore e 2° INT[]
                    if (lutAddr % 2 == 0)
                    {
                        listInt = HoldingRegisterLUT[lutAddr];
                        valInt = listInt[0];
                    }
                    else
                    {
                        lutAddr--;
                        listInt = HoldingRegisterLUT[lutAddr];
                        valInt = listInt[1];
                    }
                    // gestisco a bitmap --> salvo valore INT x check..
                    string sVal = $"{valInt}";
                    // se è variato rispetto precedente --> calcolo valori string, accodo in FLog e salvo...
                    if (item.Value != sVal)
                    {
                        valUpdated.Add(item.Value, valInt);
                    }
                }
                // se ho valori aggiornati --> aggiorno dictionary e invio
                if (valUpdated.Count > 0)
                {
                    DateTime locTStamp = DateTime.Now;
                    foreach (var item in valUpdated)
                    {
                        // salvo il valore modificato
                        OptVar2TranslInt[item.Key] = $"{item.Value}";
                        List<string> listVal = new List<string>();
                        // si tratta di valori bit --> devo cercare OGNI bit attivo (0..8 --> 0.255)
                        for (int i = 0; i < 8; i++)
                        {
                            if (BitUtils.isActive(item.Value, i))
                            {
                                // traduco ed accodo...
                                listVal.Add(itemTranslation($"{item.Key}.{i}"));
                            }
                        }
                        // costruisco stringa finale...
                        string trad = string.Join(",", listVal);
                        string sVal = $"Change {item.Key}: {locTStamp.ToString()} | Val: {item.Value} | trad: {trad}";
                        bool sent = accodaFLog(item.Key, sVal, qEncodeFLog(item.Key, trad));
                        if (sent)
                        {
                            trackDynData(item.Key, trad);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Classe di base implementazione traduzione di una LUT da memoria come valore INT a valore
        /// esplicito x FLog
        /// </summary>
        protected override void checkTranslateInt()
        {
            if (OptVar2TranslInt != null && OptVar2TranslInt.Count > 0)
            {
                Dictionary<string, string> valUpdated = new Dictionary<string, string>();
                // ciclo x ogni valore da tradurre
                foreach (var item in OptVar2TranslInt)
                {
                    int reqAddr = 0;
                    // traduco in int...
                    int.TryParse(item.Key, out reqAddr);
                    int lutAddr = reqAddr + indexLutCorr;
                    int valInt = 0;
                    int[] listInt = new int[2];
                    // cerco valore nella LUT... se è pari cerco "direttamente" altrimenti dal pari
                    // inferiore e 2° INT[]
                    if (lutAddr % 2 == 0)
                    {
                        listInt = HoldingRegisterLUT[lutAddr];
                        valInt = listInt[0];
                    }
                    else
                    {
                        lutAddr--;
                        listInt = HoldingRegisterLUT[lutAddr];
                        valInt = listInt[1];
                    }
                    // cerco traduzione...
                    string trad = itemTranslation(item.Key, $"{valInt}");
                    // se è variato rispetto precedente --> accodo in FLog e salvo...
                    if (item.Value != trad)
                    {
                        valUpdated.Add(item.Value, trad);
                    }
                }
                // se ho valori aggiornati --> aggiorno dictionary e invio
                if (valUpdated.Count > 0)
                {
                    string sVal = "";
                    DateTime locTStamp = DateTime.Now;
                    foreach (var item in valUpdated)
                    {
                        OptVar2TranslInt[item.Key] = item.Value;
                        sVal = $"Change {item.Key}: {locTStamp.ToString()} | Val: {item.Value}";
                        bool sent = accodaFLog(item.Key, sVal, qEncodeFLog(item.Key, item.Value));
                        if (sent)
                        {
                            trackDynData(item.Key, item.Value);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Decodifica il resto dell'area x i dati accessori (allarmi, ...)
        /// </summary>
        protected virtual void decodeOtherData()
        {
        }

        /// <summary>
        /// Effettua decodifica aree memoria alla bitmap usata x MAPO/GWMS
        /// - per macchine base (GWMS) IN REALTA' non gestito lo stato macchina.... previsto comunque
        /// </summary>
        protected virtual void decodeToBaseBitmap()
        {
            // init a zero...
            B_input = 0;
        }

        /// <summary>
        /// Recupero allarmi specifico x ModbusTCP (16 e 32 bit...)
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        protected override int getAlarmStatus(BaseAlarmConf item)
        {
            int answ = 0;
            int[] listInt = new int[2];
            // verifico se usare LUT
            bool useLUT = memSetR != null && memSetR.Count > 0;
            if (useLUT)
            {
                int lutAddress = parametri.holdRegBaseAddr + item.index + deltaBase + indexLutCorr;
                if (HoldingRegisterLUT.ContainsKey(lutAddress))
                {
                    try
                    {
                        listInt = HoldingRegisterLUT[lutAddress];
                    }
                    catch (Exception exc)
                    {
                        lgError($"Errore in lettura da HoldingRegisterLUT per indirizzo {lutAddress} | item {item.memAddr}{Environment.NewLine}{exc}");
                    }
                }
            }
            else
            {
                listInt = readInputReg(item.index, item.size);
            }
            answ = ModbusClient.ConvertRegistersToInt(listInt);
            return answ;
        }

        /// <summary>
        /// Override metodo x scrittura parametri su PLC
        /// </summary>
        /// <param name="updatedPar"></param>
        protected override void plcWriteParams(ref List<objItem> updatedPar)
        {
            dataConf currMem = null;
            int byteSize = 0;
            int[] CurrVal = new int[1];
            string memAddrWrite = "";
            bool fatto = false;
            string serObj = "";
            if (updatedPar != null)
            {
                // controllo i parametri... ne gestisco 4...
                foreach (var item in updatedPar)
                {
                    try
                    {
                        memAddrWrite = "";
                        int valInt = 0;
                        uint valUInt = 0;
                        double valDouble = 0;
                        // cerco in area memMapWrite...
                        if (memMap.mMapWrite.ContainsKey(item.uid))
                        {
                            // recupero!
                            currMem = memMap.mMapWrite[item.uid];
                            byteSize = currMem.size;
                            memAddrWrite = currMem.memAddr;
                            CurrVal = new int[byteSize];
                            // faccio preliminarmente upsertKey...
                            upsertKey(currMem.name, currMem.value);
                            serObj = JsonConvert.SerializeObject(item, Formatting.Indented);
                            lgInfo($"Inizio processing plcWriteParams per {currMem.name} | valore richiesto {currMem.value}{Environment.NewLine}---------------UPDATED PARAM---------------{Environment.NewLine}{serObj}{Environment.NewLine}---------------");
                            serObj = JsonConvert.SerializeObject(currMem, Formatting.Indented);
                            lgInfo($"---------------MEMORY CONTENT---------------{Environment.NewLine}{serObj}{Environment.NewLine}---------------");
                            switch (currMem.tipoMem)
                            {
                                case plcDataType.Boolean:
                                    break;

                                case plcDataType.Int:
                                    valInt = getScaledInt(currMem, true);
                                    if (byteSize == 1)
                                    {
                                        CurrVal[0] = valInt;
                                    }
                                    else
                                    {
                                        CurrVal = ModbusClient.ConvertIntToRegisters(valInt);
                                    }

                                    fatto = writeHoldingReg(currMem.index, CurrVal);
                                    break;

                                case plcDataType.IntLH:
                                    valInt = getScaledInt(currMem, true);
                                    if (byteSize == 1)
                                    {
                                        CurrVal[1] = valInt;
                                    }
                                    else
                                    {
                                        CurrVal = ModbusClient.ConvertIntToRegisters(valInt);
                                        Array.Reverse(CurrVal);
                                    }

                                    fatto = writeHoldingReg(currMem.index, CurrVal);
                                    break;

                                case plcDataType.DInt:
                                    valUInt = getScaledUInt(currMem, true);
                                    CurrVal = ModbusClient.ConvertLongToRegisters(valInt);
                                    fatto = writeHoldingReg(currMem.index, CurrVal);
                                    break;

                                case plcDataType.DIntLH:
                                    valUInt = getScaledUInt(currMem, true);
                                    CurrVal = ModbusClient.ConvertLongToRegisters(valInt);
                                    Array.Reverse(CurrVal);
                                    fatto = writeHoldingReg(currMem.index, CurrVal);
                                    break;

                                //case plcDataType.Word:
                                //    valUInt = getScaledUInt(currMem);
                                //    saveWordOnMemBlock(ref MemBlock, 0, valInt.ToString());
                                //    break;

                                //case plcDataType.DWord:
                                //    valUInt = getScaledUInt(currMem);
                                //    saveDWordOnMemBlock(ref MemBlock, 0, valInt.ToString());
                                //    break;

                                case plcDataType.Real:
                                case plcDataType.RealLH:
                                    valDouble = getScaledDouble(currMem);
                                    CurrVal = ModbusClient.ConvertFloatToRegisters((float)valDouble, ModbusClient.RegisterOrder.LowHigh);
                                    fatto = writeHoldingReg(currMem.index, CurrVal);
                                    break;

                                case plcDataType.FloatBADC:
                                case plcDataType.RealHL:
                                    valDouble = getScaledDouble(currMem);
                                    CurrVal = ModbusClient.ConvertFloatToRegisters((float)valDouble, ModbusClient.RegisterOrder.HighLow);
                                    fatto = writeHoldingReg(currMem.index, CurrVal);
                                    break;

                                case plcDataType.String:
                                    CurrVal = ModbusClient.ConvertStringToRegisters(currMem.value.PadRight(currMem.size * 2));
                                    var regLen = ModbusClient.ConvertIntToRegisters(currMem.size * 2 - 2);
                                    Array.Copy(regLen, 0, CurrVal, CurrVal.Length - regLen.Length, regLen.Length);
                                    //CurrVal[CurrVal.Length - 2] = regLen[1];
                                    //CurrVal[CurrVal.Length - 1] = regLen[0];
                                    fatto = writeHoldingReg(currMem.index, CurrVal);
                                    break;

                                default:
                                    break;
                            }
                            lgInfo($"---------------MemBlock data---------------{Environment.NewLine}CurrVal: {CurrVal}{Environment.NewLine}--------------- END data ---------------");
                            if (!string.IsNullOrEmpty(memAddrWrite))
                            {
                                // se fatto --> aggiorno!
                                if (fatto)
                                {
                                    item.value = item.reqValue;
                                    item.reqValue = "";
                                    item.lastRead = DateTime.Now;
                                    item.UM = currMem.unit;
                                }
                            }
                            else
                            {
                                lgError($"Errore: memAddrWrite vuoto!");
                            }
                        }
                        else
                        {
                            lgError($"Errore uid non trovato in area write memory: {item.uid}, ci sono {memMap.mMapWrite.Count} in area write");
                        }
                    }
                    catch (Exception exc)
                    {
                        lgError($"Eccezione in fase di plcWriteParams per item {item.uid} con valore {item.value}{Environment.NewLine}{exc}");
                    }
                }
            }
        }

        /// <summary>
        /// Imposto parametri PLC
        /// </summary>
        protected override void setParamPlc()
        {
            DateTime adesso = DateTime.Now;
            // Creo oggetto connessione NC
            parentForm.commPlcActive = true;
            lgInfo($"Start init Adapter ModBus TCP all'IP {IOBConfFull.Device.Connect.IpAddr} | port: {IOBConfFull.Device.Connect.Port} | --> IOB {IOBConfFull.General.CodIOB}");
            // SE è necessario refresh...
            if (needRefresh)
            {
                lgInfo("Refreshing connection...");
                if (parametri != null)
                {
                    try
                    {
                        lgInfoStartup("Init parametri speciali da IobConfTree...");
                        parametri.ipAdrr = IOBConfFull.Device.Connect.IpAddr;
                        parametri.port = int.Parse(IOBConfFull.Device.Connect.Port);
                        if (IOBConfFull.Special.ModbusConf != null)
                        {
                            // ora leggo valori speciali
                            parametri.memAddrRead = IOBConfFull.Special.ModbusConf.memAddrRead;
                            parametri.memAddrWrite = IOBConfFull.Special.ModbusConf.memAddrWrite;
                            parametri.memSizeRead = IOBConfFull.Special.ModbusConf.memSizeRead;
                            parametri.memSizeWrite = IOBConfFull.Special.ModbusConf.memSizeWrite;
                            parametri.holdRegBaseAddr = IOBConfFull.Special.ModbusConf.holdRegBaseAddr;
                            parametri.useCalcBaseAddr = IOBConfFull.Special.ModbusConf.useCalcBaseAddr;
                            // valore delta base e shift
                            deltaBase = IOBConfFull.Special.ModbusConf.deltaBase;
                            indexLutCorr = IOBConfFull.Special.ModbusConf.indexLutCorr;
                            // verifico eventuale parametro memoria estesa (non 0...10'000 ma 0...xFFFF=65536)
                            modbusExtReg = IOBConfFull.Special.ModbusConf.modbusExtReg;
                        }
#if false
                        // leggo file init...
                        lgInfoStartup("Reading ini file...");
                        IniFile fIni = new IniFile(cIobConf.iniFileName);
                        // ora leggo valori speciali
                        parametri.memAddrRead = fIni.ReadString("MEMORY", "ADDR_READ", "");
                        parametri.memAddrWrite = fIni.ReadString("MEMORY", "ADDR_WRITE", "");
                        parametri.memSizeRead = fIni.ReadInteger("MEMORY", "SIZE_READ", 0);
                        parametri.memSizeWrite = fIni.ReadInteger("MEMORY", "SIZE_WRITE", 0);
                        parametri.holdRegBaseAddr = fIni.ReadInteger("MEMORY", "HR_BASE_ADDR", 40001);
                        parametri.useCalcBaseAddr = fIni.ReadBoolean("MEMORY", "CALC_BASE_ADDR", true);
                        // valore delta base e shift
                        deltaBase = fIni.ReadInteger("MEMORY", "DELTA_BASE", 0);
                        indexLutCorr = fIni.ReadInteger("MEMORY", "INDEX_LUT_CORR", 0);
                        // verifico eventuale parametro memoria estesa (non 0...10'000 ma 0...xFFFF=65536)
                        modbusExtReg = fIni.ReadBoolean("MEMORY", "MODBUS_EXT_REG"); 
#endif
                        // salvo vettori memoria...
                        lgInfoStartup("Set RawInput dimensions...");
                        RawInput = new byte[parametri.memSizeRead];
                        RawOutput = new byte[parametri.memSizeWrite];
                        // salvo parametri conn!
                        lgInfoStartup($"Parametri memoria: memAddrRead: {parametri.memAddrRead} | memAddrWrite: {parametri.memAddrWrite} | memSizeRead: {parametri.memSizeRead} | memSizeWrite: {parametri.memSizeWrite} | deltaBase: {deltaBase}");
                    }
                    catch (Exception exc)
                    {
                        lgError(exc, "Errore in parse parametri da IOBConf");
                    }

                    // carico conf vettore memoria...
                    loadMemConf();
                    fixDefaultPar();
                    // avvio conf blocchi memoria
                    setupMemBlocks();
                    // aggiungo DELTA x calcolo min/MAX...
                    string deltaValStr = getOptPar("DELTA_VAL");
                    if (!string.IsNullOrEmpty(deltaValStr))
                    {
                        double.TryParse(deltaValStr.Replace(".", ","), out deltaVal);
                    }
                    // 2025.05.28: evita contapezzi dove NON configurato esplicitamente (versione "Imperativa" da nuova conf)
                    bool enabPzCnt = IOBConfFull.Device.EnabPzCount;
#if false
                    bool enableByApp = utils.CRB("enableContapezzi");
                    bool enableByIob = (getOptPar("ENABLE_PZCOUNT") == "TRUE");
                    bool disableByIob = (getOptPar("DISABLE_PZCOUNT") == "TRUE");

#endif
                    var strMaxError = getOptPar("MAX_ERROR_READ");
                    if (!string.IsNullOrEmpty(strMaxError))
                    {
                        int.TryParse(strMaxError, out maxErrorRead);
                    }
                    //if ((enableByApp || enableByIob) && !(disableByIob))
                    if (enabPzCnt)
                    {
                        lgInfoStartup("ModBus TCP: inizio gestione contapezzi");
                        try
                        {
                            // verifico quale modalità sia richiesta: STD (6711) oppure BIT (Custom,
                            // con indicazione area)
                            if (IOBConfFull.OptPar.Count > 0 && !string.IsNullOrWhiteSpace(IOBConfFull.Device.PzCountMode))
                            {
                                if (IOBConfFull.Device.PzCountMode.StartsWith("STD"))
                                {
                                    lgInfoStartup("Init contapezzi ModBusTCP: pzCntReload(true)");
                                    pzCntReload(true);
                                    // refresh associazione Macchina - IOB
                                    SendM2IOB();
                                    // invio altri dati accessori...
                                    SendMachineConf();
                                    // gestione allarmi: controllo se deve rileggere da REDIS
                                    if (resetAlarmOnStart)
                                    {
                                        SendAlarmReset();
                                    }
                                    // per adesso imposto lettura PLC == contapezzi (poi farà vera lettura...)
                                    contapezziPLC = contapezziIOB;
                                }
                                else
                                {
                                    contapezziIOB = 0;
                                    lgDebug("Contapezzi STD disabilitato: modalità {0}", IOBConfFull.Device.PzCountMode);
                                }
                            }
                            else
                            {
                                contapezziIOB = 0;
                                lgError("Parametro mancante PZCOUNT_MODE");
                            }
                        }
                        catch (Exception exc)
                        {
                            lgError(exc, "Errore in contapezzi ModBusTCP");
                        }
                    }
                    // verifico se sia connessa...
                    if (!connectionOk)
                    {
                        // ora tento avvio PLC... SE PING OK...
                        lastPING = adesso;
                        IPStatus esitoPing = testPingMachine;
                        if (esitoPing == IPStatus.Success)
                        {
                            needRefresh = false;
                            try
                            {
                                //Ip-Address and Port of Modbus-TCP-Server
                                currPLC = new ModbusClient(parametri.ipAdrr, parametri.port);
                                currPLC.ConnectionTimeout = 5000;
                                currPLC.ConnectedChanged += CurrPLC_ConnectedChanged;

                                // disconnetto e connetto...
                                if (isVerboseLog)
                                {
                                    lgInfoStartup("ModBus TCP: tryDisconnect");
                                }
                                tryDisconnect();

                                // tolgo needRefresh x evitare un loop... (tryConnect richiama setParamPlc)
                                needRefresh = false;
                                lgInfoStartup("ModBus TCP: tryConnect");
                                tryConnect();
                                lgInfoStartup("End init Adapter ModBusTCP");
                                if (isVerboseLog)
                                {
                                    lgInfo("ModBus TCP CONNESSIONE AVVENUTA");
                                }
                            }
                            catch (Exception exc)
                            {
                                lgError(exc, "Errore in INIT ModBusTCP");
                            }
                        }
                        else
                        {
                            lgError($"ModBusTCP IOB | Errore in ping: esito {esitoPing}");
                        }
                    }
                    parentForm.commPlcActive = false;
                }
                else
                {
                    lgError("Parametri null!");
                }
            }
        }

        /// <summary>
        /// effettua il setup dei memblock da gestire (NON leggo intera memoria ma tanti blocchi...)
        /// </summary>
        protected void setupMemBlocks()
        {
            // se configurato -_> deserializzo
            string confFile = getOptPar("MEM_BLOCK");
            if (!string.IsNullOrEmpty(confFile))
            {
                string jsonFileName = $"{Application.StartupPath}/DATA/CONF/{confFile}";
                lgInfo($"Apertura file {jsonFileName}");
                using (StreamReader reader = new StreamReader(jsonFileName))
                {
                    string jsonData = reader.ReadToEnd();
                    if (!string.IsNullOrEmpty(jsonData))
                    {
                        lgInfo($"File json MemBlock composto da {jsonData.Length} caratteri");
                        try
                        {
                            var currMem = JsonConvert.DeserializeObject<MemBlockConf>(jsonData);
                            memSetR = currMem.ReadBlocks;
                        }
                        catch (Exception exc)
                        {
                            lgError($"Errore in setupMemBlock{Environment.NewLine}{exc}");
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Testa la condition modbus da LUT + configurazione, valori tipo BIT
        /// </summary>
        /// <param name="cKey"></param>
        /// <returns></returns>
        protected bool testBitCondition(string cKey)
        {
            bool answ = false;
            if (OptCheckCondBit.ContainsKey(cKey))
            {
                int currStatus = 0;
                int[] listInt = new int[2];
                listInt = HoldingRegisterLUT[OptCheckCondBit[cKey].BaseAddr];
                currStatus = ModbusClient.ConvertRegistersToInt(listInt);
                // controllo valore del bit richiesto
                answ = ((currStatus & (1 << OptCheckCondBit[cKey].BitNum)) == OptCheckCondBit[cKey].ValOk);
                lgTrace($"testBitCondition for {cKey} | BaseAddr: {OptCheckCondBit[cKey].BaseAddr} | BitNum: {OptCheckCondBit[cKey].BitNum} | ValOk: {OptCheckCondBit[cKey].ValOk}");
            }
            else
            {
                lgTrace($"testBitCondition error: {cKey} not found");
            }
            return answ;
        }

        /// <summary>
        /// Effettua ciclo letture di ogni area configurata
        /// </summary>
        /// <param name="baseAddr">indirizzo di partenza</param>
        /// <param name="numVals">num valori da leggere</param>
        protected void testBlockReadExt(int baseAddr, int numVals)
        {
            int baseHold = deltaBase;
            int[] readTestInt = new int[2];
            bool[] readTestBool = new bool[2];
            modbusMemType currMemType = modbusMemType.NotDefined;
            // valori setup secondo conf memoria: memoria estesa... 0 ... xFFFF per ogni set da 100'000...
            int maxVal = modbusExtReg ? 65536 : 10000;
            int baseMult = modbusExtReg ? 100000 : 10000;
            lgTrace($"-------------------- Inizio test lettura EXT {baseAddr} --------------------");
            try
            {
                sw.Restart();
                // calcolo tipo memoria
                currMemType = getMemType(baseAddr, maxVal, baseMult);
                switch (currMemType)
                {
                    case modbusMemType.Coil:
                        readTestBool = currPLC.ReadCoils(baseAddr - baseHold, numVals);
                        trackReadData(numVals / 8, sw.Elapsed.TotalMilliseconds / 1000);
                        break;

                    case modbusMemType.DiscreteInput:
                        readTestBool = currPLC.ReadDiscreteInputs(baseAddr - 1 * baseMult + baseHold, numVals);
                        trackReadData(numVals / 8, sw.Elapsed.TotalMilliseconds / 1000);
                        break;

                    case modbusMemType.InputRegister:
                        readTestInt = currPLC.ReadInputRegisters(baseAddr - 3 * baseMult + baseHold, numVals);
                        trackReadData(numVals * 2, sw.Elapsed.TotalMilliseconds / 1000);
                        break;

                    case modbusMemType.HoldingRegister:
                        readTestInt = currPLC.ReadHoldingRegisters(baseAddr - 4 * baseMult + baseHold, numVals);
                        trackReadData(numVals * 2, sw.Elapsed.TotalMilliseconds / 1000);
                        break;

                    case modbusMemType.NotDefined:
                    default:
                        break;
                }
                sw.Stop();
                lgTrace($"Stats lettura | {readTestInt.Length} val | {sw.ElapsedMilliseconds}ms");
            }
            catch
            { }

            switch (currMemType)
            {
                case modbusMemType.Coil:
                case modbusMemType.DiscreteInput:
                    for (int i = 0; i < readTestBool.Length; i++)
                    {
                        lgTrace($"{currMemType} | {baseAddr + i:000} | Val: {readTestBool[i]}");
                    }
                    break;

                case modbusMemType.InputRegister:
                case modbusMemType.HoldingRegister:
                    for (int i = 0; i < readTestInt.Length / 2; i++)
                    {
                        int[] thisSet = new int[2];
                        Array.Copy(readTestInt, i * 2, thisSet, 0, 2);
                        lgTrace($"{currMemType} | {baseAddr + i * 2:000} | Valori: {thisSet[0]} / {thisSet[1]} | Val Int: {ModbusClient.ConvertRegistersToInt(thisSet)} | Val Real: {ModbusClient.ConvertRegistersToFloat(thisSet):N6}");
                    }
                    // provo anche come stringa intera
                    lgTrace($"{currMemType} | {baseAddr:000} | Val string: {ModbusClient.ConvertRegistersToString(readTestInt, 0, readTestInt.Length)}");
                    break;

                case modbusMemType.NotDefined:
                default:
                    lgTrace($"Errore: tipo memoria non definitito per {baseAddr} / {numVals}");
                    break;
            }
            lgTrace($"-------------------- Completato test lettura {baseAddr} --------------------");
        }

        /// <summary>
        /// Test connessione CNC
        /// </summary>
        /// <returns></returns>
        protected bool testCncConn()
        {
            bool answ = currPLC.Connected;
            if (!answ)
            {
                // riduco i controlli ping.. li faccio solo ogni 5 ping period se precedente positivo...
                DateTime adesso = DateTime.Now;
                if (lastPingOk && adesso.Subtract(lastPingConn).TotalMilliseconds < 5 * parametri.pingMsTimeout)
                {
                    answ = lastPingOk;
                }
                else
                {
                    IPStatus pingStatus = testPingMachine;

                    // se non ok riprovo 1 volta dopo attesa
                    if (pingStatus != IPStatus.Success)
                    {
                        Thread.Sleep(2 * IOBConfFull.Device.Connect.PingMsTimeout);
                        pingStatus = testPingMachine;
                    }
                    // se non passa ancora errore!
                    if (pingStatus != IPStatus.Success)
                    {
                        lgError($"Errore in testCncConn | reply Status {pingStatus} | IP: {parametri.ipAdrr} | T.Out: {parametri.pingMsTimeout}ms");
                    }
                    // se passa il ping faccio il resto...
                    else
                    {
                        if (!currPLC.Connected)
                        {
                            currPLC.Connect();
                        }

                        if (!currPLC.Available(500))
                        {
                            lgError($"PLC ModBus NON disponibile: {currPLC.IPAddress} | {currPLC.Port}");
                        }
                        else
                        {
                            if (!currPLC.Connected)
                            {
                                lgError($"PLC ModBus NON connesso:{currPLC.IPAddress} | {currPLC.Port}");
                            }
                            else
                            {
                                // tutto ok
                                parentForm.updateComStats("Connection OK");
                                answ = true;
                            }
                        }
                    }
                    // salvo stato ping
                    lastPingConn = adesso;
                }
                lastPingOk = answ;
            }

            return answ;
        }

        /// <summary>
        /// Testa la condition modbus da LUT + configurazione, valori tipo BIT tramite DiscreteInput
        /// (0x02 - 10000+)
        /// </summary>
        /// <param name="cKey"></param>
        /// <returns></returns>
        protected bool testDisInBitCondition(string cKey)
        {
            bool answ = false;
            if (OptCheckCondBit.ContainsKey(cKey))
            {
                if (DiscreteInputLUT.Count > 0)
                {
                    int idxMem = OptCheckCondBit[cKey].BaseAddr + indexLutCorr;
                    if (DiscreteInputLUT.ContainsKey(idxMem))
                    {
                        answ = DiscreteInputLUT[idxMem] == (OptCheckCondBit[cKey].ValOk != 0);
                        lgTrace($"testDisInBitCondition for {cKey} | BaseAddr: {OptCheckCondBit[cKey].BaseAddr} | BitNum: {OptCheckCondBit[cKey].BitNum} | ValOk: {OptCheckCondBit[cKey].ValOk} | actual: {answ}");
                    }
                }
            }
            else
            {
                lgTrace($"testDiscrInputBitCondition error: {cKey} not found");
            }
            return answ;
        }

        /// <summary>
        /// Testa la condition modbus da LUT + configurazione, valori tipo INT
        /// </summary>
        /// <param name="cKey"></param>
        /// <returns></returns>
        protected bool testIntCondition(string cKey)
        {
            bool answ = false;
            if (OptCheckCondInt.ContainsKey(cKey))
            {
                int currStatus = 0;
                int[] listInt = new int[2];
                listInt = HoldingRegisterLUT[OptCheckCondInt[cKey].BaseAddr];
                // leggo da conf OptCheckCondInt[cKey].IntIndex
                if (OptCheckCondInt[cKey].IntIndex < 2)
                {
                    currStatus = listInt[OptCheckCondInt[cKey].IntIndex];
                }
                else
                {
                    currStatus = ModbusClient.ConvertRegistersToInt(listInt);
                }
                //answ = (currStatus == OptCheckCondInt[cKey].ValOk);
                // controllo se il valore sia in elenco di quelli validi...
                answ = OptCheckCondInt[cKey].ValOk.Contains(currStatus);
                lgTrace($"testIntCondition for {cKey} | BaseAddr: {OptCheckCondInt[cKey].BaseAddr} | currStatus: {currStatus} | ValOk: {string.Join(",", OptCheckCondInt[cKey].ValOk)}");
            }
            else
            {
                lgTrace($"testIntCondition error: {cKey} not found");
            }
            return answ;
        }

        /// <summary>
        /// Test di lettura dei blocchi di memoria ESTESO (considerato memorie xFFFF e tutti e 4 i
        /// tipi di dati modbus)
        /// </summary>
        protected void testReadExt()
        {
            foreach (var item in memSetR)
            {
                testBlockReadExt(item.Key, item.Value);
            }
        }

        #endregion Protected Methods

        #region Private Fields

        /// <summary>
        /// Valore delta x gestione min/MAX e valore rilevato
        /// </summary>
        private double deltaVal = 0;

        #endregion Private Fields

        #region Private Properties

        /// <summary>
        /// Valore da gestire come delta degli indirizzi configurati
        /// </summary>
        private int deltaBase { get; set; } = 0;

        /// <summary>
        /// Valore da gestire come delta degli index x lettura LUT
        /// </summary>
        private int indexLutCorr { get; set; } = 0;

        /// <summary>
        /// Indica se usare gli indirizzi estesi per ogni blocco: false = 0...10'000 true =
        /// 0...65'536 (= xFFFF)
        /// </summary>
        private bool modbusExtReg { get; set; } = false;

        #endregion Private Properties

        #region Private Methods

        /// <summary>
        /// converte valore in tipo desiderato
        /// </summary>
        /// <param name="listInt"></param>
        /// <param name="size"></param>
        /// <param name="tipoMem"></param>
        /// <returns></returns>
        private static double convertFromReg(int[] listInt, int size, plcDataType tipoMem)
        {
            double valore = 0;
            //verifico se sia INT o real x convertire...
            switch (tipoMem)
            {
                case plcDataType.Real:
                case plcDataType.RealLH:
                case plcDataType.FloatABCD:
                    if (size == 4)
                    {
                        valore = ModbusClient.ConvertRegistersToDouble(listInt);
                    }
                    else if (size == 2)
                    {
                        valore = ModbusClient.ConvertRegistersToFloat(listInt, ModbusClient.RegisterOrder.LowHigh);
                    }
                    break;

                case plcDataType.RealHL:
                case plcDataType.FloatCDAB:
                    if (size == 4)
                    {
                        //// inverto array...
                        //Array.Reverse(listInt);
                        //valore = ModbusClient.ConvertRegistersToDouble(listInt);
                        valore = ModbusClient.ConvertRegistersToDouble(listInt, ModbusClient.RegisterOrder.HighLow);
                    }
                    else if (size == 2)
                    {
                        //// inverto array...
                        //Array.Reverse(listInt);
                        //valore = ModbusClient.ConvertRegistersToFloat(listInt);
                        valore = ModbusClient.ConvertRegistersToFloat(listInt, ModbusClient.RegisterOrder.HighLow);
                    }
                    break;

                // caso speciale x Cedax, che usa HighVal moltiplicato x 32768...
                case plcDataType.HLPInt:
                    if (size == 4)
                    {
                        valore = ModbusClient.ConvertRegistersToLong(listInt);
                    }
                    else if (size == 2)
                    {
                        int fact = Int16.MaxValue + 1;
                        valore = listInt[1] * fact + listInt[0];
                    }
                    break;

                case plcDataType.Int:
                default:
                    if (size == 4)
                    {
                        valore = ModbusClient.ConvertRegistersToLong(listInt);
                    }
                    else if (size == 2)
                    {
                        valore = ModbusClient.ConvertRegistersToInt(listInt);
                    }
                    else if (size == 1)
                    {
                        valore = listInt[0];
                    }
                    break;

                case plcDataType.IntLH:
                case plcDataType.DIntLH:
                    if (size == 4)
                    {
                        Array.Reverse(listInt);
                        valore = ModbusClient.ConvertRegistersToLong(listInt);
                    }
                    else if (size == 2)
                    {
                        Array.Reverse(listInt);
                        valore = ModbusClient.ConvertRegistersToInt(listInt);
                    }
                    break;
            }
            return valore;
        }

        private static double getScaledDouble(dataConf currMem)
        {
            double valDouble;
            // prima faccio eventuale fattore di scala...
            double.TryParse(currMem.value, out valDouble);
            if (currMem.factor != 1)
            {
                valDouble = valDouble * (double)currMem.factor;
            }

            return valDouble;
        }

        /// <summary>
        /// Effettua scalatura valore secondo fattore Read --&gt; * (moltiplico) Write --&gt; / (divido)
        /// </summary>
        /// <param name="currMem"></param>
        /// <param name="isWrite"></param>
        /// <returns></returns>
        private static int getScaledInt(dataConf currMem, bool isWrite = false)
        {
            int valInt = 0;
            double temp;
            // prima faccio eventuale fattore di scala...
            double.TryParse(currMem.value.Replace(".", ","), out temp);
            //int.TryParse(currMem.value, out valInt);
            if (currMem.factor != 1)
            {
                if (isWrite)
                {
                    valInt = (int)(temp * 10 / currMem.factor) / 10;
                }
                else
                {
                    valInt = (int)(temp * 10 * currMem.factor) / 10;
                }
            }
            else
            {
                valInt = (int)temp;
            }

            return valInt;
        }

        /// <summary>
        /// Effettua scalatura valore secondo fattore Read --&gt; * (moltiplico) Write --&gt; / (divido)
        /// </summary>
        /// <param name="currMem"></param>
        /// <param name="isWrite"></param>
        private static uint getScaledUInt(dataConf currMem, bool isWrite = false)
        {
            uint valUInt;
            // prima faccio eventuale fattore di scala...
            uint.TryParse(currMem.value, out valUInt);
            if (currMem.factor != 1)
            {
                if (isWrite)
                {
                    valUInt = valUInt / (uint)currMem.factor;
                }
                else
                {
                    valUInt = valUInt * (uint)currMem.factor;
                }
            }

            return valUInt;
        }

        /// <summary>
        /// se si disconnette --&gt; processo tentativo riconnessione..
        /// </summary>
        /// <param name="sender"></param>
        private void CurrPLC_ConnectedChanged(object sender)
        {
            if (!currPLC.Connected)
            {
                tryDisconnect();
            }
        }

        /// <summary>
        /// Recupero dati da singole letture
        /// </summary>
        /// <param name="memItemList">Valori da processare</param>
        /// <param name="useLUT">Impiego della LookUpTable x diminuire accessi</param>
        /// <param name="outVal">Dizionario valori in uscita</param>
        /// <returns>Errori di lettura</returns>
        private Dictionary<string, dataConfTSVC> getDataDictionary(Dictionary<string, dataConfTSVC> memItemList, bool useLUT, ref Dictionary<string, string> outVal)
        {
            Dictionary<string, dataConfTSVC> readErrorList = new Dictionary<string, dataConfTSVC>();
            // inizializzo i valori
            bool[] listBool = new bool[1];
            int[] listInt = new int[2];
            // procedo x ogni valore configurato...
            foreach (var item in memItemList)
            {
                try
                {
                    listInt = new int[item.Value.size];
                    // attesa 50 ms prima di procedere x evitare burst dati
                    if (!useLUT)
                    {
                        Thread.Sleep(50);
                    }
                    string valString = "";
                    double valore = 0;
                    double valoreScal = 0;
                    bool dataOk = false;
                    // in primis DEVO determinare di quale TIPO di valore ho bisogno... dalla PRIMA
                    // cifra di memAddr...
                    modBusAddrType memAddrType = getAddrType(item.Value.memAddr);
                    // in base al tipo leggo array...
                    switch (memAddrType)
                    {
                        case modBusAddrType.Coil:
                            listBool = readCoil(item.Value.index, item.Value.size);
                            valore = listBool[0] ? 1 : 0;
                            break;

                        case modBusAddrType.DiscreteInput:
                            listBool = readDiscrInputs(item.Value.index, item.Value.size);
                            valore = listBool[0] ? 1 : 0;
                            break;

                        case modBusAddrType.InputRegister:
                            if (useLUT)
                            {
                                int lutAddress = (parametri.holdRegBaseAddr / 4 * 3) + item.Value.index + deltaBase + indexLutCorr;
                                // se size = 1 --> devo cambiare modo recupero
                                if (item.Value.size == 1)
                                {
                                    //if (item.Value.index % 2 == 0)
                                    if (lutAddress % 2 == 0)
                                    {
                                        //Array.Copy(HoldingRegisterLUT[lutAddress], listInt, 1);
                                        Array.Copy(InputRegisterLUT[lutAddress], 0, listInt, 0, 1);
                                    }
                                    else
                                    {
                                        lutAddress--;
                                        Array.Copy(InputRegisterLUT[lutAddress], 1, listInt, 0, 1);
                                    }
                                }
                                else
                                {
                                    if (InputRegisterLUT.ContainsKey(lutAddress))
                                    {
                                        try
                                        {
                                            if (listInt.Length == 2)
                                            {
                                                listInt = InputRegisterLUT[lutAddress];
                                            }
                                            else
                                            {
                                                // faccio ciclo x copiare 2 alla volta...
                                                for (int i = 0; i < listInt.Length; i += 2)
                                                {
                                                    Array.Copy(InputRegisterLUT[lutAddress + i], 0, listInt, i, 2);
                                                }
                                            }
                                        }
                                        catch (Exception exc)
                                        {
                                            lgError($"Errore in lettura da InputRegisterLUT per indirizzo {lutAddress} | item {item.Key}{Environment.NewLine}{exc}");
                                        }
                                    }
                                }
                            }
                            else
                            {
                                listInt = readInputReg(item.Value.index, item.Value.size);
                            }
                            if (item.Value.tipoMem == plcDataType.String)
                            {
                                // valore string...
                                valString = ModbusClient.ConvertRegistersToString(listInt, 0, item.Value.size);
                            }
                            else
                            {
                                // valori numerici...
                                valore = convertFromReg(listInt, item.Value.size, item.Value.tipoMem);
                            }

                            break;

                        case modBusAddrType.HoldingRegister:
                            if (useLUT)
                            {
                                int lutAddress = parametri.holdRegBaseAddr + item.Value.index + deltaBase + indexLutCorr;
                                // se size = 1 --> devo cambiare modo recupero
                                if (item.Value.size == 1)
                                {
                                    //if (item.Value.index % 2 == 0)
                                    if (lutAddress % 2 == 0)
                                    {
                                        Array.Copy(HoldingRegisterLUT[lutAddress], 0, listInt, 0, 1);
                                    }
                                    else
                                    {
                                        lutAddress--;
                                        Array.Copy(HoldingRegisterLUT[lutAddress], 1, listInt, 0, 1);
                                    }
                                }
                                else
                                {
                                    if (HoldingRegisterLUT.ContainsKey(lutAddress))
                                    {
                                        try
                                        {
                                            if (listInt.Length == 2)
                                            {
                                                listInt = HoldingRegisterLUT[lutAddress];
                                            }
                                            else
                                            {
                                                // faccio ciclo x copiare 2 alla volta...
                                                for (int i = 0; i < listInt.Length; i += 2)
                                                {
                                                    Array.Copy(HoldingRegisterLUT[lutAddress + i], 0, listInt, i, 2);
                                                }
                                            }
                                        }
                                        catch (Exception exc)
                                        {
                                            lgError($"Errore in lettura da HoldingRegisterLUT per indirizzo {lutAddress} | item {item.Key}{Environment.NewLine}{exc}");
                                        }
                                    }
                                }
                            }
                            else
                            {
                                listInt = readHoldReg(item.Value.index - 1, item.Value.size);
                            }
                            if (item.Value.tipoMem == plcDataType.String)
                            {
                                // valore string...
                                valString = ModbusClient.ConvertRegistersToString(listInt, 0, item.Value.size);
                                valString = valString.Replace("\n", "").Replace("\r", "").Replace("\0", string.Empty).Trim();
                            }
                            else
                            {
                                valore = convertFromReg(listInt, item.Value.size, item.Value.tipoMem);
                            }
                            break;

                        default:
                            break;
                    }
                    if (item.Value.tipoMem == plcDataType.String)
                    {
                        saveValueString(ref outVal, item.Key, valString);
                        lgDebug($"getDataDictionary: valore ricevuto | {item.Key} | val: {valString}");
                    }
                    else
                    {
                        // verifica limite... con delta da impianto
                        dataOk = (valore >= (item.Value.minVal + deltaVal) && valore <= (item.Value.maxVal - deltaVal));
                        if (dataOk || disDynDataRangeCheck)
                        {
                            // verifica valori EPOCH x convertirli in datetime...
                            if (item.Value.unit == "EPOCH")
                            {
                                valString = TimeUtils.epochConvert(valore).ToString("yyyy-MM-dd HH:mm:ss");
                                saveValueString(ref outVal, item.Key, valString);
                                lgDebug($"getDataDictionary: valore EPOCH ricevuto | {item.Key} | val: {valore} | valString: {valString}");
                            }
                            else
                            {
                                // moltiplico x fattore conversione...
                                valoreScal = valore * item.Value.factor;
                                saveValue(ref outVal, item.Key, valoreScal);
                                lgDebug($"getDataDictionary: valore ricevuto | {item.Key} | val: {valore} | valoreScal: {valoreScal}");
                            }
                        }
                        else
                        {
                            lgError($"getDataDictionary: valore scartato x limiti min/max | {item.Key} | val: {valore} | valoreScal: {valoreScal} | min-max: {item.Value.minVal}-{item.Value.maxVal} | deltaVal: {deltaVal}");
                            readErrorList.Add(item.Key, item.Value);
                            lgDebug($"--> rimesso in coda lettura | parametro: {item.Key} | index: {item.Value.index} | size: {item.Value.size}");
                        }
                    }
                }
                catch (Exception exc)
                {
                    lgError($"Eccezione in lettura variabile | item: {item.Key} | mem: {item.Value.memAddr}{Environment.NewLine}{exc}");
                }
            }

            return readErrorList;
        }

        /// <summary>
        /// Calcola tipo memoria modbus dato indirizzo, dati parametri maxVal (slot) + base moltiplica
        /// </summary>
        /// <param name="baseAddr">Indirizzo memoria</param>
        /// <param name="maxVal">Valore massimo ammesso</param>
        /// <param name="baseMult">Base moltiplica slot memoria</param>
        /// <returns></returns>
        private modbusMemType getMemType(int baseAddr, int maxVal, int baseMult)
        {
            modbusMemType currMemType = modbusMemType.NotDefined;
            // determino tipo di memoria
            if (baseAddr < maxVal)
            {
                currMemType = modbusMemType.Coil;
            }
            else if (baseAddr < baseMult + maxVal)
            {
                currMemType = modbusMemType.DiscreteInput;
            }
            else if (baseAddr < 3 * baseMult + maxVal)
            {
                currMemType = modbusMemType.InputRegister;
            }
            else
            {
                currMemType = modbusMemType.HoldingRegister;
            }
            return currMemType;
        }

        /// <summary>
        /// Verifica SE sia il caso di fare il log della memoria indicata
        /// </summary>
        /// <param name="memAddrWrite"></param>
        /// <param name="logValue"></param>
        private void maybeLogWrite(string memAddrWrite, string logValue)
        {
            bool doWrite = true;
            DateTime adesso = DateTime.Now;
            if (!lastMemWrite.ContainsKey(memAddrWrite))
            {
                lastMemWrite.Add(memAddrWrite, adesso.AddMinutes(-1));
            }
            // ora mi leggo valore ultimas crittura e confronto con adesso
            try
            {
                doWrite = (lastMemWrite[memAddrWrite].AddSeconds(vetoSeconds) < adesso);
            }
            catch (Exception exc)
            {
                lgError($"Eccezione in maybeLogWrite{Environment.NewLine}{exc}");
            }
            // se encessario --> LOG!
            if (doWrite)
            {
                lgInfo(logValue);
                lastMemWrite[memAddrWrite] = adesso;
            }
        }

        /// <summary>
        /// Effettua lettura blocco memoria indicato e lo salva in copia locale in CoilLUT
        /// </summary>
        /// <param name="startAddr">Indirizzo di partenza</param>
        /// <param name="numReg">Num registri da leggere (max 120)</param>
        private bool readMemBlockCoil(int startAddr, int numReg, int baseAddr)
        {
            bool allOk = false;
            if (currPLC.Connected)
            {
                allOk = true;
                // fix massima lunghezza pacchetto
                if (numReg > 120)
                {
                    lgError($"Attenzione richiesta lettura blocco troppo grande: numReg {numReg} --> 120");
                    numReg = 120;
                }

                bool[] rawData = new bool[2];
                try
                {
                    sw.Restart();
                    rawData = currPLC.ReadCoils(startAddr - baseAddr, numReg);
                    sw.Stop();
                    lgTrace($"Lettura in blocco Colis | startAddr: {startAddr} | numReg {numReg} | rawData.lenght: {rawData.Length} | {sw.ElapsedMilliseconds}ms");
                    // salvo statistica...
                    trackReadData(rawData.Length / 8, sw.Elapsed.TotalMilliseconds / 1000);
                    lastReadPLC = DateTime.Now;
                    // salvo in LUT i dati...
                    if (rawData.Length > 0)
                    {
                        for (int i = 0; i < rawData.Length; i++)
                        {
                            if (rawData.Length >= i)
                            {
                                // salvo nel registro...
                                int currAddr = startAddr + i;
                                if (CoilLUT.ContainsKey(currAddr))
                                {
                                    CoilLUT[currAddr] = rawData[i];
                                }
                                else
                                {
                                    CoilLUT.Add(currAddr, rawData[i]);
                                }
                            }
                            else
                            {
                                currReadErrors++;
                                allOk = false;
                                lgError($"CoilLUT | Impossibile copiare dati, array di origine troppo corto: rawData.lenght: {rawData.Length} | base addr richiesto i: {i} | errori currReadErrors: {currReadErrors}");
                            }
                        }
                    }
                }
                catch (Exception exc)
                {
                    currReadErrors++;
                    allOk = false;
                    lgError($"Eccezione in readMemBlockCoil  | startAddr: {startAddr} | baseAddr: {baseAddr} | numReg: {numReg} | currReadErrors: {currReadErrors} |{Environment.NewLine}{exc}");
                }
            }
            else
            {
                lgError($"readMemBlockCoil | Attenzione, PLC disconnesso... currReadErrors: {currReadErrors}");
                tryDisconnect();
            }
            return allOk;
        }

        /// <summary>
        /// Effettua lettura blocco memoria indicato e lo salva in copia locale in DiscreteInputLUT
        /// </summary>
        /// <param name="startAddr">Indirizzo di partenza</param>
        /// <param name="numReg">Num registri da leggere (max 120)</param>
        private bool readMemBlockDiscreteIn(int startAddr, int numReg, int baseAddr)
        {
            bool allOk = false;
            if (currPLC.Connected)
            {
                allOk = true;
                // fix massima lunghezza pacchetto
                if (numReg > 120)
                {
                    lgError($"Attenzione richiesta lettura blocco troppo grande: numReg {numReg} --> 120");
                    numReg = 120;
                }

                bool[] rawData = new bool[2];
                try
                {
                    sw.Restart();
                    int memAddr = startAddr - baseAddr;
                    rawData = currPLC.ReadDiscreteInputs(memAddr, numReg);
                    sw.Stop();
                    lgTrace($"Lettura in blocco DiscreteInput | startAddr: {startAddr} | numReg {numReg} | rawData.lenght: {rawData.Length} | {sw.ElapsedMilliseconds}ms");
                    // salvo statistica...
                    trackReadData(rawData.Length / 8, sw.Elapsed.TotalMilliseconds / 1000);
                    lastReadPLC = DateTime.Now;
                    // salvo in LUT i dati...
                    if (rawData.Length > 0)
                    {
                        for (int i = 0; i < rawData.Length; i++)
                        {
                            if (rawData.Length >= i)
                            {
                                // salvo nel registro...
                                int currAddr = startAddr + i;
                                if (DiscreteInputLUT.ContainsKey(currAddr))
                                {
                                    DiscreteInputLUT[currAddr] = rawData[i];
                                }
                                else
                                {
                                    DiscreteInputLUT.Add(currAddr, rawData[i]);
                                }
                            }
                            else
                            {
                                currReadErrors++;
                                allOk = false;
                                lgError($"DiscreteInputLUT | Impossibile copiare dati, array di origine troppo corto: rawData.lenght: {rawData.Length} | base addr richiesto i: {i} | errori currReadErrors: {currReadErrors}");
                            }
                        }
                    }
                }
                catch (Exception exc)
                {
                    currReadErrors++;
                    allOk = false;
                    lgError($"Eccezione in readMemBlockDiscreteIn | startAddr: {startAddr} | baseAddr: {baseAddr} | numReg: {numReg} | currReadErrors: {currReadErrors} |{Environment.NewLine}{exc}");
                }
            }
            else
            {
                lgError($"readMemBlockDiscreteIn | Attenzione, PLC disconnesso... currReadErrors: {currReadErrors}");
                tryDisconnect();
            }
            return allOk;
        }

        /// <summary>
        /// Effettua lettura blocco memoria indicato e lo salva in copia locale in HoldingRegisterLUT
        /// </summary>
        /// <param name="startAddr">Indirizzo di partenza</param>
        /// <param name="numReg">Num registri da leggere (max 120)</param>
        /// <param name="baseAddr">Indirizzo di base (da sottrarre all'indirizzo richiesto)</param>
        private bool readMemBlockHoldingReg(int startAddr, int numReg, int baseAddr)
        {
            bool allOk = false;
            if (currPLC.Connected)
            {
                allOk = true;
                // fix massima lunghezza pacchetto
                if (numReg > 120)
                {
                    lgError($"Attenzione richiesta lettura blocco troppo grande: numReg {numReg} --> 120");
                    numReg = 120;
                }
                // si lavora rispetto all'indirizzo base 40001
                int[] rawData = new int[2];
                try
                {
                    sw.Restart();
                    int memAddr = startAddr - baseAddr;
                    rawData = currPLC.ReadHoldingRegisters(memAddr, numReg);
                    sw.Stop();
                    lgTrace($"Lettura in blocco HoldingRegisters| startAddr: {startAddr} | numReg {numReg} | rawData.lenght: {rawData.Length} | {sw.Elapsed.TotalMilliseconds}ms");
                    // salvo statistica...
                    trackReadData(rawData.Length * 2, sw.Elapsed.TotalMilliseconds / 1000);
                    lastReadPLC = DateTime.Now;
                    // se risposta troppo rapida (< minRespTimeMs, tipicamente 5 msse non impostato in OptPar) indico NON ok e errori...
                    if (sw.Elapsed.TotalMilliseconds < minRespTimeMs)
                    {
                        allOk = false;
                        currReadErrors += 10;
                    }
                    // se leggo tutti zero --> indico +5 read error!
                    bool allZero = (rawData.Where(x => x > 0).Count() == 0);
                    if (allZero)
                    {
                        currReadErrors += 5;
                    }
                    // salvo in LUT la versione ESPLOSA 2 byte alla volta...
                    if (rawData.Length > 0)
                    {
                        for (int i = 0; i < rawData.Length / 2; i++)
                        {
                            int[] thisSet = new int[2];
                            if (rawData.Length >= (i + 1) * 2)
                            {
                                Array.Copy(rawData, i * 2, thisSet, 0, 2);
                                // salvo nel registro...
                                int currAddr = startAddr + i * 2;
                                if (HoldingRegisterLUT.ContainsKey(currAddr))
                                {
                                    HoldingRegisterLUT[currAddr] = thisSet;
                                }
                                else
                                {
                                    HoldingRegisterLUT.Add(currAddr, thisSet);
                                }
                            }
                            else
                            {
                                currReadErrors++;
                                allOk = false;
                                lgError($"Impossibile copiare dati, array di origine troppo corto: rawData.lenght: {rawData.Length} | base addr richiesto i*2: {i * 2} | errori currReadErrors: {currReadErrors}");
                            }
                        }
                    }
                }
                catch (Exception exc)
                {
                    // eccezione aggiunge 10 read errors!
                    currReadErrors += 10;
                    allOk = false;
                    lgError($"Eccezione in readMemBlockHoldingReg  | startAddr: {startAddr} | baseAddr: {baseAddr} | numReg: {numReg} | currReadErrors: {currReadErrors} |{Environment.NewLine}{exc}");
                }
            }
            else
            {
                lgError($"readMemBlockHoldingReg | Attenzione, PLC disconnesso... currReadErrors: {currReadErrors}");
                tryDisconnect();
            }
            // all ok: se non scattato verifico se errori < 50...
            if (allOk)
            {
                allOk = currReadErrors < 10;
            }
            return allOk;
        }

        /// <summary>
        /// Effettua lettura blocco memoria indicato e lo salva in copia locale in InputRegisterLUT
        /// </summary>
        /// <param name="startAddr">Indirizzo di partenza</param>
        /// <param name="numReg">Num registri da leggere (max 120)</param>
        private bool readMemBlockInputReg(int startAddr, int numReg, int baseAddr)
        {
            bool allOk = false;
            if (currPLC.Connected)
            {
                allOk = true;
                // fix massima lunghezza pacchetto
                if (numReg > 120)
                {
                    lgError($"Attenzione richiesta lettura blocco troppo grande: numReg {numReg} --> 120");
                    numReg = 120;
                }
                // si lavora rispetto all'indirizzo base 40001
                int[] rawData = new int[2];
                try
                {
                    sw.Restart();
                    int memAddr = startAddr - baseAddr;
                    rawData = currPLC.ReadInputRegisters(memAddr, numReg);
                    sw.Stop();
                    lgTrace($"Lettura in blocco InputRegisters| startAddr: {startAddr} | numReg {numReg} | rawData.lenght: {rawData.Length} | {sw.ElapsedMilliseconds}ms");
                    // salvo statistica...
                    trackReadData(rawData.Length * 2, sw.Elapsed.TotalMilliseconds / 1000);
                    lastReadPLC = DateTime.Now;
                    // salvo in LUT la versione ESPLOSA 2 byte alla volta...
                    if (rawData.Length > 0)
                    {
                        for (int i = 0; i < rawData.Length / 2; i++)
                        {
                            int[] thisSet = new int[2];
                            if (rawData.Length >= (i + 1) * 2)
                            {
                                Array.Copy(rawData, i * 2, thisSet, 0, 2);
                                // salvo nel registro...
                                int currAddr = startAddr + i * 2;
                                if (InputRegisterLUT.ContainsKey(currAddr))
                                {
                                    InputRegisterLUT[currAddr] = thisSet;
                                }
                                else
                                {
                                    InputRegisterLUT.Add(currAddr, thisSet);
                                }
                            }
                            else
                            {
                                currReadErrors++;
                                allOk = false;
                                lgError($"Impossibile copiare dati, array di origine troppo corto: rawData.lenght: {rawData.Length} | base addr richiesto i*2: {i * 2} | errori currReadErrors: {currReadErrors}");
                            }
                        }
                    }
                }
                catch (Exception exc)
                {
                    currReadErrors++;
                    allOk = false;
                    lgError($"Eccezione in readMemBlockInputReg  | startAddr: {startAddr} | baseAddr: {baseAddr} | numReg: {numReg} | currReadErrors: {currReadErrors} |{Environment.NewLine}{exc}");
                }
            }
            else
            {
                lgError($"readMemBlockInputReg | Attenzione, PLC disconnesso... currReadErrors: {currReadErrors}");
                tryDisconnect();
            }
            return allOk;
        }

        /// <summary>
        /// Verifica il tipo di memoria e di conseguenza effettua chiama la procedura di lettura
        /// </summary>
        /// <param name="startAddr">Indirizzo di partenza</param>
        /// <param name="numReg">Num registri da leggere (max 120)</param>
        private void readMemoryBlock(int startAddr, int numReg)
        {
            bool allOk = false;
            modbusMemType currMemType = modbusMemType.NotDefined;
            // valori setup secondo conf memoria: memoria estesa... 0 ... xFFFF per ogni set da 100'000...
            int maxVal = modbusExtReg ? 65536 : 10000;
            int baseMult = modbusExtReg ? 100000 : 10000;
            int baseAddr = parametri.holdRegBaseAddr;
            // calcolo tipo memoria
            currMemType = getMemType(startAddr, maxVal, baseMult);
            // in base al tipo di memoria procedo
            switch (currMemType)
            {
                case modbusMemType.Coil:
                    if (parametri.useCalcBaseAddr)
                    {
                        baseAddr = 0 * baseMult;
                    }
                    allOk = readMemBlockCoil(startAddr, numReg, baseAddr);
                    break;

                case modbusMemType.DiscreteInput:
                    if (parametri.useCalcBaseAddr)
                    {
                        baseAddr = 1 * baseMult;
                    }
                    allOk = readMemBlockDiscreteIn(startAddr, numReg, baseAddr);
                    break;

                case modbusMemType.InputRegister:
                    if (parametri.useCalcBaseAddr)
                    {
                        baseAddr = 3 * baseMult;
                    }
                    allOk = readMemBlockInputReg(startAddr, numReg, baseAddr);
                    break;

                case modbusMemType.HoldingRegister:
                    if (parametri.useCalcBaseAddr)
                    {
                        baseAddr = 4 * baseMult;
                    }
                    allOk = readMemBlockHoldingReg(startAddr, numReg, baseAddr);
                    break;

                case modbusMemType.NotDefined:
                default:
                    break;
            }

            // se tutto ok --> riduco contatore errori di 2...
            if (allOk)
            {
                currReadErrors = currReadErrors >= 2 ? currReadErrors - 2 : 0;
            }
            else
            {
                // se > max errori --> disconnetto
                if (currReadErrors > maxReadErrors)
                {
                    lgError($"Read Errors: Superato limite --> tryDisconnect | currReadErrors: {currReadErrors} | maxReadErrors: {maxReadErrors}");
                    currReadErrors = 0;
                    tryDisconnect();
                }
                else
                {
                    // altrimenti pausa forzata
                    Thread.Sleep(1000);
                }
            }
        }

        #endregion Private Methods
    }
}