using IOB_UT_NEXT;
using IOB_UT_NEXT.Config;
using MapoSDK;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading.Tasks;

namespace IOB_WIN_MITSUBISHI.Iob
{
    public class Mitsubishi : Iob.GenericNext
    {
        #region Public Constructors

        /// <summary>
        /// Estende l'init della classe base...
        /// </summary>
        /// <param name="caller">Form chiamante</param>
        /// <param name="IobConfFull">Configurazione (v 4.x)</param>
        public Mitsubishi(AdapterFormNext caller, IobConfTree IobConfFull) : base(caller, IobConfFull)
        {
            lgInfo("Start init Mitsubishi");
            // gestione invio ritardato contapezzi

            lastPzCountSend = DateTime.Now;
            lastWarnODL = DateTime.Now;

            // inizializzo correttamente aree memoria secondo CONF - da IobConfFull
            IOB_UT_NEXT.Config.Mem.MemAreaDto currArea = new IOB_UT_NEXT.Config.Mem.MemAreaDto();
            string aName = "AREAV";
            if (IobConfFull.Special.BankConf.AreaConf.ContainsKey(aName))
            {
                currArea = IobConfFull.Special.BankConf.AreaConf[aName];
                MemVarStart = currArea.Start;
                MemVarSize = currArea.Size;
            }
            else
            {
                MemVarStart = 500;
                MemVarSize = 1;
            }
            aName = "PAR";
            if (IobConfFull.Special.BankConf.AreaConf.ContainsKey(aName))
            {
                currArea = IobConfFull.Special.BankConf.AreaConf[aName];
                ParamStart = currArea.Start;
                ParamSize = currArea.Size;
            }
            else
            {
                ParamStart = 8001;
                ParamSize = 1;
            }
            aName = "SIGN";
            if (IobConfFull.Special.BankConf.AreaConf.ContainsKey(aName))
            {
                currArea = IobConfFull.Special.BankConf.AreaConf[aName];
                SignStart = currArea.Start;
                SignSize = currArea.Size;
            }
            else
            {
                SignStart = 10096;
                SignSize = 1;
            }


            // loggo aree di memoria avviate...
            lgInfo($"Avviata area di memoria Var: {MemVarSize} vars");
            lgInfo($"Avviata area di memoria Param: {ParamSize} params");

            // 2025.02.25 da verificare assegnazione DA CONF
            signLUT = new Dictionary<string, string>(IOBConfFull.Device.SigLUT);

            // recupero sysType del controllo, sennò default M700L...
            string sEZNC_SYS = getOptPar("EZNC_SYS");
            if (!string.IsNullOrEmpty(sEZNC_SYS))
            {
                sysType = (SYSTEMTYPE)Enum.Parse(typeof(SYSTEMTYPE), sEZNC_SYS);
            }
            lgInfo($"Start init Mitsubishi | SysType: {sysType}");

            // gestione override contapezzi
            disablePzCountByIob = (getOptPar("DISABLE_PZCOUNT") == "FALSE");


            if (!string.IsNullOrEmpty(getOptPar("MAX_CHAR_DESC")))
            {
                string MAX_CHAR_DESC = getOptPar("MAX_CHAR_DESC");
                int.TryParse(MAX_CHAR_DESC, out maxVarStrLen);
            }

            // è little endian (NON serve conversione)
            hasBigEndian = false;
            lgInfoStartup($"Start init Adapter MITSUBISHI | IP {IOBConfFull.Device.Connect.IpAddr}:{IOBConfFull.Device.Connect.Port} per IOB {IOBConfFull.General.CodIOB}");

            // Creo oggetto connessione NC
            parentForm.commPlcActive = true;

            // init oggetto
            oEZNcAutCom = new EZNCAUTLib.DispEZNcCommunication();
            int.TryParse(IOBConfFull.Device.Connect.Port, out ezPort);
            ipAddr = IOBConfFull.Device.Connect.IpAddr;

            parentForm.commPlcActive = false;

            // effettuo tryConnect...
            lgInfo("MITSUBISHI startup: tryConnect");
            tryConnect();
            if (enablePzCountByApp)
            {
                lgInfo("MITSUBISHI: inizio gestione contapezzi");
                try
                {
                    // verifico quale modalità sia richiesta: STD (6711) oppure BIT (Custom, con
                    // indicazione area)
                    if (!string.IsNullOrEmpty(IOBConfFull.Device.PzCountMode))
                    {
                        if (IOBConfFull.Device.PzCountMode.StartsWith("STD"))
                        {
                            lgInfo("Init contapezzi MITSUBISHI: pzCntReload(true)");
                            pzCntReload(true);
                            // refresh associazione Macchina - IOB
                            SendM2IOB();
                            // invio altri dati accessori...
                            SendMachineConf();
                            // per adesso imposto lettura MITSUBISHI == contapezzi (poi farà vera lettura...)
                            contapezziPLC = contapezziIOB;
                        }
                        else
                        {
                            contapezziIOB = 0;
                            lgInfo("Contapezzi STD disabilitato: modalità {0}", IOBConfFull.Device.PzCountMode);
                        }
                    }
                    else
                    {
                        contapezziIOB = 0;
                        lgInfo("Parametro mancante PZCOUNT_MODE");
                    }
                }
                catch (Exception exc)
                {
                    lgError(exc, "Errore in contapezzi MITSUBISHI 01");
                }
            }
            // finisco INIT ADAPTER
            lgInfo("End init Adapter MITSUBISHI");
        }

        #endregion Public Constructors

        #region Public Methods

        /// <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)
        {
            // Verificare il protocollo: dovrebbe togliere SOLO i task eseguiti...
            Dictionary<string, string> taskDone = new Dictionary<string, string>();
            if (task2exe != null)
            {
                bool taskOk = false;
                string taskVal = "";
                string newVal = "";
                string memAddr = "";
                long valLong = 0;
                // cerco task specifici: se ho startSetup --> imposto bit DBB701.DBB0.4
                foreach (var item in task2exe)
                {
                    taskOk = false;
                    taskVal = "";
                    // converto richiesta in enum...
                    taskType tName = taskType.nihil;
                    Enum.TryParse(item.Key, out tName);
                    switch (tName)
                    {
                        case taskType.setArt:
                        case taskType.setComm:
                            lgInfo($"Richiesta scrittura {tName}");
                            // salvo in memoria il valore richiesto...
                            if (memMap != null && memMap.mMapWrite != null)
                            {
                                if (memMap.mMapWrite.ContainsKey(item.Key))
                                {
                                    dataConf currMem = memMap.mMapWrite[item.Key];
                                    memAddr = currMem.memAddr;
                                    taskVal = $"SET task: {item.Key} --> {newVal} | mem: {currMem.memAddr} - {currMem.size} byte";
                                    // salvo il nuovo valore nella memoria... così prox invio lo trasmetterà
                                    memMap.mMapWrite[item.Key].value = newVal;
                                }
                                else
                                {
                                    taskVal = $"NO DATA MEM, SET task: {item.Key} --> {newVal} ({item.Value})";
                                }
                            }
                            else
                            {
                                taskVal = $"NO MemMap found, SET task: {item.Key} --> {newVal} ({item.Value})";
                            }
                            // salvo in currProd..
                            upsertKey(item.Key, item.Value);
                            break;

                        case taskType.nihil:
                        case taskType.fixStopSetup:
                        case taskType.forceResetPzCount:
                        case taskType.setProg:
                        case taskType.forceSetPzCount:
                        case taskType.sendWatchDogMes2Plc:
                            taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | SKIPPED | NO EXEC";
                            lgInfo($"Chiamata senza processing: taskOk: {taskOk} | taskVal: {taskVal}");
                            break;

                        case taskType.setArtNum:
                            lgInfo($"Richiesta scrittura CodART numerico");
                            // in primis faccio una chiamata per tutta la tab SE fosse vuoto il dict
                            // di traduzione
                            if (DictNumArt == null || DictNumArt.Count == 0)
                            {
                                getNumArt("");
                                lgInfo($"Recuperato DictNumArt, trovati {DictNumArt.Count} rec");
                            }
                            // chiamo server x avere decodifica valore INT
                            newVal = getNumArt(item.Value);
                            // se c'è gestione semplificata num articolo --> usa trim dei soli
                            // caratteri alfabetici lasciando numeri..
                            if (numArtCharTrim)
                            {
                                newVal = baseUtils.GetNumbers(item.Value);
                            }
                            // converto int...
                            long.TryParse(newVal, out valLong);
                            // procedo come il resto cercando mappatura in memMap: recupero dati da memMap...
                            if (memMap != null && memMap.mMapWrite != null)
                            {
                                if (memMap.mMapWrite.ContainsKey(item.Key))
                                {
                                    dataConf currMem = memMap.mMapWrite[item.Key];
                                    memAddr = currMem.memAddr;
                                    taskVal = $"SET task: {item.Key} --> {newVal} | mem: {currMem.memAddr} - {currMem.size} byte";
                                    // salvo il nuovo valore nella memoria... così prox invio lo trasmetterà
                                    memMap.mMapWrite[item.Key].value = newVal;
                                }
                                else
                                {
                                    taskVal = $"NO DATA MEM, SET task: {item.Key} --> {newVal} ({item.Value})";
                                }
                            }
                            else
                            {
                                taskVal = $"NO MemMap found, SET task: {item.Key} --> {newVal} ({item.Value})";
                            }

                            // invio a controller...
                            taskOk = setNumArt(valLong);
                            //se ho successo rileggo ed eventualmente sistemo valore
                            if (taskOk)
                            {
                                var rVal = getValByMemAddr(memAddr);
                                if (rVal == newVal)
                                {
                                    lgInfo("All OK");
                                    // invio aggiornamento x reset richiesta... test!
                                    sendOptVal(item.Key, newVal);
                                }
                                else
                                {
                                    lgError($"Error | memAddr: {memAddr} | rVal: {rVal} | newVal: {newVal}");
                                }
                            }
                            else
                            {
                                lgError("Errore in scrittura SetNumArt");
                            }
                            break;

                        case taskType.setCommNum:
                            lgInfo($"Richiesta scrittura cod COMMESSA numerica");
                            // chiamo server x avere decodifica valore INT
                            newVal = getNumComm(item.Value);
                            // converto int...
                            long.TryParse(newVal, out valLong);
                            //riscrivo senza eventuali zeri...
                            newVal = valLong > 0 ? $"{valLong}" : newVal;
                            lgTrace($"Conv.Int numComm | newVal: {newVal} | valLong: {valLong}");
                            // procedo come il resto cercando mappatura in memMap: recupero dati da memMap...
                            if (memMap != null && memMap.mMapWrite != null)
                            {
                                if (memMap.mMapWrite.ContainsKey(item.Key))
                                {
                                    dataConf currMem = memMap.mMapWrite[item.Key];
                                    memAddr = currMem.memAddr;
                                    taskVal = $"SET task: {item.Key} --> {newVal} | mem: {currMem.memAddr} - {currMem.size} byte";
                                    // salvo il nuovo valore nella memoria... così prox invio lo trasmetterà
                                    memMap.mMapWrite[item.Key].value = newVal;
                                }
                                else
                                {
                                    taskVal = $"NO DATA MEM, SET task: {item.Key} --> {newVal} ({item.Value})";
                                }
                            }
                            else
                            {
                                taskVal = $"NO MemMap found, SET task: {item.Key} --> {newVal} ({item.Value})";
                            }

                            // invio a controller...
                            taskOk = setNumCom(valLong);
                            //se ho successo rileggo ed eventualmente sistemo valore
                            if (taskOk)
                            {
                                var rVal = getValByMemAddr(memAddr);
                                if (rVal == newVal)
                                {
                                    lgInfo("All OK");
                                    // invio aggiornamento x reset richiesta... test!
                                    sendOptVal(item.Key, newVal);
                                }
                                else
                                {
                                    lgError($"Error | memAddr: {memAddr} | rVal: {rVal} | newVal: {newVal}");
                                }
                            }
                            else
                            {
                                lgError("Errore in scrittura SetNumCom");
                            }
                            break;

                        case taskType.setPzComm:
                            int pzReq = 0;
                            int.TryParse(item.Value, out pzReq);
                            // procedo come il resto cercando mappatura in memMap: recupero dati da memMap...
                            if (memMap != null && memMap.mMapWrite != null)
                            {
                                if (memMap.mMapWrite.ContainsKey(item.Key))
                                {
                                    dataConf currMem = memMap.mMapWrite[item.Key];
                                    memAddr = currMem.memAddr;
                                    taskVal = $"SET task: {item.Key} --> {newVal} | mem: {currMem.memAddr} - {currMem.size} byte";
                                    // salvo il nuovo valore nella memoria... così prox invio lo trasmetterà
                                    memMap.mMapWrite[item.Key].value = newVal;
                                }
                                else
                                {
                                    taskVal = $"NO DATA MEM, SET task: {item.Key} --> {newVal} ({item.Value})";
                                }
                            }
                            else
                            {
                                taskVal = $"NO MemMap found, SET task: {item.Key} --> {newVal} ({item.Value})";
                            }
                            // set pezzi richiesti inizio setup
                            taskOk = setPzComm(pzReq);
                            //se ho successo rileggo ed eventualmente sistemo valore
                            if (taskOk)
                            {
                                var rVal = getValByMemAddr(memAddr);
                                if (rVal == newVal)
                                {
                                    lgInfo("All OK");
                                    // invio aggiornamento x reset richiesta... test!
                                    sendOptVal(item.Key, newVal);
                                }
                                else
                                {
                                    lgError($"Error | memAddr: {memAddr} | rVal: {rVal} | newVal: {newVal}");
                                }
                                // salvo in currProd..
                                upsertKey(item.Key, item.Value);
                            }
                            else
                            {
                                lgError("Errore in scrittura SetNumCom");
                            }
                            taskVal = taskOk ? "RESET: SETUP START" : "PZ RESET DISABLED | NO EXEC";
                            lgInfo($"Chiamata startSetup: taskOk: {taskOk} | taskVal: {taskVal}");

                            break;

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

                        case taskType.stopSetup:
                            // reset contapezzi fine setup SE ESPLICITAMENTE IMPOSTATO
                            if (IOBConfFull.OptPar.Count > 0 && getOptPar("ENABLE_PZ_RESET_stopSetup") == "TRUE")
                            {
                                taskOk = resetContapezziPLC(codTav);
                            }
                            taskVal = taskOk ? "RESET: SETUP END" : "PZ RESET DISABLED | NO EXEC";
                            lgInfo($"Chiamata stopSetup: taskOk: {taskOk} | taskVal: {taskVal}");
                            break;

                        case taskType.setParameter:
                            lgInfo($"Chiamata setParameter | NO processing: taskOk: {taskOk} | taskVal: {taskVal}");
                            taskVal = "setParameter | SKIPPED | NO EXEC";
                            break;

                        default:
                            lgInfo($"Chiamata default senza processing: taskOk: {taskOk} | taskVal: {taskVal}");
                            taskVal = "SKIPPED | NO EXEC";
                            break;
                    }

                    // aggiungo task!
                    taskDone.Add(item.Key, taskVal);
                }
            }
            return taskDone;
        }

        /// <summary>
        /// Recupero dati dinamici...
        /// </summary>
        public override Dictionary<string, string> getDynData()
        {
            Dictionary<string, string> outVal = new Dictionary<string, string>();
            // processo SOLO SE connected...
            if (connectionOk)
            {
                // leggo i valori Var
                ReadMitsubVars();

                // cerco tra tutte le aree read le VARS...
                foreach (var item in memMap.mMapRead)
                {
                    if (item.Value.memAddr.StartsWith("VAR"))
                    {
                        // certo in dict ed accodo...
                        int iVar = 0;
                        string sVar = item.Value.memAddr.Replace("VAR.", "");
                        int.TryParse(sVar, out iVar);
                        if (iVar > 0)
                        {
                            // cerco in dizionario...
                            if (DictVar.ContainsKey(iVar))
                            {
                                // se NON presente...
                                if (!outVal.ContainsKey(item.Key))
                                {
                                    outVal.Add(item.Key, $"{DictVar[iVar]}");
                                }
                                upsertKeyLP(item.Key, $"{DictVar[iVar]}");
                                //upsertKey(item.Key, $"{DictVar[iVar]}");
                            }
                        }
                    }
                }
                // cerco tra tutte le aree write le VARS...
                foreach (var item in memMap.mMapWrite)
                {
                    if (item.Value.memAddr.StartsWith("VAR"))
                    {
                        // certo in dict ed accodo...
                        int iVar = 0;
                        string sVar = item.Value.memAddr.Replace("VAR.", "");
                        int.TryParse(sVar, out iVar);
                        if (iVar > 0)
                        {
                            // cerco in dizionario...
                            if (DictVar.ContainsKey(iVar))
                            {
                                // se NON presente...
                                if (!outVal.ContainsKey(item.Key))
                                {
                                    outVal.Add(item.Key, $"{DictVar[iVar]}");
                                }
                                //upsertKey(item.Key, $"{DictVar[iVar]}");
                                upsertKeyLP(item.Key, $"{DictVar[iVar]}");
                            }
                        }
                    }
                }
                // aggiungo DynData... calcolato come variazione pezzi e lampade...
                if (outVal.ContainsKey("DYNDATA"))
                {
                    outVal.Remove("DYNDATA");
                }
                StringBuilder sb = new StringBuilder();
                // inserisco i segnali
                sb.Append("SIGN:[");
                foreach (var item in DictSign)
                {
                    sb.Append($"{item.Key}:{item.Value}|");
                }
                // inserisco parametri
                sb.Append("] PAR:[");
                foreach (var item in DictParam)
                {
                    sb.Append($"{item.Key}:{item.Value}|");
                }
                // inserisco le variabili
                sb.Append("] VAR:[");
                foreach (var item in outVal)
                {
                    sb.Append($"{item.Value}|");
                }
                sb.Append("]");
                outVal.Add("DYNDATA", sb.ToString());
            }
            return outVal;
        }

        /// <summary>
        /// Recupero programma in lavorazione
        /// </summary>
        /// <returns></returns>
        public override string getPrgName()
        {
            string prgName = "";
            // recupero NUOVO prgName...
            try
            {
                int iRet = oEZNcAutCom.Program_GetProgramNumber2((Int32)PROGRAMTYPE.EZNC_MAINPRG, out String strProgramNo);

                // recupero nome programma MAIN
                prgName = utils.purgedChar2String(strProgramNo);
                // trimmo path del programma SE presente
                prgName = prgName.Replace(utils.CRS("basePrgMemPath"), "");
                lgInfo("Current PROG: {0}", prgName);
            }
            catch (Exception exc)
            {
                lgError(string.Format("Eccezione in recupero PRG NAME MAIN:{0}{1}", Environment.NewLine, exc));
                connectionOk = false;
            }
            return prgName;
        }

        /// <summary>
        /// Recupero info lavorazione come Dictionary...
        /// - SYSINFO: (prima KEY globale) TUTTI i valori separati da # (x fare check modifica)
        /// - altre stringhe: ogni singolo parametro / valore
        /// </summary>
        /// <returns></returns>
        public override Dictionary<string, string> getSysInfo()
        {
            Dictionary<string, string> outVal = new Dictionary<string, string>();
            sw.Restart();
            try
            {
                oEZNcAutCom.System_GetSystemInformation(0, out int partSys);
                outVal.Add("PartSys", $"{partSys}");
                oEZNcAutCom.System_GetSystemInformation(1, out int numAx);
                outVal.Add("NumAxis", $"{numAx}");
                oEZNcAutCom.System_GetSerialNo(out string serNo);
                outVal.Add("SerialNo", $"{serNo}");
                for (int i = 0; i < 3; i++)
                {
                    oEZNcAutCom.System_GetVersion(0, i, out string pVers);
                    outVal.Add($"V_{i}", $"{pVers}");
                }
            }
            catch (Exception exc)
            {
                lgError($"Eccezione in getSysInfo{Environment.NewLine}{exc}");
                connectionOk = false;
            }
            if (utils.CRB("recTime"))
            {
                TimingData.addResult(IOBConfFull.General.CodIOB, string.Format("SYS-INFO"), sw.ElapsedTicks);
            }
            return outVal;
        }

        /// <summary>
        /// Effettua vero processing contapezzi da configurazione
        /// PZCOUNT_MODE: pezzi lavorati (parziale)
        /// PZGTOT_MODE: pezzi lavorati totali
        /// PZREQ_MODE: pezzi richiesti
        /// </summary>
        public override void processContapezzi()
        {
            if (enablePzCountByApp)
            {
                // procedo SOLO SE ho connessione...
                if (connectionOk)
                {
                    try
                    {
                        // lettura parametri per procedere
                        ReadMitsubParams();
                        // controllo di AVERE parametri opzionali x conteggi vari
                        if (IOBConfFull.OptPar.Count > 0)
                        {
                            // contapezzi ATTUALE
                            string memAddr = IOBConfFull.Device.PzCountMode;
                            if (!string.IsNullOrEmpty(memAddr))
                            {
                                // verifico quale modalità sia richiesta: STD oppure CST (Custom, TBD)
                                if (memAddr.StartsWith("STD"))
                                {
                                    // inizio verifica area memoria/parametro levando prima parte codice
                                    memAddr = memAddr.Replace("STD.", "");
                                }

                                // verifico SE sia di tipo PAR e cerco valore...
                                int cntAddr = 0;
                                if (memAddr.StartsWith("PAR."))
                                {
                                    // recupero parametro...
                                    int.TryParse(memAddr.Replace("PAR.", ""), out cntAddr);
                                }
                                // cerco in dizionario...
                                if (DictParam.ContainsKey(cntAddr))
                                {
                                    string rawVal = DictParam[cntAddr];
                                    int newVal = -1;
                                    Int32.TryParse(rawVal, out newVal);
                                    contapezziPLC = newVal > -1 ? newVal : contapezziPLC;
                                }

                                sw.Stop();
                            }
                        }
                    }
                    catch (Exception exc)
                    {
                        lgError(exc, "Errore in contapezzi MITSUBISHI 02");
                        connectionOk = false;
                    }
                }
                else
                {
                    lgError("Errore: manca connessione in contapezzi MITSUBISHI");
                }
            }
        }

        /// <summary>
        /// Recupero altri counters se ci sono
        /// </summary>
        public override async Task processOtherCounters()
        {
            try
            {
                // lettura parametri per procedere
                ReadMitsubParams();

                // controllo di AVERE parametri opzionali x conteggi vari
                if (IOBConfFull.OptPar.Count > 0)
                {
                    // gestione con ricerca in memoria Write / optPar...
                    if (string.IsNullOrEmpty(memAddrPzReq))
                    {
                        lgTrace("PzReq disabilitato | NO memConf.Write | NO optPar");
                    }
                    else
                    {
                        await sendOptVal(memNamePzReq, getValByMemAddr(memAddrPzReq));
                    }
                    if (string.IsNullOrEmpty(memAddrArt))
                    {
                        lgTrace("Articolo disabilitato | NO memConf.Write | NO optPar");
                    }
                    else
                    {
                        await sendOptVal(memNameArt, getValByMemAddr(memAddrArt));
                    }
                    if (string.IsNullOrEmpty(memAddrCom))
                    {
                        lgTrace("Commessa disabilitata | NO memConf.Write | NO optPar");
                    }
                    else
                    {
                        await sendOptVal(memNameCom, getValByMemAddr(memAddrCom));
                    }
                    if (string.IsNullOrEmpty(memAddrPzCnt))
                    {
                        lgTrace("PzCount disabilitato | NO memConf.Write | NO optPar");
                    }
                    else
                    {
                        await sendOptVal(memNamePzCnt, getValByMemAddr(memAddrPzCnt));
                    }
                    if (string.IsNullOrEmpty(memAddrPzCntTot))
                    {
                        lgTrace("PzCntTOT disabilitato | NO memConf.Write | NO optPar");
                    }
                    else
                    {
                        await sendOptVal(memNamePzCntTot, getValByMemAddr(memAddrPzCntTot));
                    }
                    if (string.IsNullOrEmpty(memAddrCaden))
                    {
                        lgTrace("Cadenza disabilitata | NO memConf.Write | NO optPar");
                    }
                    else
                    {
                        await sendOptVal(memNameCaden, getValByMemAddr(memAddrCaden));
                    }
                }
            }
            catch (Exception exc)
            {
                lgError(exc, "Eccezione in processOtherCounters");
            }
        }

        /// <summary>
        /// Effettua lettura semafori principale <paramref name="currDispData">Parametri da
        /// aggiornare x display in form</paramref>
        /// </summary>
        public override void readSemafori(ref newDisplayData currDispData)
        {
            DateTime adesso = DateTime.Now;
            base.readSemafori(ref currDispData);
            // verifico non sia in veto invio iniziale...
            if (queueInEnabCurr)
            {
                try
                {
                    if (verboseLog)
                    {
                        lgInfo("inizio read semafori");
                    }

                    currDispData.semIn = Semaforo.SV;
                    ReadMitsubGenData();
                    // salvo il solo BYTE dell'input decifrando il semaforo...
                    decodeToBitmap();
                    reportRawInput(ref currDispData);
                }
                catch (Exception exc)
                {
                    lgError(string.Format("Eccezione in readSemafori:{0}{1}", Environment.NewLine, exc));
                    connectionOk = false;
                    currDispData.semIn = Semaforo.SR;
                }
            }
            else
            {
                lgDebug($"[VETO readSemafori] | veto attivo alle {adesso:yyyy.MM.dd HH:mm:ss}");
                checkVetoQueueIn();
            }
        }

        /// <summary>
        /// Effettua reset del contapezzi
        /// </summary>
        /// <returns></returns>
        public override bool resetContapezziPLC(string codTav)
        {
            bool answ = false;
            // ...SE abilitato da conf IOB
            if (getOptPar("ENABLE_PZ_RESET") == "TRUE")
            {
                // scrivo valore 0 x il contapezzi
                try
                {
                    // contapezzi ATTUALE
                    if (!string.IsNullOrEmpty(IOBConfFull.Device.PzCountMode))
                    {
                        int valReq = 0;
                        // scrivo valore 0 x il contapezzi
                        try
                        {
                            answ = writeMitsubMem("NumPz", memAddrPzCnt, valReq);
                        }
                        catch (Exception exc)
                        {
                            lgError($"Errore in SET NumPz MITSUBISHI{Environment.NewLine}{exc}");
                        }
                        answ = true;
                    }
                }
                catch (Exception exc)
                {
                    lgError(exc, "Errore in RESET contapezzi MITSUBISHI");
                    connectionOk = false;
                }
                answ = true;
            }
            return answ;
        }

        /// <summary>
        /// Effettua impostazione del valore numerico ARTICOLO (specifico MITSUBISHI)
        /// </summary>
        /// <returns></returns>
        public bool setNumArt(double valReq)
        {
            bool answ = false;
            string memAddr = memAddrArt;
            lgTrace($"INIT setNumArt | memAddr: {memAddr}");
            if (IOBConfFull.OptPar.Count > 0 && !string.IsNullOrEmpty(memAddr))
            {
                // scrivo valore richiesto in area configurata
                try
                {
                    answ = writeMitsubMem("CodArt", memAddr, valReq);
                    if (answ)
                    {
                        // salvo in currProd..
                        upsertKey("setArtNum", $"{valReq}");
                    }
                }
                catch (Exception exc)
                {
                    lgError(exc, "Errore in setNumArt MITSUBISHI");
                }
            }
            return answ;
        }

        /// <summary>
        /// Effettua impostazione del valore numerico COMMESSA (specifico MITSUBISHI)
        /// </summary>
        /// <returns></returns>
        public bool setNumCom(double valReq)
        {
            bool answ = false;
            // verifico quale modalità sia richiesta
            string memAddr = memAddrCom;
            lgTrace($"INIT setNumCom | memAddr: {memAddr}");
            if (IOBConfFull.OptPar.Count > 0 && !string.IsNullOrEmpty(memAddr))
            {
                lgTrace($"setNumCom | memAddr: {memAddr} | valReq: {valReq}");
                // scrivo valore richiesto in area configurata
                try
                {
                    answ = writeMitsubMem("ODL", memAddr, valReq);
                    if (answ)
                    {
                        // salvo in currProd..
                        upsertKey("setCommNum", $"{valReq}");
                    }
                }
                catch (Exception exc)
                {
                    lgError(exc, "Errore in setNumCom MITSUBISHI");
                }
                answ = true;
            }
            return answ;
        }

        /// <summary>
        /// Effettua impostazione del conteggio pezzi richiesti
        /// </summary>
        /// <returns></returns>
        public override bool setPzComm(int valReq)
        {
            bool answ = false;
            // ...SE abilitato da conf IOB
            if (IOBConfFull.OptPar.Count > 0 && IOBConfFull.Counters.EnableSetPzReq)
            {
                // scrivo valore 0 x il contapezzi
                try
                {
                    bool write1 = writeMitsubMem("NumPz", memAddrPzReq, valReq);
                    bool write2 = writeMitsubMem("PzOdl", memAddrPzReqOdl, valReq);
                    answ = write1 && write2;
                    if (write2)
                    {
                        // salvo in currProd..
                        upsertKey("setPzCommOdl", $"{valReq}");
                    }
                }
                catch (Exception exc)
                {
                    lgError($"Errore in SET NumPz MITSUBISHI{Environment.NewLine}{exc}");
                }
                answ = true;
            }
            return answ;
        }

        /// <summary>
        /// Override connessione
        /// </summary>
        public override void tryConnect()
        {
            if (!connectionOk)
            {
                // controllo che il ping sia stato tentato almeno pingTestSec fa...
                if (DateTime.Now.Subtract(lastPING).TotalSeconds > utils.CRI("pingTestSec"))
                {
                    if (verboseLog || periodicLog)
                    {
                        lgInfo("MITSUBISHI: ConnKO - tryConnect");
                    }
                    // in primis salvo data ping...
                    lastPING = DateTime.Now;
                    // ora PING!!!
                    Ping pingSender = new Ping();
                    IPAddress address = IPAddress.Loopback;
                    IPAddress.TryParse(IOBConfFull.Device.Connect.PingIpAddr, out address);
                    PingReply reply;
                    try
                    {
                        // se != null --> uso address...
                        if (address != null)
                        {
                            reply = pingSender.Send(address, 100);
                        }
                        else
                        {
                            reply = pingSender.Send(IOBConfFull.Device.Connect.IpAddr, 100);
                        }
                    }
                    catch
                    {
                        reply = pingSender.Send(IPAddress.Loopback, 100);
                    }
                    // se passa il ping faccio il resto...
                    if (reply.Status == IPStatus.Success)
                    {
                        string szStatusConnection = "";
                        try
                        {
                            // ora provo connessione...
                            parentForm.commPlcActive = true;

                            // Open communication
                            int iRet = 0;
                            oEZNcAutCom = new EZNCAUTLib.DispEZNcCommunication();
                            iRet = oEZNcAutCom.SetTCPIPProtocol(ipAddr, ezPort);
                            iRet = oEZNcAutCom.Open3((int)sysType, 1, 10, hostName);
                            parentForm.commPlcActive = false;
                            lgInfo($"Mitubishi | open Result: {iRet}");
                            connectionOk = iRet == 0;
                            connectionOk = true;
                            // refresh stato allarmi!!!
                            if (connectionOk)
                            {
                                checkVetoQueueIn();
                                dtAvvioAdp = DateTime.Now;
                                if (adpRunning)
                                {
                                    lgInfo("Connessione OK");
                                }
                            }
                            else
                            {
                                lgError("Impossibile procedere, connessione mancante...");
                            }
                        }
                        catch (Exception exc)
                        {
                            lgFatal(string.Format("Errore nella connessione all'adapter MITSUBISHI: {0}{1}{2}", szStatusConnection, Environment.NewLine, exc));
                            connectionOk = false;
                            lgInfo(string.Format("Eccezione in TryConnect, Adapter NON running, pausa di {0} msec prima di ulteriori tentativi di riconnessione", utils.CRI("waitRecMSec")));
                        }
                    }
                    else
                    {
                        // loggo no risposta ping ...
                        connectionOk = false;
                        if (verboseLog || periodicLog)
                        {
                            lgInfo($"Attenzione: controllo PING fallito per IP {IOBConfFull.Device.Connect.PingIpAddr} - {reply.Status}");
                        }
                    }
                }
            }
            // 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 (connectionOk)
            {
                string szStatusConnection = "";
                try
                {
                    // Close.
                    var iRet = oEZNcAutCom.Close();
                    //Release object
                    oEZNcAutCom = null;
                    connectionOk = false;
                    // resetto timing!
                    TimingData.resetData();
                    lgInfo(szStatusConnection);
                    lgInfo("Effettuata disconnessione adapter MITSUBISHI!");
                }
                catch (Exception exc)
                {
                    lgFatal(exc, "Errore nella disconnessione dall'adapter MITSUBISHI");
                }
            }
            else
            {
                lgError("IMPOSSIBILE effettuare disconnessione: Connessione non disponibile...");
            }
            queueInEnabCurr = false;
        }

        #endregion Public Methods

        #region Protected Fields

        /// <summary>
        /// Porta di comunicazione INT
        /// </summary>
        protected int ezPort = 683;

        /// <summary>
        /// Nome amcchina x connessione
        /// </summary>
        protected string hostName = "EZNC_LOCALHOST";

        /// <summary>
        /// Nome/IP del CN
        /// </summary>
        protected string ipAddr = "";

        /// <summary>
        /// Obj di comunicazione Mitsubishi
        /// </summary>
        protected EZNCAUTLib.DispEZNcCommunication oEZNcAutCom = null;

        #endregion Protected Fields

        #region Protected Methods

        /// <summary>
        /// Metodo da overridare x scrivere DAVVERO i parametri sul PLC
        /// </summary>
        /// <param name="updatedPar"></param>
        protected override void plcWriteParams(ref List<objItem> updatedPar)
        {
            lgTrace($"plcWriteParams: richiesta per {updatedPar.Count} params");
            foreach (var item in updatedPar)
            {
                lgInfo($"ITEM | {item.uid} | {item.value}");
                // effettuo scrittura SE configurata in write param...
                bool fatto = false;
                double valReq = 0;
                if (memMap != null && memMap.mMapWrite != null)
                {
                    if (memMap.mMapWrite.ContainsKey(item.uid))
                    {
                        if (!string.IsNullOrEmpty(item.reqValue))
                        {
                            double.TryParse(item.reqValue, out valReq);
                        }
                        else
                        {
                            double.TryParse(item.value, out valReq);
                        }
                        var currSet = memMap.mMapWrite[item.uid];
                        //se NON HA una vera area di scrittura lo do x fatto...
                        if (string.IsNullOrEmpty(currSet.memAddr))
                        {
                            fatto = true;
                            item.value = item.reqValue;
                        }
                        else
                        {
                            fatto = writeMitsubMem(currSet.description, currSet.memAddr, valReq);
                        }
                    }
                }
                // se i valori richiesto e fatto corrispondono... resetto!
                if (fatto && item.reqValue.Trim() == item.value.Trim())
                {
                    // salvo i valori ricevuti
                    upsertKey(item.uid, item.reqValue);
                    // resetto richiesta!
                    item.reqValue = "";
                }
            }
        }

        #endregion Protected Methods

        #region Private Fields

        private DateTime lastParRefr = DateTime.Now;

        private DateTime lastSigRefr = DateTime.Now;

        private DateTime lastVarRefr = DateTime.Now;

        /// <summary>
        /// Max num caratteri string ammessi VAR label
        /// </summary>
        private int maxVarStrLen = 7;

        /// <summary>
        /// Num celle Var da leggere
        /// </summary>
        private int MemVarSize = 1;

        /// <summary>
        /// Indirizzo partenza lettura Var(100/500)
        /// </summary>
        private int MemVarStart = 500;

        /// <summary>
        /// Num celle Param da leggere
        /// </summary>
        private int ParamSize = 1;

        /// <summary>
        /// Indirizzo partenza lettura Param
        /// </summary>
        private int ParamStart = 8000;

        /// <summary>
        /// LookUpTable di decodifica da CNC a segnali tipo bitmap MAPO
        /// </summary>
        private Dictionary<string, string> signLUT = new Dictionary<string, string>();

        /// <summary>
        /// Num celle Signal da leggere
        /// </summary>
        private int SignSize = 3;

        /// <summary>
        /// Indirizzo partenza lettura Signal
        /// </summary>
        private int SignStart = 10096;

        /// <summary>
        /// Tipo di controllo gestito
        /// </summary>
        private SYSTEMTYPE sysType = SYSTEMTYPE.EZNC_SYS_MELDAS700L;

        #endregion Private Fields

        #region Private Enums

        /// <summary>
        /// Tipo program
        /// </summary>
        private enum PROGRAMTYPE
        {
            /// <summary>
            /// Main Program メインプログラム
            /// </summary>
            EZNC_MAINPRG = 0,

            /// <summary>
            /// Sub Program サブプログラム
            /// </summary>
            EZNC_SUBPRG = 1,
        }

        /// <summary>
        /// Controllo specifico da gestire
        /// </summary>
        private enum SYSTEMTYPE
        {
            /// <summary>
            /// M700L
            /// </summary>
            EZNC_SYS_MELDAS700L = 5,

            /// <summary>
            /// M700M
            /// </summary>
            EZNC_SYS_MELDAS700M = 6,

            /// <summary>
            /// C70
            /// </summary>
            EZNC_SYS_MELDASC70 = 7,

            /// <summary>
            /// M800L
            /// </summary>
            EZNC_SYS_MELDAS800L = 8,

            /// <summary>
            /// M800M
            /// </summary>
            EZNC_SYS_MELDAS800M = 9,

            /// <summary>
            /// C80
            /// </summary>
            EZNC_SYS_CNCC80 = 10,
        }

        #endregion Private Enums

        #region Private Properties

        /// <summary>
        /// Dictionary contenuto memorie Param
        /// </summary>
        private Dictionary<int, string> DictParam { get; set; } = new Dictionary<int, string>();

        /// <summary>
        /// Dictionary contenuto dati Signal (es Y)
        /// </summary>
        private Dictionary<int, string> DictSign { get; set; } = new Dictionary<int, string>();

        /// <summary>
        /// Dictionary contenuto memorie Var
        /// </summary>
        private Dictionary<int, double> DictVar { get; set; } = new Dictionary<int, double>();

        private string memAddrArt
        {
            get
            {
                string memAddr = "";
                if (memMap != null && memMap.mMapWrite != null)
                {
                    //cerco in primis modalità NUM altrimenti standard...
                    if (memMap.mMapWrite.ContainsKey("setArtNum"))
                    {
                        memAddr = memMap.mMapWrite["setArtNum"].memAddr;
                    }
                    // se vuoto provo altra alternativa...
                    if (string.IsNullOrEmpty(memAddr) && memMap.mMapWrite.ContainsKey("setArt"))
                    {
                        memAddr = memMap.mMapWrite["setArt"].memAddr;
                    }
                }
                // se non trovato... leggo da OPT_PAR
                if (string.IsNullOrEmpty(memAddr))
                {
                    memAddr = getOptPar("SET_NUM_ART");
                }
                // bonifica variabile da definizione STD iniziale...
                if (memAddr.StartsWith("STD"))
                {
                    memAddr = memAddr.Replace("STD.", "");
                }
                return memAddr;
            }
        }

        private string memAddrCaden
        {
            get
            {
                string memAddr = "";
                if (memMap != null && memMap.mMapWrite != null)
                {
                    //cerco in primis modalità NUM altrimenti standard...
                    if (memMap.mMapWrite.ContainsKey("Cadenza"))
                    {
                        memAddr = memMap.mMapWrite["Cadenza"].memAddr;
                    }
                }
                // se non trovato... leggo da OPT_PAR
                if (string.IsNullOrEmpty(memAddr))
                {
                    memAddr = getOptPar("PZCAD_MODE");
                }
                // bonifica variabile da definizione STD iniziale...
                if (memAddr.StartsWith("STD"))
                {
                    memAddr = memAddr.Replace("STD.", "");
                }
                return memAddr;
            }
        }

        private string memAddrCom
        {
            get
            {
                string memAddr = "";
                if (memMap != null && memMap.mMapWrite != null)
                {
                    //cerco in primis modalità NUM altrimenti standard...
                    if (memMap.mMapWrite.ContainsKey("setCommNum"))
                    {
                        memAddr = memMap.mMapWrite["setCommNum"].memAddr;
                    }
                    // se vuoto provo altra alternativa...
                    if (string.IsNullOrEmpty(memAddr) && memMap.mMapWrite.ContainsKey("setComm"))
                    {
                        memAddr = memMap.mMapWrite["setComm"].memAddr;
                    }
                }
                // se non trovato... leggo da OPT_PAR
                if (string.IsNullOrEmpty(memAddr))
                {
                    memAddr = getOptPar("SET_NUM_COM");
                }
                // bonifica variabile da definizione STD iniziale...
                if (memAddr.StartsWith("STD"))
                {
                    memAddr = memAddr.Replace("STD.", "");
                }
                return memAddr;
            }
        }

        private string memAddrPzCnt
        {
            get
            {
                string memAddr = "";
                if (memMap != null && memMap.mMapWrite != null)
                {
                    //cerco in primis modalità NUM altrimenti standard...
                    if (memMap.mMapWrite.ContainsKey("ContatoreParziale"))
                    {
                        memAddr = memMap.mMapWrite["ContatoreParziale"].memAddr;
                    }
                }
                // se non trovato... leggo da OPT_PAR
                if (string.IsNullOrEmpty(memAddr))
                {
                    memAddr = IOBConfFull.Device.PzCountMode;
                }
                // bonifica variabile da definizione STD iniziale...
                if (memAddr.StartsWith("STD"))
                {
                    memAddr = memAddr.Replace("STD.", "");
                }
                return memAddr;
            }
        }

        private string memAddrPzCntTot
        {
            get
            {
                string memAddr = "";
                if (memMap != null && memMap.mMapWrite != null)
                {
                    //cerco in primis modalità NUM altrimenti standard...
                    if (memMap.mMapWrite.ContainsKey("ContatoreAssoluto"))
                    {
                        memAddr = memMap.mMapWrite["ContatoreAssoluto"].memAddr;
                    }
                }
                // se non trovato... leggo da OPT_PAR
                if (string.IsNullOrEmpty(memAddr))
                {
                    memAddr = getOptPar("PZGTOT_MODE");
                }
                // bonifica variabile da definizione STD iniziale...
                if (memAddr.StartsWith("STD"))
                {
                    memAddr = memAddr.Replace("STD.", "");
                }
                return memAddr;
            }
        }

        private string memAddrPzReq
        {
            get
            {
                string memAddr = "";
                if (memMap != null && memMap.mMapWrite != null)
                {
                    //cerco in primis modalità NUM altrimenti standard...
                    if (memMap.mMapWrite.ContainsKey("setPzComm"))
                    {
                        memAddr = memMap.mMapWrite["setPzComm"].memAddr;
                    }
                }
                // se non trovato... leggo da OPT_PAR
                if (string.IsNullOrEmpty(memAddr))
                {
                    memAddr = getOptPar("PZREQ_MODE");
                }
                // bonifica variabile da definizione STD iniziale...
                if (memAddr.StartsWith("STD"))
                {
                    memAddr = memAddr.Replace("STD.", "");
                }
                return memAddr;
            }
        }

        /// <summary>
        /// Area memoria PzOdl, NON abilitata in scrittura utente (è duplo/copia)
        /// </summary>
        private string memAddrPzReqOdl
        {
            get
            {
                string memAddr = "";
                //cerco prima in read area
                if (memMap != null && memMap.mMapRead != null)
                {
                    //cerco in primis modalità NUM altrimenti standard...
                    if (memMap.mMapRead.ContainsKey("setPzCommOdl"))
                    {
                        memAddr = memMap.mMapRead["setPzCommOdl"].memAddr;
                    }
                }
                // cerco anche in write se non trovata..
                if (string.IsNullOrEmpty(memAddr))
                {
                    if (memMap != null && memMap.mMapWrite != null)
                    {
                        //cerco in primis modalità NUM altrimenti standard...
                        if (memMap.mMapWrite.ContainsKey("setPzCommOdl"))
                        {
                            memAddr = memMap.mMapWrite["setPzCommOdl"].memAddr;
                        }
                    }
                }
                // bonifica variabile da definizione STD iniziale...
                if (memAddr.StartsWith("STD"))
                {
                    memAddr = memAddr.Replace("STD.", "");
                }
                return memAddr;
            }
        }

        private string memNameArt
        {
            get
            {
                string memName = "";
                if (memMap != null && memMap.mMapWrite != null)
                {
                    //cerco in primis modalità NUM altrimenti standard...
                    if (memMap.mMapWrite.ContainsKey("setArtNum"))
                    {
                        memName = "setArtNum";
                    }
                    // se vuoto provo altra alternativa...
                    if (string.IsNullOrEmpty(memName) && memMap.mMapWrite.ContainsKey("setArt"))
                    {
                        memName = "setArt";
                    }
                }
                // se non trovato... leggo da OPT_PAR
                if (string.IsNullOrEmpty(memName))
                {
                    memName = "PZ_COUNT";
                }
                return memName;
            }
        }

        private string memNameCaden
        {
            get
            {
                string memName = "";
                if (memMap != null && memMap.mMapWrite != null)
                {
                    //cerco in primis modalità NUM altrimenti standard...
                    if (memMap.mMapWrite.ContainsKey("Cadenza"))
                    {
                        memName = "Cadenza";
                    }
                }
                // se non trovato... leggo da OPT_PAR
                if (string.IsNullOrEmpty(memName))
                {
                    memName = "CICLE_CAD";
                }
                return memName;
            }
        }

        private string memNameCom
        {
            get
            {
                string memName = "";
                if (memMap != null && memMap.mMapWrite != null)
                {
                    //cerco in primis modalità NUM altrimenti standard...
                    if (memMap.mMapWrite.ContainsKey("setCommNum"))
                    {
                        memName = "setCommNum";
                    }
                    // se vuoto provo altra alternativa...
                    if (string.IsNullOrEmpty(memName) && memMap.mMapWrite.ContainsKey("setComm"))
                    {
                        memName = "setComm";
                    }
                }
                // se non trovato... leggo da OPT_PAR
                if (string.IsNullOrEmpty(memName))
                {
                    memName = "SET_NUM_COM";
                }
                return memName;
            }
        }

        private string memNamePzCnt
        {
            get
            {
                string memName = "";
                if (memMap != null && memMap.mMapWrite != null)
                {
                    //cerco in primis modalità NUM altrimenti standard...
                    if (memMap.mMapWrite.ContainsKey("ContatoreParziale"))
                    {
                        memName = "ContatoreParziale";
                    }
                }
                // se non trovato... leggo da OPT_PAR
                if (string.IsNullOrEmpty(memName))
                {
                    memName = "PZ_COUNT";
                }
                return memName;
            }
        }

        private string memNamePzCntTot
        {
            get
            {
                string memName = "";
                if (memMap != null && memMap.mMapWrite != null)
                {
                    //cerco in primis modalità NUM altrimenti standard...
                    if (memMap.mMapWrite.ContainsKey("ContatoreAssoluto"))
                    {
                        memName = "ContatoreAssoluto";
                    }
                }
                // se non trovato... leggo da OPT_PAR
                if (string.IsNullOrEmpty(memName))
                {
                    memName = "PZ_GTOT";
                }
                return memName;
            }
        }

        private string memNamePzReq
        {
            get
            {
                string memName = "";
                if (memMap != null && memMap.mMapWrite != null)
                {
                    //cerco in primis modalità NUM altrimenti standard...
                    if (memMap.mMapWrite.ContainsKey("setPzComm"))
                    {
                        memName = "setPzComm";
                    }
                }
                // se non trovato... leggo da OPT_PAR
                if (string.IsNullOrEmpty(memName))
                {
                    memName = "PZ_REQ";
                }
                return memName;
            }
        }

        #endregion Private Properties

        #region Private Methods

        /// <summary>
        /// Effettua decodifica aree memoria alla bitmap usata x MAPO
        /// </summary>
        private void decodeToBitmap()
        {
            /*------------------------------------------
             * Bitmap MAPO STD 54
             * B0: POWER_ON
             * B1: RUN
             * B2: pzCount
             * B3: allarme
             * B4: manuale
             * B5: Emerg (1 = emergenza, 0 = NON emergenza)
             *
             *------------------------------------------*/
            // init a zero...
            B_input = 0;
            if (connectionOk)
            {
                // vedere conf:
                // Y60: RED
                // Y61: YELLOW
                // Y62: GREEN
                // Y63: BLUE

                // default connesso = poweron, valutare se blue?
                B_input += 1 << 0;

                string bKey = "";
                string bVal = "";
                // valore INVERTED (default è false)
                bool invSignal = false;
                string cVal = "";
                int bitNum = 0;
                int byte2check = 0;
                for (int i = 0; i < 8; i++)
                {
                    bKey = $"BIT{i}";
                    // cerco se ci sia in LUT
                    if (signLUT.ContainsKey(bKey))
                    {
                        // recupero nome variabile...
                        bVal = signLUT[bKey];
                        // se l'area è PZCOUNT... processo PUNTUALMENTE il CONTAPEZZI...
                        if (bVal.StartsWith("PZCOUNT"))
                        {
                            // disabilito pzcount singolo
#if false
                            // procedo SOLO SE è enabled IOB
                            if (IobOnline)
                            {
                                try
                                {
                                    currODL = utils.callUrl(urlGetCurrODL);
                                    // solo SE HO un ODL...
                                    if (string.IsNullOrEmpty(currODL) || currODL == "0")
                                    {
                                        if (periodicLog)
                                        {
                                            lgInfo(string.Format("MITSUBISHI | Lettura ODL andata a vuoto: currODL: {0}", currODL));
                                        }
                                    }
                                    else
                                    {
                                        // se variato o scaduto timeout log...
                                        if (periodicLog || ($"{currIdxODL}" != currODL))
                                        {
                                            lgInfo(string.Format("MITSUBISHI | Lettura ODL, currODL: {0} --> currIdxODL prec: {1}", currODL, currIdxODL));
                                        }
                                        // provo a salvare nuovo ODL
                                        int.TryParse(currODL, out currIdxODL);
                                    }
                                }
                                catch (Exception exc)
                                {
                                    if (DateTime.Now.Subtract(lastWarnODL).TotalSeconds > 15)
                                    {
                                        lgError(exc, "Errore in fase di chiamata URL x ODL corrente | URL chiamato: {0}", urlGetCurrODL);
                                        lastWarnODL = DateTime.Now;
                                    }
                                }
                            }
                            else
                            {
                                // imposto currODL a vuoto!
                                currODL = "";
                                if (periodicLog)
                                {
                                    lgInfo($"MITSUBISHI | Lettura ODL non effettuata: IobOnline: {IobOnline} | currODL impostato a vuoto");
                                }
                            }
                            if (!string.IsNullOrEmpty(currODL) && currODL != "0")
                            {
                                // controllo se è passato intervallo minimo tra 2
                                // controlli/elaborazioni x distanziare invio e ridurre letture
                                if (DateTime.Now >= lastPzCountSend.AddMilliseconds(pzCountDelay))
                                {
                                    // se sono differenti MOSTRO...
                                    if (contapezziPLC != contapezziIOB)
                                    {
                                        // registro contapezzi
                                        lgInfo($"Differenza Contapezzi: contapezziPLC: {contapezziPLC} | contapezziIOB {contapezziIOB}");
                                    }

                                    if (enablePzCountByApp)
                                    {
                                        // verifico se variato contapezzi...
                                        if (contapezziPLC > contapezziIOB)
                                        {
                                            // salvo nuovo contapezzi (incremento di 1...) +
                                            // richiesta refresh conteggio
                                            contapezziIOB++;
                                            needRefreshPzCount = true;
                                            // salvo in semaforo!
                                            B_input += 1 << 2;
                                            // registro contapezzi
                                            lgInfo($"contapezziPLC MITSUBISHI: {contapezziPLC} | contapezziIOB {contapezziIOB}");
                                        }
                                        // invio a server contapezzi (aggiornato)
                                        string retVal = utils.callUrl(urlSetPzCount + contapezziIOB.ToString());
                                        // verifica se tutto OK
                                        if (retVal != contapezziIOB.ToString())
                                        {
                                            // errore salvataggio contapezzi
                                            lgInfo($"Errore salvataggio Contapezzi MITSUBISHI: contapezziPLC {contapezziPLC} | contapezziIOB {contapezziIOB} | risposta: {retVal}");
                                            // rileggo il counter pezzi da server
                                            pzCntReload(true);
                                        }
                                        // resetto timer...
                                        lastPzCountSend = DateTime.Now;
                                    }
                                }
                            }
                            else
                            {
                                if (DateTime.Now >= lastPzCountSend.AddMilliseconds(pzCountDelay))
                                {
                                    lgInfo($"Attenzione: mancanza ODL non procedo con gestione contapezzi. contapezziPLC MITSUBISHI: {contapezziPLC} | contapezziIOB {contapezziIOB}");
                                    // resetto timer...
                                    lastPzCountSend = DateTime.Now;
                                }
                            }
#endif
                        }
                        else // area "normale" byte.bit
                        {
                            // di norma è segnale normale => 1, altrimenti inverse => 0...
                            invSignal = false;
                            // cerco se sia inverse (primo char "!") --> registro e elimino char...
                            invSignal = bVal.StartsWith("!");
                            // se è da tracciare...
                            if (mem2trace.Contains($"|{bKey}|"))
                            {
                                if (invSignal)
                                {
                                    lgTrace($"Segnale invertito | bVal: {bVal}");
                                }
                            }
                            // tolgo comunque inversione...
                            bVal = bVal.Replace("!", "");

                            // recupero int key
                            int iKey = 0;
                            int.TryParse(bVal, out iKey);
                            // leggo dizionario richiesto
                            if (DictSign.ContainsKey(iKey))
                            {
                                cVal = DictSign[iKey];
                            }

                            // a secondo che sia segnale normale o inverso...
                            if (invSignal)
                            {
                                // controllo se il bit sia NON attivo (basso)... == 0
                                if (cVal == "0")
                                {
                                    B_input += 1 << i;
                                }
                            }
                            else
                            {
                                // controllo se il bit sia attivo (alto)... == 1
                                if (cVal == "1")
                                {
                                    B_input += 1 << i;
                                }
                            }

                            // se è da tracciare...
                            if (mem2trace.Contains($"|{bKey}|"))
                            {
                                var bMapAct = Convert.ToString(byte2check, 2).PadLeft(8, '0');
                                var bMapReq = Convert.ToString((1 << bitNum), 2).PadLeft(8, '0');
                                lgTrace($"Valore | bKey: {bKey} | bVal: {bVal} | byte: {bMapAct} | tgt: {bMapReq} | B_input: {B_input}");
                            }
                        }
                    }
                }
            }
            // log opzionale!
            if (verboseLog)
            {
                lgInfo(string.Format("Trasformazione B_input: {0}", B_input));
            }
        }

        /// <summary>
        /// Recupera il valore da memoria per successivo processing
        /// </summary>
        /// <param name="varName"></param>
        /// <returns></returns>
        private string getValByMemAddr(string memAddr)
        {
            lgTrace($"inizio getValByMemAddr | memAddr: {memAddr}");
            string answ = "";
            // verifico quale modalità sia richiesta: STD (6711) oppure BIT (Custom, con indicazione area)
            if (memAddr.StartsWith("STD"))
            {
                // inizio verifica area memoria/parametro levando prima parte codice
                memAddr = memAddr.Replace("STD.", "");
            }
            // var di appoggio
            int cntAddr = 0;
            // verifico se si tratta di lettura parametro... formato tipo STD.PAR.8001
            if (memAddr.StartsWith("PAR."))
            {
                // recupero parametro...
                int.TryParse(memAddr.Replace("PAR.", ""), out cntAddr);
                // cerco in dizionario...
                if (DictParam.ContainsKey(cntAddr))
                {
                    answ = DictParam[cntAddr];
                }
            }
            else if (memAddr.StartsWith("VAR."))
            {
                // recupero parametro...
                int.TryParse(memAddr.Replace("VAR.", ""), out cntAddr);
                // cerco in dizionario...
                if (DictVar.ContainsKey(cntAddr))
                {
                    answ = $"{DictVar[cntAddr]}";
                }
            }
            return answ;
        }

        /// <summary>
        /// Recupera il valore INT dal nome del parametro per successivo processing
        /// </summary>
        /// <param name="varName"></param>
        /// <returns></returns>
        private string getValByParam(string varName)
        {
            string memAddr = getOptPar(varName);
            string answ = getValByMemAddr(memAddr);
            if (isVerboseLog)
            {
                lgInfo($"[3] PAR letto: {varName} | {answ}");
            }
            return answ;
        }

        /// <summary>
        /// Lettura dati generici (es var Y)
        /// </summary>
        private void ReadMitsubGenData()
        {
            DateTime adesso = DateTime.Now;
            int parSPeriod = samplePeriod;
            // controllo se sia passato il samplePeriod minimo...
            if (adesso.Subtract(lastSigRefr.AddMilliseconds(parSPeriod)).TotalSeconds > 0)
            {
                lastSigRefr = adesso;
                sw.Restart();
                // in primis: leggo i valor in sequenza e salvo in dizionario...
                object pvRead = "bbb";
                // sezione segnali: cablata 53...
                int sec = 53;
                // Start da Y60 (Hex) --> 10000+96
                for (int i = SignStart; i < SignStart + SignSize; i++)
                {
                    oEZNcAutCom.Generic_ReadData(0, sec, i, ref pvRead);
                    UpsertSign(i, $"{pvRead}");
                    if (verboseLog)
                    {
                        lgTrace($"Sign | {i} : {pvRead}");
                    }
                }
                // recording tempi
                if (utils.CRB("recTime"))
                {
                    TimingData.addResult(IOBConfFull.General.CodIOB, $"Sign{SignSize}-R", sw.ElapsedTicks);
                }
                sw.Stop();
            }
            else
            {
                lgTrace($"Saltata ReadMitsubGenData x periodo minimo {parSPeriod} ms");
            }
        }

        /// <summary>
        /// Lettura area parametri mitsubishi come configurato
        /// </summary>
        private void ReadMitsubParams()
        {
            DateTime adesso = DateTime.Now;
            int parSPeriod = samplePeriod * 3;
            // controllo se sia passato il samplePeriod minimo...
            if (adesso.Subtract(lastParRefr.AddMilliseconds(parSPeriod)).TotalSeconds > 0)
            {
                lastParRefr = adesso;
                // per prima cosa processo la lettura parametri e li salvo in DICT
                int lAxis = 0;
                object parValue = "";
                sw.Restart();
                int iRet = oEZNcAutCom.Parameter_GetData3(0, ParamStart, ParamSize, lAxis, out parValue);
                if (utils.CRB("recTime"))
                {
                    TimingData.addResult(IOBConfFull.General.CodIOB, $"R{ParamSize}-PAR", sw.ElapsedTicks);
                }
                // converto in array di string...
                string[] rawData = (string[])parValue;
                // salvo in dict...
                for (int i = 0; i < ParamSize; i++)
                {
                    UpsertParam(ParamStart + i, rawData[i]);
                }
            }
            else
            {
                lgTrace($"Saltata ReadMitsubParams x periodo minimo {parSPeriod} ms");
            }
        }

        /// <summary>
        /// Lettura area Var100/Var500 mitsubishi come configurato
        /// </summary>
        private void ReadMitsubVars()
        {
            DateTime adesso = DateTime.Now;
            int parSPeriod = samplePeriod * 5;
            // controllo se sia passato il samplePeriod minimo...
            if (adesso.Subtract(lastVarRefr.AddMilliseconds(parSPeriod)).TotalSeconds > 0)
            {
                lastVarRefr = adesso;
                // per prima cosa processo la lettura Variabili e li salvo in DICT
                double pData = 0;
                int pType = 0;
                sw.Restart();
                for (int i = MemVarStart; i < MemVarStart + MemVarSize; i++)
                {
                    oEZNcAutCom.CommonVariable_Read2(i, out pData, out pType);
                    UpsertVar(i, pData);
                    if (verboseLog)
                    {
                        lgTrace($"Var | {i} : {pData}");
                    }
                }
                if (utils.CRB("recTime"))
                {
                    TimingData.addResult(IOBConfFull.General.CodIOB, $"R{MemVarSize}-VAR", sw.ElapsedTicks);
                }
            }
            else
            {
                lgTrace($"Saltata ReadMitsubVars x periodo minimo {parSPeriod} ms");
            }
        }

        /// <summary>
        /// Upsert info in DICT dei Params (es 8001..8003 contatori pezzi)
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        private void UpsertParam(int key, string value)
        {
            if (DictParam.ContainsKey(key))
            {
                DictParam[key] = value;
            }
            else
            {
                DictParam.Add(key, value);
            }
        }

        /// <summary>
        /// Upsert info in DICT dei SIGNAL (es Y)
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        private void UpsertSign(int key, string value)
        {
            if (DictSign.ContainsKey(key))
            {
                DictSign[key] = value;
            }
            else
            {
                DictSign.Add(key, value);
            }
        }

        /// <summary>
        /// Upsert info in DICT dei VAR (es Var100, Var500)
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        private void UpsertVar(int key, double value)
        {
            if (DictVar.ContainsKey(key))
            {
                DictVar[key] = value;
            }
            else
            {
                DictVar.Add(key, value);
            }
        }

        /// <summary>
        /// Scrive valore richiesto nella memoria richiesta
        /// </summary>
        /// <param name="memName"></param>
        /// <param name="memAddr"></param>
        /// <param name="valReq"></param>
        /// <returns></returns>
        private bool writeMitsubMem(string memName, string memAddr, double valReq)
        {
            bool answ = false;
            lgTrace($"INIT {memName} | memAddr: {memAddr}");
            // contapezzi ATTUALE
            if (!string.IsNullOrEmpty(memAddr))
            {
                if (memAddr.StartsWith("STD"))
                {
                    // inizio verifica area memoria/parametro levando prima parte codice
                    memAddr = memAddr.Replace("STD.", "");
                }
                // var di appoggio
                int cntAddr = 0;
                // verifico se si tratta di tipo VAR... formato tipo VAR.501
                if (memAddr.StartsWith("VAR."))
                {
                    // recupero parametro...
                    int.TryParse(memAddr.Replace("VAR.", ""), out cntAddr);
                    lgTrace($"VAR | {memName} Write 01 | memAddr: {memAddr} | idx: {cntAddr}");
                    // processo SET valore
                    sw.Restart();
                    if (cntAddr > 0)
                    {
                        // scrivo valore
                        int iRet = oEZNcAutCom.CommonVariable_Write2(cntAddr, valReq, 1);
                        // trimmo se necessario memName...
                        if (memName.Length > maxVarStrLen)
                        {
                            memName = memName.Substring(0, maxVarStrLen);
                        }
                        // scrivo label
                        iRet = oEZNcAutCom.CommonVariable_SetName(cntAddr, memName);
                        lgTrace($"VAR | {memName} Write 02 | memAddr: {memAddr} | valReq: {valReq}");
                        if (utils.CRB("recTime"))
                        {
                            TimingData.addResult(IOBConfFull.General.CodIOB, "VAR-W", sw.ElapsedTicks);
                        }
                        answ = iRet == 0;
                        // se successo --> rileggo!
                        if (answ)
                        {
                            ReadMitsubVars();
                        }
                    }
                    sw.Stop();
                }
                else if (memAddr.StartsWith("PAR."))
                {
                    // recupero parametro...
                    int.TryParse(memAddr.Replace("PAR.", ""), out cntAddr);
                    lgTrace($"PAR | {memName} Write 01 | memAddr: {memAddr} | idx: {cntAddr}");
                    // processo SET valore
                    sw.Restart();
                    if (cntAddr > 0)
                    {
                        // scrivo pz req come PAR...
                        int lAxis = 0;
                        string[] sValue = new string[1];
                        // cablato: zero decimali...
                        sValue[0] = $"{valReq:N0}";
                        int iRet = oEZNcAutCom.Parameter_SetData3(0, cntAddr, lAxis, (object)sValue);
                        lgTrace($"PAR | {memName} Write 02 | memAddr: {memAddr} | valReq: {valReq}");
                        if (utils.CRB("recTime"))
                        {
                            TimingData.addResult(IOBConfFull.General.CodIOB, "PAR-W", sw.ElapsedTicks);
                        }
                        answ = iRet == 0;
                        // se successo --> rileggo!
                        if (answ)
                        {
                            ReadMitsubParams();
                        }
                    }
                    sw.Stop();
                }
            }
            return answ;
        }

        #endregion Private Methods
    }
}