using IOB_UT_NEXT;
using IOB_UT_NEXT.Config;
using MapoSDK;
using Newtonsoft.Json;
using S7.Net;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading;

namespace IOB_WIN_SIEMENS.IobSiemens
{
    public class Siemens : Iob.GenericNext, IDisposable
    {
        /* --------------------------------------------------------------------------------
         * Controlli SIEMENS tramite libreria S7.net
         * - basasto su SIEMENS
         * - S7 testato vers 300 e 1200
         * - attenzione conf rack/slot x varie serie controlli
         * - necessità apertura metodi put/get
         * -------------------------------------------------------------------------------- */

        #region Public Fields

        /// <summary>
        /// Byte dimensione buffer dati memoria (da file map)
        /// </summary>
        public int numByte = 0;

        #endregion Public Fields

        #region Public Constructors

        /// <summary>
        /// Classe base con i metodi x Siemens
        /// </summary>
        /// <param name="caller">Form chiamante</param>
        /// <param name="IobConfFull">Configurazione (v 4.x)</param>
        public Siemens(AdapterFormNext caller, IobConfTree IobConfFull) : base(caller, IobConfFull)
        {
            memMap = new plcMemMapExt();
            writePre = true;
            if (IOBConfFull.OptPar.ContainsKey("WRITE_PRE"))
            {
                bool.TryParse(IOBConfFull.OptPar["WRITE_PRE"], out writePre);
                lgInfo($"Override Write pre da conf: {IOBConfFull.OptPar["WRITE_PRE"]} --> {writePre}");
            }
            if (IOBConfFull != null)
            {
                // gestione invio ritardato contapezzi

                lastPzCountSend = DateTime.Now;
                lastWarnODL = DateTime.Now;
                // inizializzo parametri...
                parametri = new connParamS7()
                {
                    ipAdrr = "127.0.0.1",
                    tipoCpu = CpuType.S7200,
                    slot = 0,
                    rack = 0,
                    pingMsTimeout = IOBConfFull.Device.Connect.PingMsTimeout,
                    memAddrRead = "DB1.DBB0",
                    memAddrWrite = "DB2.DBB0",
                    memSizeRead = 0,
                    memSizeWrite = 0
                };
                setParamPlc();

                // sistemo variabili varie (es contapezzi)
                memAddrPzCount = IOBConfFull.Device.PzCountMode;
                memAddrPzQCtrl = getOptPar("PZQCTRL_MODE");
                disableByIob = (getOptPar("DISABLE_PZCOUNT") == "TRUE");
                enableContapezzi = utils.CRB("enableContapezzi");

                // 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!");
            }
        }

        #endregion Public Constructors

        #region Public Methods

        /// <summary>
        /// Converte direttamente un valore Int su un oggetto byte[4]
        /// </summary>
        /// <param name="valore">valore da scrivere</param>
        public byte[] dintToByte(string valore)
        {
            byte[] answ = new byte[4];
            try
            {
                int valInt = 0;
                int.TryParse(valore, out valInt);
                byte[] strByte = S7.Net.Types.DInt.ToByteArray(valInt);
                int byteLen = 4;
                Buffer.BlockCopy(strByte, 0, answ, 0, byteLen);
            }
            catch (Exception exc)
            {
                lgError($"Errore in gestione scrittura DINT {valore} in byte{Environment.NewLine}{exc}");
            }
            return answ;
        }

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

        /// <summary>
        /// Converte direttamente un valore UInt32 su un oggetto byte[4]
        /// </summary>
        /// <param name="valore">valore da scrivere</param>
        public byte[] dwordToByte(string valore)
        {
            byte[] answ = new byte[4];
            try
            {
                ushort valInt = 0;
                ushort.TryParse(valore, out valInt);
                byte[] strByte = S7.Net.Types.DWord.ToByteArray(valInt);
                int byteLen = 4;
                Buffer.BlockCopy(strByte, 0, answ, 0, byteLen);
            }
            catch (Exception exc)
            {
                lgError($"Errore in gestione scrittura DINT {valore} in byte{Environment.NewLine}{exc}");
            }
            return answ;
        }

        /// <summary>
        /// Recupero dati dinamici...
        /// </summary>
        public override Dictionary<string, string> getDynData()
        {
            // 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 Siemens PLC, vettore memoria troppo piccolo: {RawInput.Length} byte / {parametri.memSizeRead} byte presenti/richiesti)");
                }
                else
                {
                    try
                    {
                        // processo x ogni valore configurato...
                        if (memMap.mMapRead.Count > 0)
                        {
                            // inizializzo i valori
                            bool valBool = false;
                            double valore = 0;
                            string valString = "";
                            // procedo x ogni valore configurato......
                            foreach (var item in memMap.mMapRead)
                            {
                                // in primis DEVO determinare di quale TIPO di valore ho bisogno...
                                switch (item.Value.tipoMem)
                                {
                                    case plcDataType.Boolean:
                                        valBool = S7.Net.Types.Boolean.GetValue(RawInput[item.Value.index], item.Value.size);
                                        break;

                                    case plcDataType.Int:
                                        valore = ((double)S7.Net.Types.Int.FromByteArray(RawInput.Skip(item.Value.index).Take(item.Value.size).ToArray())) / item.Value.factor;
                                        saveValue(ref outVal, item.Key, valore);
                                        break;

                                    case plcDataType.DInt:
                                        valore = ((double)S7.Net.Types.DInt.FromByteArray(RawInput.Skip(item.Value.index).Take(item.Value.size).ToArray())) / item.Value.factor;
                                        saveValue(ref outVal, item.Key, valore);
                                        break;

                                    case plcDataType.Word:
                                        valore = ((double)S7.Net.Types.Word.FromByteArray(RawInput.Skip(item.Value.index).Take(item.Value.size).ToArray())) / item.Value.factor;
                                        saveValue(ref outVal, item.Key, valore);
                                        break;

                                    case plcDataType.DWord:
                                        valore = ((double)S7.Net.Types.DWord.FromByteArray(RawInput.Skip(item.Value.index).Take(item.Value.size).ToArray())) / item.Value.factor;
                                        saveValue(ref outVal, item.Key, valore);
                                        break;

                                    case plcDataType.Real:
                                        valore = S7.Net.Types.Double.FromByteArray(RawInput.Skip(item.Value.index).Take(item.Value.size).ToArray()) / item.Value.factor;
                                        saveValue(ref outVal, item.Key, valore);
                                        break;

                                    case plcDataType.String:
                                        valString = S7.Net.Types.String.FromByteArray(RawInput.Skip(item.Value.index).Take(item.Value.size).ToArray());
                                        saveValueString(ref outVal, item.Key, valString);
                                        break;

                                    case plcDataType.Byte:
                                    case plcDataType.BitMap:
                                        byte[] byteState = RawInput.Skip(item.Value.index).Take(item.Value.size).ToArray();
                                        // calcolo valore string come unione delle bitmap attive...
                                        valString = getBitmapState(item.Value, byteState);
                                        // se vuoto segno "OK"
                                        valString = string.IsNullOrEmpty(valString) ? "OK" : valString;
                                        saveValueString(ref outVal, item.Key, valString);
                                        break;

                                    default:
                                        break;
                                }
                            }
                        }
                        else
                        {
                            lgInfo($"getDynData: {memMap.mMapRead.Count} record in mMapRead");
                        }
                    }
                    catch (Exception exc)
                    {
                        lgError(exc, "Errore in getDynData x Siemens PLC");
                    }
                }
            }
            else
            {
                lgInfo($"Non processo getDynData: enableTSVC = false");
            }
            if (periodicLog || outVal.Count > 0)
            {
                lgInfo($"Esito getDynData: {outVal.Count} valori VALIDI in outVal");
            }
            return outVal;
        }

        /// <summary>
        /// Recupero programma in lavorazione
        /// </summary>
        /// <returns></returns>
        public override string getPrgName()
        {
            // valore non presente in vers default... se gestito fare override
            string prgName = "";
            return prgName;
        }

        /// <summary>
        /// Recupero programma in lavorazione come Dictionary FANUC...
        /// - 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()
        {
            // valore non presente in vers default... se gestito fare override
            Dictionary<string, string> outVal = new Dictionary<string, string>();
            return outVal;
        }

        /// <summary>
        /// Converte direttamente un valore Short su un oggetto byte[2]
        /// </summary>
        /// <param name="valore">valore da scrivere</param>
        public byte[] intToByte(string valore)
        {
            byte[] answ = new byte[2];
            try
            {
                short valInt = 0;
                short.TryParse(valore, out valInt);
                byte[] strByte = S7.Net.Types.Int.ToByteArray(valInt);
                int byteLen = 2;
                Buffer.BlockCopy(strByte, 0, answ, 0, byteLen);
            }
            catch (Exception exc)
            {
                lgError($"Errore in gestione scrittura INT {valore} in byte{Environment.NewLine}{exc}");
            }
            return answ;
        }

        /// <summary>
        /// Effettua vero processing contapezzi
        /// </summary>
        public override void processContapezzi()
        {
            if (!rawInputRead)
            {
                lgDebug($"Veto processContapezzi: rawInput non acora acquisito");
            }
            else
            {
                if (enableContapezzi && !disableByIob)
                {
                    try
                    {
                        // verifico quale modalità sia richiesta: STD (6711) oppure BIT (Custom, con
                        // indicazione area)
                        int pzCountBuoni = -1;
                        int pzCountQCtrl = -1;
                        int pzCountAll = 0;
                        string strOutValPzBuoni = "";
                        string strOutValPzQCtrl = "";

                        if (!string.IsNullOrEmpty(memAddrPzCount))
                        {
                            strOutValPzBuoni = readCountVal(memAddrPzCount);

                            // verifico valori letti
                            if (!string.IsNullOrEmpty(strOutValPzBuoni))
                            {
                                // salvo...
                                Int32.TryParse(strOutValPzBuoni, out pzCountBuoni);
                                // e sommo (SE sono > -1)
                                if (pzCountBuoni >= 0)
                                {
                                    pzCountAll += pzCountBuoni;
                                }
                            }
                        }
                        if (!string.IsNullOrEmpty(memAddrPzQCtrl))
                        {
                            strOutValPzQCtrl = readCountVal(memAddrPzQCtrl);

                            // verifico valori letti
                            if (!string.IsNullOrEmpty(strOutValPzQCtrl))
                            {
                                // salvo...
                                Int32.TryParse(strOutValPzQCtrl, out pzCountQCtrl);
                                // e sommo (SE sono > -1)
                                if (pzCountQCtrl >= 0)
                                {
                                    pzCountAll += pzCountQCtrl;
                                }
                            }
                        }

                        // proseguo SOLO SE ho un pzCount > 0...
                        if (pzCountAll >= 0)
                        {
                            // 2025.02.26 aggiunto controllo che non sia appena avviato IOB
                            bool isJustStarted = DateTime.Now.Subtract(prgStarted).TotalMinutes < 3;
                            // verifico il NUOVO contapezzi PRIMA di accettare ogni incremento...
                            int deltaPzCount = pzCountAll - contapezziPLC;
                            double maxDelta = DateTime.Now.Subtract(plcLastPzRead).TotalMinutes / (plcAvgTc / 60);
                            // se incremento superiore del doppio atteso e NON parto da zero pezzi = appena avviato --> segnalo errore e NON accetto
                            if (deltaPzCount > maxDelta * (maxPzDeltaPerc / 100) && !isJustStarted)
                            {
                                lgError($"[DELTA CHECK]: intremento contapezziPLC troppo elevato: lettura {pzCountAll} | contapezzi attuale: {contapezziPLC} | ultima lettura PLC: {plcLastPzRead} | TCiclo medio: {plcAvgTc}s | incremento accettato ");
                            }
                            else
                            {
                                contapezziPLC = (pzCountAll >= 0 ? pzCountAll : contapezziPLC);
                                if (isVerboseLog)
                                {
                                    lgInfo($"[2] pzBuoni: {strOutValPzBuoni} | pzCQ: {strOutValPzQCtrl}");
                                    lgInfo($"[3] contapezziPLC: {contapezziPLC}");
                                }
                            }
                        }
                    }
                    catch (Exception exc)
                    {
                        lgError(exc, "Errore in contapezzi SIEMENS");
                    }
                }
            }
        }

        /// <summary>
        /// Esegue processing MODE (e nel contempo recupera altri dati dell'area G)
        /// </summary>
        public override void processMode()
        {
            // valore non presente in vers default... se gestito fare override
            if (utils.CRB("enableMode"))
            {
            }
        }

        /// <summary>
        /// Indica che la lettura dati da INPUT è avvenuta (almeno una prima volta)
        /// </summary>
        protected bool rawInputRead = false;

        /// <summary>
        /// Effettua lettura semafori principale
        /// <paramref name="currDispData">Parametri da aggiornare x display in form</paramref>
        /// </summary>
        public override void readSemafori(ref newDisplayData currDispData)
        {
            base.readSemafori(ref currDispData);
            try
            {
                currDispData.semIn = Semaforo.SV;

                if (verboseLog)
                {
                    lgInfo("inizio read semafori");
                }
                // leggo TUTTI i byte configurati...
                byte[] MemBlock = new byte[parametri.memSizeRead];
                rawInputRead = S7ReadBB(ref MemBlock);
                Buffer.BlockCopy(MemBlock, 0, RawInput, 0, parametri.memSizeRead);
                if (verboseLog)
                {
                    lgInfo(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
            {
                currDispData.semIn = Semaforo.SR;
            }
        }

        /// <summary>
        /// wrapper chiamata LETTURA in blocco MULTI BYTE dell'area read DI DEFAULT...
        /// </summary>
        /// <param name="MATRICE valori letti"></param>
        /// <returns></returns>
        public bool S7ReadBB(ref byte[] Value)
        {
            return S7ReadBB(ref Value, parametri.memAddrRead, parametri.memSizeRead);
        }

        /// <summary>
        /// wrapper chiamata LETTURA in blocco MULTI BYTE...
        /// </summary>
        /// <param name="Value">MATRICE valori letti</param>
        /// <param name="memAddrRead">Area memoria da leggere...</param>
        /// <param name="numByte">Numero byte da leggere</param>
        /// <returns></returns>
        public bool S7ReadBB(ref byte[] Value, string memAddrRead, int numByte)
        {
            bool answ = false;
            if (Value != null)
            {
                sw.Restart();
                parentForm.commPlcActive = true;
                if (testCncConn())
                {
                    // decodifico memoria...
                    memAreaSiemens memoria = new memAreaSiemens(memAddrRead);
                    Byte[] memByteRead = currPLC.ReadBytes(DataType.DataBlock, memoria.DbNum, memoria.indiceMem, numByte);
                    // copio in value, sennò do errore...
                    if (memByteRead.Length == Value.Length)
                    {
                        Value = memByteRead;
                        answ = true;
                    }
                    else
                    {
                        lgError($"Mismatch dimensione array memoria: indirizzo: {memAddrRead} | passato array di {Value.Length} byte, letti da S7 {memByteRead.Length} byte");
                        numErroriCheck++;
                    }
                    // loggo lettura...
                    if (verboseLog)
                    {
                        StringBuilder sb = new StringBuilder();
                        sb.AppendLine(lineSep);
                        sb.AppendLine($"READ BLOCK MEM BYTE: {parametri.memAddrRead} --> {numByte} byte");
                        sb.AppendLine($"Contenuto area memoria acquisita:");
                        string byteVal = "";
                        for (int i = 0; i < memByteRead.Length; i++)
                        {
                            byteVal = Convert.ToString(memByteRead[i], 2).PadLeft(8, '0');
                            sb.AppendLine($"B{i:000}: {byteVal} | {memByteRead[i]}");
                        }
                        sb.AppendLine(lineSep);
                        lgInfo(sb.ToString());
                    }
                }
                else
                {
                    connectionOk = false;
                }
                parentForm.commPlcActive = false;
                sw.Stop();
                if (utils.CRB("recTime"))
                {
                    TimingData.addResult(IOBConfFull.General.CodIOB, string.Format("{0}|{1}", parametri.memAddrRead, numByte), sw.ElapsedTicks);
                }
            }
            // se supero soglia errori lettura --> disconnetto e resetto
            if (numErroriCheck > maxErroriCheck)
            {
                lgInfo($"numErroriCheck: {numErroriCheck} --> richiesta disconnessione adapter con tryDisconnect");
                numErroriCheck = 0;
                tryDisconnect();
            }

            return answ;
        }

        /// <summary>
        /// wrapper chiamata LETTURA in blocco MULTI BYTE... default size a parametri.memSizeRead
        /// </summary>
        /// <param name="MATRICE valori letti"></param>
        /// <param name="memAddrRead">Area memoria da leggere...</param>
        /// <returns></returns>
        public bool S7ReadBB(ref byte[] Value, string memAddrRead)
        {
            bool answ = false;
            answ = S7ReadBB(ref Value, memAddrRead, parametri.memSizeRead);
            return answ;
        }

        /// <summary>
        /// wrapper chiamata SCRITTURA in blocco MULTI BYTE, DI DEFAULT su area configurata x
        /// scrittura CONTINUA...
        /// </summary>
        /// <param name="MATRICE valori scritti"></param>
        /// <returns></returns>
        public bool S7WriteBB(ref byte[] Value)
        {
            return S7WriteBB(ref Value, parametri.memAddrWrite);
        }

        /// <summary>
        /// Override scrittura in area DBB
        /// </summary>
        /// <param name="Value"></param>
        /// <param name="memAddrWrite"></param>
        /// <returns></returns>
        public bool S7WriteBB(ref byte[] Value, string memAddrWrite)
        {
            bool answ = false;
            if (Value == null)
            {
                lgError($"Errore in S7WriteBB: Value = null");
            }
            else
            {
                if (string.IsNullOrEmpty(memAddrWrite))
                {
                    lgError($"Errore in S7WriteBB: memAddrWrite = vuoto");
                }
                else
                {
                    sw.Restart();
                    if (testCncConn())
                    {
                        try
                        {
                            // decodifico memoria...
                            memAreaSiemens memoria = new memAreaSiemens(memAddrWrite);
                            int numByte = Value.Length;
                            ErrorCode errorCode = currPLC.WriteBytes(DataType.DataBlock, memoria.DbNum, memoria.indiceMem, Value);
                            switch (errorCode)
                            {
                                case ErrorCode.NoError:
                                    answ = true;
                                    maybeLogWrite(memAddrWrite, $"S7WriteBB-01 Effettuata correttamente scrittura su PLC: MEMORIA {memAddrWrite} | numByte: {Value.Length} | ValOriginale: {BitConverter.ToString(Value)}");
                                    break;

                                case ErrorCode.WrongCPU_Type:
                                case ErrorCode.ConnectionError:
                                case ErrorCode.IPAddressNotAvailable:
                                case ErrorCode.WrongVarFormat:
                                case ErrorCode.WrongNumberReceivedBytes:
                                case ErrorCode.SendData:
                                case ErrorCode.ReadData:
                                case ErrorCode.WriteData:
                                    lgError($"Errore in S7WriteBB su {memAddrWrite}: {errorCode.ToString()} | numByte: {Value.Length}| {Value.ValToBinString()}");
                                    answ = false;
                                    break;

                                default:
                                    break;
                            }
                        }
                        catch (Exception exc)
                        {
                            lgError($"Eccezione in S7WriteBB | memAddrWrite {memAddrWrite} | numByte: {Value.Length}{Environment.NewLine}{exc}");
                        }
                    }
                    sw.Stop();
                }
            }
            return answ;
        }

        /// <summary>
        /// Override scrittura in area DBB
        /// </summary>
        /// <param name="Value">Valore byte[] da scrivere</param>
        /// <param name="DbNum">Numero del DB (es 700 per DB700)</param>
        /// <param name="IndiceMem">Indice interno al datablock del byte da cui partire</param>
        /// <returns></returns>
        public bool S7WriteBB(ref byte[] Value, int DbNum, int IndiceMem)
        {
            bool answ = false;
            if (Value == null)
            {
                lgError($"Errore in S7WriteBB: Value è null");
            }
            else
            {
                if (DbNum < 0 || IndiceMem < 0)
                {
                    lgError($"Errore in S7WriteBB | DbNum: {DbNum} | IndiceMem: {IndiceMem}");
                }
                else
                {
                    sw.Restart();
                    if (testCncConn())
                    {
                        try
                        {
                            int numByte = Value.Length;
                            ErrorCode errorCode = currPLC.WriteBytes(DataType.DataBlock, DbNum, IndiceMem, Value);
                            switch (errorCode)
                            {
                                case ErrorCode.NoError:
                                    lgInfo($"S7WriteBB-02 Effettuata correttamente scrittura su PLC: DB {DbNum}.{IndiceMem} | numByte: {Value.Length}| {Value.ValToBinString()}");
                                    break;

                                case ErrorCode.WrongCPU_Type:
                                case ErrorCode.ConnectionError:
                                case ErrorCode.IPAddressNotAvailable:
                                case ErrorCode.WrongVarFormat:
                                case ErrorCode.WrongNumberReceivedBytes:
                                case ErrorCode.SendData:
                                case ErrorCode.ReadData:
                                case ErrorCode.WriteData:
                                    lgError($"Errore in S7WriteBB su DB {DbNum}.{IndiceMem}: {errorCode.ToString()} | numByte: {Value.Length}| {Value.ValToBinString()}");
                                    break;

                                default:
                                    break;
                            }
                            answ = true;
                        }
                        catch (Exception exc)
                        {
                            lgError($"Eccezione in S7WriteBB: DbNum {DbNum}, IndiceMem: {IndiceMem}, numByte: {Value.Length}{Environment.NewLine}{exc}");
                        }
                    }
                    sw.Stop();
                }
            }
            return answ;
        }

        /// <summary>
        /// Salvo in memblock il valore DInt indicato con formattazione siemens
        /// </summary>
        /// <param name="MemBlock">Blocco memoria come byte[] dove scrivere</param>
        /// <param name="startPos">Posizione inizio scrittura</param>
        /// <param name="valore">Valore da scrivere</param>
        public void saveDIntOnMemBlock(ref byte[] MemBlock, int startPos, string valore)
        {
            try
            {
                lgInfo($"saveDIntOnMemBlock: MemBlock size: {MemBlock.Length} | startPos: {startPos} | valore: {valore}");

                int valInt = 0;
                int.TryParse(valore, out valInt);
                byte[] strByte = S7.Net.Types.DInt.ToByteArray(valInt);
                int byteLen = 4;
                Buffer.BlockCopy(strByte, 0, MemBlock, startPos, byteLen);
                //var verifica = S7.Net.Types.String.FromByteArray(MemBlock);
            }
            catch (Exception exc)
            {
                lgError($"Errore in gestione scrittura DINT {valore} alla posizione {startPos} byte{Environment.NewLine}{exc}");
            }
        }

        /// <summary>
        /// Salvo in memblock il valore DInt indicato con formattazione siemens
        /// </summary>
        /// <param name="MemBlock">Blocco memoria come byte[] dove scrivere</param>
        /// <param name="stringKey">Valore da scrivere</param>
        /// <param name="startPos">Posizione inizio scrittura</param>
        public void saveDIntOnMemBlock(ref byte[] MemBlock, string stringKey, int startPos)
        {
            if (currProdData.ContainsKey(stringKey))
            {
                try
                {
                    string valore = currProdData[stringKey];
                    lgInfo($"saveDIntOnMemBlock: MemBlock size: {MemBlock.Length} | startPos: {startPos} | key: {stringKey} | valore: {valore}");
                    saveDIntOnMemBlock(ref MemBlock, startPos, valore);
                }
                catch (Exception exc)
                {
                    lgError($"Errore in gestione scrittura DINT {stringKey}{Environment.NewLine}{exc}");
                }
            }
        }

        /// <summary>
        /// Salvo in memblock il valore DInt indicato con formattazione siemens
        /// </summary>
        /// <param name="MemBlock">Blocco memoria come byte[] dove scrivere</param>
        /// <param name="startPos">Posizione inizio scrittura</param>
        /// <param name="valore">Valore da scrivere</param>
        public void saveDWordOnMemBlock(ref byte[] MemBlock, int startPos, string valore)
        {
            try
            {
                lgInfo($"saveDWordOnMemBlock: MemBlock size: {MemBlock.Length} | startPos: {startPos} | valore: {valore}");
                byte[] stringPar = new byte[4];
                int valInt = 0;
                int.TryParse(valore, out valInt);
                byte[] strByte = S7.Net.Types.DInt.ToByteArray(valInt);
                int byteLen = 4;
                Buffer.BlockCopy(strByte, 0, MemBlock, startPos, byteLen);
                //var verifica = S7.Net.Types.String.FromByteArray(MemBlock);
            }
            catch (Exception exc)
            {
                lgError($"Errore in gestione scrittura DWord {valore} alla posizione {startPos} byte{Environment.NewLine}{exc}");
            }
        }

        /// <summary>
        /// Salvo in memblock il valore DInt indicato con formattazione siemens
        /// </summary>
        /// <param name="MemBlock">Blocco memoria come byte[] dove scrivere</param>
        /// <param name="stringKey">Valore da scrivere</param>
        /// <param name="startPos">Posizione inizio scrittura</param>
        public void saveDWordOnMemBlock(ref byte[] MemBlock, string stringKey, int startPos)
        {
            if (currProdData.ContainsKey(stringKey))
            {
                try
                {
                    string valore = currProdData[stringKey];
                    lgInfo($"saveDWordOnMemBlock: MemBlock size: {MemBlock.Length} | startPos: {startPos} | key: {stringKey} | valore: {valore}");
                    saveDIntOnMemBlock(ref MemBlock, startPos, valore);
                }
                catch (Exception exc)
                {
                    lgError($"Errore in gestione scrittura DINT {stringKey}{Environment.NewLine}{exc}");
                }
            }
        }

        /// <summary>
        /// Salvo in memblock il valore Int indicato con formattazione siemens
        /// </summary>
        /// <param name="MemBlock">Blocco memoria come byte[] dove scrivere</param>
        /// <param name="startPos">Posizione inizio scrittura</param>
        /// <param name="valore">valore da scrivere</param>
        public void saveIntOnMemBlock(ref byte[] MemBlock, int startPos, string valore)
        {
            try
            {
                lgInfo($"saveIntOnMemBlock: MemBlock size: {MemBlock.Length} | startPos: {startPos} | valore: {valore}");
                short valInt = 0;
                short.TryParse(valore, out valInt);
                byte[] strByte = S7.Net.Types.Int.ToByteArray(valInt);
                int byteLen = 2;
                Buffer.BlockCopy(strByte, 0, MemBlock, startPos, byteLen);
                //var verifica = S7.Net.Types.String.FromByteArray(MemBlock);
            }
            catch (Exception exc)
            {
                lgError($"Errore in gestione scrittura INT {valore} alla posizione {startPos} byte{Environment.NewLine}{exc}");
            }
        }

        /// <summary>
        /// Salvo in memblock il valore Int indicato con formattazione siemens
        /// </summary>
        /// <param name="MemBlock">Blocco memoria come byte[] dove scrivere</param>
        /// <param name="stringKey">Nome del parametro da recuperare da prodData x scrivere</param>
        /// <param name="startPos">Posizione inizio scrittura</param>
        public void saveIntOnMemBlock(ref byte[] MemBlock, string stringKey, int startPos)
        {
            if (currProdData.ContainsKey(stringKey))
            {
                try
                {
                    string valore = currProdData[stringKey];
                    lgInfo($"saveIntOnMemBlock: MemBlock size: {MemBlock.Length} | startPos: {startPos} | key: {stringKey} | valore: {valore}");
                    saveIntOnMemBlock(ref MemBlock, startPos, valore);
                }
                catch (Exception exc)
                {
                    lgError($"Errore in gestione scrittura INT {stringKey}{Environment.NewLine}{exc}");
                }
            }
        }

        /// <summary>
        /// Salvo in memblock il valore stringa indicato con formattazione siemens
        /// </summary>
        /// <param name="MemBlock">Blocco memoria come byte[] dove scrivere</param>
        /// <param name="startPos">Posizione inizio scrittura</param>
        /// <param name="valore">Valore scrivere</param>
        public void saveRealOnMemBlock(ref byte[] MemBlock, int startPos, string valore)
        {
            try
            {
                byte[] stringPar = new byte[2];
                lgInfo($"saveRealOnMemBlock: MemBlock size: {MemBlock.Length} | startPos: {startPos} | valore: {valore}");

                double valReal = 0;
                double.TryParse(valore, out valReal);
                byte[] strByte = S7.Net.Types.Double.ToByteArray(valReal);
                int byteLen = 4;
                Buffer.BlockCopy(strByte, 0, MemBlock, startPos, byteLen);
                //var verifica = S7.Net.Types.String.FromByteArray(MemBlock);
            }
            catch (Exception exc)
            {
                lgError($"Errore in gestione scrittura REAL {valore} alla posizione {startPos} byte{Environment.NewLine}{exc}");
            }
        }

        /// <summary>
        /// Salvo in memblock il valore stringa indicato con formattazione siemens
        /// </summary>
        /// <param name="MemBlock">Blocco memoria come byte[] dove scrivere</param>
        /// <param name="stringKey">Valore scrivere</param>
        /// <param name="startPos">Posizione inizio scrittura</param>
        public void saveRealOnMemBlock(ref byte[] MemBlock, string stringKey, int startPos)
        {
            if (currProdData.ContainsKey(stringKey))
            {
                try
                {
                    string valore = currProdData[stringKey];
                    lgInfo($"saveRealOnMemBlock: MemBlock size: {MemBlock.Length} | startPos: {startPos} | key: {stringKey} | valore: {valore}");
                    saveRealOnMemBlock(ref MemBlock, startPos, valore);
                }
                catch (Exception exc)
                {
                    lgError($"Errore in gestione scrittura REAL {stringKey}{Environment.NewLine}{exc}");
                }
            }
        }

        /// <summary>
        /// Salvo in memblock il valore stringa indicato con formattazione siemens
        /// </summary>
        /// <param name="MemBlock">Blocco memoria come byte[] dove scrivere</param>
        /// <param name="startPos">Posizione inizio scrittura</param>
        /// <param name="totLen">
        /// Lunghezza max stringa (se ci sono 2 byte iniziali verrà ridotta di 2)
        /// </param>
        /// <param name="valore">valore da scrivere</param>
        public void saveStringOnMemBlock(ref byte[] MemBlock, int startPos, int totLen, string valore)
        {
            if (MemBlock != null)
            {
                // loggare valore? fornire un output con memBlock e NON ref?
                try
                {
                    lgInfo($"saveStringOnMemBlock: MemBlock size: {MemBlock.Length} | startPos: {startPos} | totLen: {totLen} | valore: {valore}");
                    byte[] stringPar = new byte[2];
                    byte[] strByte = S7.Net.Types.String.ToByteArray(valore);
                    // eventuale decremento val lungh totale...
                    totLen = writePre ? totLen - 2 : totLen;
                    int byteLen = strByte.Length <= totLen ? strByte.Length : totLen;
                    int shiftStrByte = writePre ? 2 : 0;
                    if (writePre)
                    {
                        // MAX LUN
                        stringPar[1] = (byte)totLen;
                        // LUNGH STRING
                        stringPar[0] = (byte)byteLen;
                        Buffer.BlockCopy(stringPar, 0, MemBlock, startPos, shiftStrByte);
                    }
                    Buffer.BlockCopy(strByte, 0, MemBlock, startPos + shiftStrByte, byteLen);
                }
                catch (Exception exc)
                {
                    lgError($"Errore in gestione scrittura {valore} alla posizione {startPos} per {totLen} byte{Environment.NewLine}{exc}");
                }
            }
            else
            {
                lgError("Errore: MemBlock nullo");
            }
        }

        /// <summary>
        /// Salvo in memblock il valore stringa indicato con formattazione siemens
        /// </summary>
        /// <param name="MemBlock">Blocco memoria come byte[] dove scrivere</param>
        /// <param name="stringKey">Nome del parametro da recuperare da prodData x scrivere</param>
        /// <param name="startPos">Posizione inizio scrittura</param>
        /// <param name="totLen">
        /// Lunghezza max stringa (se ci sono 2 byte iniziali verrà ridotta di 2)
        /// </param>
        public void saveStringOnMemBlock(ref byte[] MemBlock, string stringKey, int startPos, int totLen)
        {
            if (currProdData.ContainsKey(stringKey))
            {
                try
                {
                    string valore = currProdData[stringKey];
                    saveStringOnMemBlock(ref MemBlock, startPos, totLen, valore);
                }
                catch (Exception exc)
                {
                    lgError($"Errore in gestione scrittura x key {stringKey}{Environment.NewLine}{exc}");
                }
            }
        }

        /// <summary>
        /// Salvo in memblock il valore DInt indicato con formattazione siemens
        /// </summary>
        /// <param name="MemBlock">Blocco memoria come byte[] dove scrivere</param>
        /// <param name="startPos">Posizione inizio scrittura</param>
        /// <param name="valore">Valore da scrivere</param>
        public void saveWordOnMemBlock(ref byte[] MemBlock, int startPos, string valore)
        {
            try
            {
                lgInfo($"saveWordOnMemBlock: MemBlock size: {MemBlock.Length} | startPos: {startPos} | valore: {valore}");
                byte[] stringPar = new byte[4];
                int valInt = 0;
                int.TryParse(valore, out valInt);
                byte[] strByte = S7.Net.Types.DInt.ToByteArray(valInt);
                int byteLen = 4;
                Buffer.BlockCopy(strByte, 0, MemBlock, startPos, byteLen);
                //var verifica = S7.Net.Types.String.FromByteArray(MemBlock);
            }
            catch (Exception exc)
            {
                lgError($"Errore in gestione scrittura Word {valore} alla posizione {startPos} byte{Environment.NewLine}{exc}");
            }
        }

        /// <summary>
        /// Salvo in memblock il valore DInt indicato con formattazione siemens
        /// </summary>
        /// <param name="MemBlock">Blocco memoria come byte[] dove scrivere</param>
        /// <param name="stringKey">Valore da scrivere</param>
        /// <param name="startPos">Posizione inizio scrittura</param>
        public void saveWordOnMemBlock(ref byte[] MemBlock, string stringKey, int startPos)
        {
            if (currProdData.ContainsKey(stringKey))
            {
                try
                {
                    string valore = currProdData[stringKey];
                    lgInfo($"saveWordOnMemBlock: MemBlock size: {MemBlock.Length} | startPos: {startPos} | key: {stringKey} | valore: {valore}");
                    saveDIntOnMemBlock(ref MemBlock, startPos, valore);
                }
                catch (Exception exc)
                {
                    lgError($"Errore in gestione scrittura DINT {stringKey}{Environment.NewLine}{exc}");
                }
            }
        }

        /// <summary>
        /// Converte direttamente un valore stringa su un oggetto byte[] (senza limitazioni di dimensione)
        /// </summary>
        /// <param name="valore"></param>
        /// <param name="maxLenght">Dimensione massima ammessa per la stringa</param>
        /// <returns></returns>
        public byte[] stringToByte(string valore, int maxLenght)
        {
            byte[] answ = new byte[1];
            byte[] stringPar = new byte[2];
            byte[] strByte = S7.Net.Types.String.ToByteArray(valore);
            int shiftStrByte = writePre ? 2 : 0;
            int byteLen = strByte.Length <= maxLenght ? strByte.Length : maxLenght;
            if (writePre)
            {
                // MAX LUN
                stringPar[1] = (byte)maxLenght;
                // LUNGH STRING
                stringPar[0] = (byte)byteLen;
                Buffer.BlockCopy(stringPar, 0, answ, 0, shiftStrByte);
            }
            Buffer.BlockCopy(strByte, 0, answ, shiftStrByte, byteLen);
            return answ;
        }

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

                    // reimporto parametri PLC se necessario...
                    setParamPlc();
                }
                lgInfoStartup("SIEMENS: tryConnect step 03");

                // controllo che il ping sia stato tentato almeno pingTestSec fa...
                if (DateTime.Now.Subtract(lastPING).TotalSeconds > utils.CRI("pingTestSec"))
                {
                    if (doLog)
                    {
                        lgInfoStartup("SIEMENS: ConnKO - tryConnect");
                    }
                    lgInfoStartup("SIEMENS: 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.Open();
                            szStatusConnection = "OPEN";
                            parentForm.commPlcActive = false;
                            connectionOk = true;
                            lgInfoStartup($"StatusConnection: {szStatusConnection}");
                            // refresh stato allarmi!!!
                            if (connectionOk)
                            {
                                queueInEnabCurr = true;
                                if (adpRunning)
                                {
                                    lgInfo($"Connessione OK: {connectionOk} | adpRunning: {adpRunning}");
                                }
                            }
                            else
                            {
                                lgError("Impossibile procedere, connessione mancante...");
                            }
                        }
                        catch (Exception exc)
                        {
                            lgFatal($"Errore in TryConnect adapter SIEMENS | szStatusConnection {szStatusConnection}{Environment.NewLine}{exc}");
                            connectionOk = false;
                            needRefresh = true;
                        }
                    }
                    else
                    {
                        // loggo no risposta ping ...
                        connectionOk = false;
                        if (doLog)
                        {
                            lgInfo($"Attenzione: SIEMENS controllo PING fallito per IP {IOBConfFull.Device.Connect.PingIpAddr}");
                        }
                    }
                }
            }
            // se non è ancora connesso faccio processing memoria caso disconnesso...
            if (!connectionOk)
            {
                // processo semafori ed invio...
                processMemoryDiscon();
            }
        }

        /// <summary>
        /// Override disconnessione
        /// </summary>
        public override void tryDisconnect()
        {
            if (connectionOk)
            {
                string szStatusConnection = "";
                try
                {
                    currPLC.Close();
                    connectionOk = false;
                    queueInEnabCurr = false;
                    lgInfo(szStatusConnection);
                    lgInfo("Effettuata disconnessione adapter SIEMENS!");
                }
                catch (Exception exc)
                {
                    lgFatal(exc, "Errore nella disconnessione dall'adapter SIEMENS");
                }
            }
            else
            {
                lgError("IMPOSSIBILE effettuare disconnessione SIEMENS: Connessione non disponibile...");
            }
        }

        /// <summary>
        /// Converte direttamente un valore UInt16 su un oggetto byte[2]
        /// </summary>
        /// <param name="valore">valore da scrivere</param>
        public byte[] wordToByte(string valore)
        {
            byte[] answ = new byte[2];
            try
            {
                ushort valInt = 0;
                ushort.TryParse(valore, out valInt);
                byte[] strByte = S7.Net.Types.Word.ToByteArray(valInt);
                int byteLen = 2;
                Buffer.BlockCopy(strByte, 0, answ, 0, byteLen);
            }
            catch (Exception exc)
            {
                lgError($"Errore in gestione scrittura INT {valore} in byte{Environment.NewLine}{exc}");
            }
            return answ;
        }

        #endregion Public Methods

        #region Protected Fields

        /// <summary>
        /// Oggetto PLC da ri-utilizzare...
        /// </summary>
        protected Plc currPLC;

        /// <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>
        /// Lungh massima stringhe
        /// </summary>
        protected int maxStrChar = 20;

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

        /// <summary>
        /// indica se scrivere i primi byte x string siemens x indicare lung max e corrente
        /// </summary>
        protected bool writePre = true;

        #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>
        /// decodifica da bitmap il CURRENT MODE del controllo
        /// </summary>
        /// <param name="currModeBitmap"></param>
        /// <returns></returns>
        protected virtual string decodeCurrMode(byte currModeBitmap)
        {
            string answ = "";
            if (verboseLog)
            {
                lgInfo(string.Format("CURR_MODE raw data: {0}", utils.binaryForm(currModeBitmap)));
            }
            // decodifica del MODO... B21
            /*
             * CURR MODE diviso in BIT:
             * B0 (01) = AUTO
             * B1 (02) = MDI
             * B2 (04) = JOG
             * B3 (08) = TeachIN (associato a MDI --> 10)
             * B4 (16) = Repos (associato a JOG --> 20)
             * B5 (32) = RefPoint (associato a Jog --> 36)
             * B6 (64) = Incr1 (associato a Jog --> 68)
             * B7 (128) = Incr10 (associato a Jog --> -124 / 132 se UInt)
             * */

            // modi principali
            if (currModeBitmap.SelectBit(0))
            {
                answ = "AUTO";
            }
            else if (currModeBitmap.SelectBit(1))
            {
                answ = "MDI";
            }
            else if (currModeBitmap.SelectBit(2))
            {
                answ = "JOG";
            }
            // modi accessori
            if (currModeBitmap.SelectBit(3))
            {
                answ += " | TEACH-IN";
            }
            if (currModeBitmap.SelectBit(4))
            {
                answ += " | REPOS";
            }
            if (currModeBitmap.SelectBit(5))
            {
                answ += " | REF-POINT";
            }
            if (currModeBitmap.SelectBit(6))
            {
                answ += " | INCR-1";
            }
            if (currModeBitmap.SelectBit(7))
            {
                answ += " | INCR-10";
            }
            return answ;
        }

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

        /// <summary>
        /// Effettua decodifica aree memoria alla bitmap usata x MAPO
        /// </summary>
        protected virtual void decodeToBaseBitmap()
        {
            // init a zero...
            B_input = 0;
        }

        /// <summary>
        /// Restituisce path completo file da chiave configurazione
        /// </summary>
        /// <param name="keyFile">chiave conf x file richiesto</param>
        /// <returns></returns>
        protected string filePath(string keyFile)
        {
            string answ = "";
            try
            {
                answ = $"{utils.confDir}\\{utils.CRS(keyFile)}";
            }
            catch (Exception exc)
            {
                lgError(exc, "Eccezione in recupero filePath");
            }
            return answ;
        }

        /// <summary>
        /// Verifica presenza allarmi (tra quelli configurati)
        /// </summary>
        protected override bool hasAlarms()
        {
            bool answ = false;
            int numErrors = 0;
            uint currStatus = 0;
            if (alarmMaps != null)
            {
                // leggo a ciclo le aree degli allarmi CONFIGURATI, se ne trovo --> segnalo allarme...
                foreach (var item in alarmMaps)
                {
                    // banchi in WORD (2 byte) --> scompongo
                    for (int i = 0; i < item.size / 2; i++)
                    {
                        currStatus = S7.Net.Types.Counter.FromByteArray(RawInput.Skip(item.index + 2 * i).Take(2).ToArray());
                        // verifica e decremento blink...
                        item.checkBlinkCounter(i, (uint)currStatus);

                        // verifico SE sia variato... confronto allarmi filtrato stile blink per bit status:
                        // - allarmi che iniziano per # IGNORATI
                        // - altri allarmi con un countdown da MAX_COUNTER_BLINK a 0 per il fronte
                        // di discesa
                        if (item.isChanged(i, currStatus))
                        {
                            if (currStatus > 0)
                            {
                                numErrors++;
                            }
                            // registro gli allarmi attivi e trasmetto...
                            if (sendAlarmVariations(item.memAddr, i, item.alarmsState[i], (uint)(item.alarmsMask[i] & currStatus), item.messages))
                            {
                                // se inviato --> salvo stato da current...
                                item.updStatusVal(i, (uint)(item.alarmsMask[i] & currStatus));
                            }
                        }
                    }
                }
            }
            answ = numErrors > 0;
            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;
            byte[] MemBlock = new byte[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;
                        // cerco in area memMapWrite...
                        if (memMap.mMapWrite.ContainsKey(item.uid))
                        {
                            // recupero!
                            currMem = memMap.mMapWrite[item.uid];
                            byteSize = currMem.size;
                            memAddrWrite = currMem.memAddr;
                            MemBlock = new byte[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);
                                    saveIntOnMemBlock(ref MemBlock, 0, valInt.ToString());
                                    break;

                                case plcDataType.DInt:
                                    valInt = getScaledInt(currMem);
                                    saveDIntOnMemBlock(ref MemBlock, 0, valInt.ToString());
                                    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:
                                    saveRealOnMemBlock(ref MemBlock, 0, currMem.value);
                                    break;

                                case plcDataType.String:
                                    // se ho writePre --> "allungo" di 2 la dimensione della stringa
                                    // x MemBlock...
                                    if (writePre)
                                    {
                                        MemBlock = new byte[byteSize + 2];
                                    }
                                    saveStringOnMemBlock(ref MemBlock, 0, currMem.size, currMem.value);
                                    break;

                                default:
                                    break;
                            }
                            lgInfo($"Preparo scrittura{Environment.NewLine}---------------MemBlock data---------------{Environment.NewLine}{BitConverter.ToString(MemBlock)}{Environment.NewLine}--------------- END data ---------------");
                            if (!string.IsNullOrEmpty(memAddrWrite))
                            {
                                // scrivo su siemens
                                fatto = S7WriteBB(ref MemBlock, memAddrWrite);
                                // se fatto --> aggiorno!
                                if (fatto)
                                {
                                    item.value = item.reqValue;
                                    item.reqValue = "";
                                    item.lastRead = DateTime.Now;
                                    item.writable = true;
                                }
                                // se configurato faccio verifica write...
                                if (getOptPar("WRITE_CHECK") == "TRUE")
                                {
                                    byte[] MemBlockRead = new byte[MemBlock.Length];
                                    S7ReadBB(ref MemBlockRead, memAddrWrite, MemBlock.Length);
                                    // se non corrispondessero loggo!
                                    if (!MemBlock.SequenceEqual(MemBlockRead))
                                    {
                                        lgError($"Errore: mancata corrispondenza tra dati scritti e letti:{Environment.NewLine}Write: {BitConverter.ToString(MemBlock)}{Environment.NewLine}read: {BitConverter.ToString(MemBlockRead)}");
                                    }
                                    else
                                    {
                                        lgInfo($"Scrittura corretta: {BitConverter.ToString(MemBlockRead)}");
                                    }
                                }
                            }
                            else
                            {
                                lgInfo($"Errore: memAddrWrite vuoto!");
                            }
                        }
                        else
                        {
                            lgInfo($"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()
        {
            // Creo oggetto connessione NC
            parentForm.commPlcActive = true;
            lgInfoStartup($"Start init Adapter SIEMENS all'IP {IOBConfFull.Device.Connect.IpAddr} | CPU: {IOBConfFull.Special.SiemensConf.CpuType} | R/S: {IOBConfFull.Special.SiemensConf.Rack}/{IOBConfFull.Special.SiemensConf.Slot} | --> IOB {IOBConfFull.General.CodIOB}");
            // SE è necessario refresh...
            if (needRefresh)
            {
                lgInfoStartup("Refreshing connection...");
                if (parametri != null)
                {
                    // ora tento avvio PLC... SE PING OK...
                    IPStatus esitoPing = testPingMachine;
                    if (esitoPing == IPStatus.Success)
                    {
                        try
                        {
                            parametri.slot = IOBConfFull.Special.SiemensConf.Slot;
                            parametri.rack = IOBConfFull.Special.SiemensConf.Rack;
                            parametri.tipoCpu = (CpuType)Enum.Parse(typeof(CpuType), IOBConfFull.Special.SiemensConf.CpuType);
                            parametri.ipAdrr = IOBConfFull.Device.Connect.IpAddr;
#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);
#endif

                            parametri.memAddrRead = IOBConfFull.Special.SiemensConf.MemAddrRead;
                            parametri.memAddrWrite = IOBConfFull.Special.SiemensConf.MemAddrWrite;
                            parametri.memSizeRead = IOBConfFull.Special.SiemensConf.MemSizeRead;
                            parametri.memSizeWrite = IOBConfFull.Special.SiemensConf.MemSizeWrite;
                            // salvo vettori memoria...
                            lgInfoStartup("Set RawInput dimensions...");
                            RawInput = new byte[parametri.memSizeRead];
                            RawOutput = new byte[parametri.memSizeWrite];
                            // salvo parametri conn!
                            lgInfoStartup(string.Format("Parametri memoria: memAddrRead: {0} | memAddrWrite: {1} | memSizeRead: {2} | memSizeWrite: {3}", parametri.memAddrRead, parametri.memAddrWrite, parametri.memSizeRead, parametri.memSizeWrite));
                        }
                        catch (Exception exc)
                        {
                            lgError(exc, "Errore in parse parametri da IOBConf");
                        }
                        needRefresh = false;
                        try
                        {
                            currPLC = new Plc(parametri.tipoCpu, parametri.ipAdrr, parametri.rack, parametri.slot);
                            // disconnetto e connetto...
                            if (isVerboseLog)
                            {
                                lgInfo("SIEMENS: tryDisconnect");
                            }

                            tryDisconnect();
                            // lo ripeto x evitare che ci sia un loop... e tryConnect richiami la
                            // procedura corrente...
                            needRefresh = false;
                            lgInfoStartup("SIEMENS: tryConnect");
                            lastConnectTry = DateTime.Now;
                            tryConnect();
                            lgInfoStartup("End init Adapter SIEMENS");
                            if (isVerboseLog)
                            {
                                lgInfo("S7+ CONNESSIONE AVVENUTA");
                            }
                        }
                        catch (Exception exc)
                        {
                            lgError(exc, "Errore in INIT PLC S7+");
                        }
                    }
                    else
                    {
                        lgError($"IOB SIEMENS | Errore in ping: esito {esitoPing}");
                    }
                    parentForm.commPlcActive = false;
                    // carico conf vettore memoria...
                    loadMemConf();
                    bool enableByApp = utils.CRB("enableContapezzi");
                    bool enableByIob = (getOptPar("ENABLE_PZCOUNT") == "TRUE");
                    bool disableByIob = (getOptPar("DISABLE_PZCOUNT") == "TRUE");
                    if ((enableByApp || enableByIob) && !(disableByIob))
                    {
                        // se disonnesso lo eseguo SOLO SE ho contapezzi a zero...
                        if (contapezziIOB == 0 && !connectionOk)
                        {
                            lgDebug("SIEMENS: 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"))
                                    {
                                        lgDebug("Init contapezzi SIEMENS: pzCntReload(true)");
                                        pzCntReload(true);
                                        // refresh associazione Macchina - IOB
                                        SendM2IOB();
                                        // invio altri dati accessori...
                                        SendMachineConf();
                                        // 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;
                                    lgDebug("Parametro mancante PZCOUNT_MODE");
                                }
                            }
                            catch (Exception exc)
                            {
                                lgError(exc, "Errore in contapezzi SIEMENS");
                            }
                        }
                    }
                }
                else
                {
                    lgError("Parametri null!");
                }
            }
        }

        /// <summary>
        /// Test connessione CNC
        /// </summary>
        /// <returns></returns>
        protected bool testCncConn()
        {
            bool answ = false;
            // 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.IsConnected)
                    {
                        currPLC.Open();
                    }

                    if (!currPLC.IsAvailable)
                    {
                        lgError($"PLC Siemens NON disponibile:{currPLC.LastErrorCode} | {currPLC.LastErrorString}");
                        currPLC.ClearLastError();
                    }
                    else
                    {
                        if (!currPLC.IsConnected)
                        {
                            lgError($"PLC Siemens NON connesso:{currPLC.LastErrorCode} | {currPLC.LastErrorString}");
                            currPLC.ClearLastError();
                            parentForm.updateComStats("NO connection");
                        }
                        else
                        {
                            // tutto ok
                            parentForm.updateComStats("Connection OK");
                            answ = true;
                        }
                    }
                }
                // salvo stato ping
                lastPingOk = answ;
                lastPingConn = adesso;
            }

            return answ;
        }

        #endregion Protected Methods

        #region Private Fields

        private bool disableByIob = false;
        private bool enableContapezzi = true;
        private string memAddrPzCount = "";
        private string memAddrPzQCtrl = "";

        #endregion Private Fields

        #region Private Methods

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

            return valInt;
        }

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

            return valUInt;
        }

        /// <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 ultima scrittura 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)
            {
                // 2022.03.16 portato a livello TRACE x evitare log troppo verboso
                lgTrace(logValue);
                lastMemWrite[memAddrWrite] = adesso;
            }
        }

        /// <summary>
        /// Effettua lettura del contatore e restitusice la stringa grezza del dato
        /// </summary>
        /// <param name="memAddrPzCountOrig"></param>
        /// <returns></returns>
        private string readCountVal(string memAddrPzCountOrig)
        {
            // lavoro sul nome scomponendolo passo passo x decidere il da farsi...
            string memAddrPzCount = memAddrPzCountOrig;
            object outputVal = new object();
            string strOutVal;
            // cerco i pezzi contati STANDARD
            if (memAddrPzCount.StartsWith("STD"))
            {
                // inizio verifica area memoria/parametro levando prima parte codice
                memAddrPzCount = memAddrPzCount.Replace("STD.", "");
                // verifico se si tratta di lettura area DB... formato tipo STD.DB700.DBB22.W
                if (memAddrPzCount.StartsWith("DB"))
                {
                    memAreaSiemens areaCounter = new memAreaSiemens(memAddrPzCount);

                    if (isVerboseLog)
                    {
                        lgInfo("DB [0] area memoria: {1}.{2}.{3}", memAddrPzCount, areaCounter.DbNum, areaCounter.indiceMem, areaCounter.tipoMem);
                    }
                    // copio da blocco già letto... con switch x tipo dati --> tipo lettura... e
                    // salvo ultimo conteggio rilevato
                    switch (areaCounter.tipoMem)
                    {
                        case "B":
                            byte valB = RawInput[areaCounter.indiceMem];
                            outputVal = valB;
                            break;

                        case "W":
                            ushort valW = S7.Net.Types.Word.FromByteArray(RawInput.Skip(areaCounter.indiceMem).Take(2).ToArray());
                            outputVal = valW;
                            break;

                        case "DW":
                            uint valDW = S7.Net.Types.DWord.FromByteArray(RawInput.Skip(areaCounter.indiceMem).Take(4).ToArray());
                            outputVal = valDW;
                            break;

                        case "DI":
                            int valSDW = S7.Net.Types.DInt.FromByteArray(RawInput.Skip(areaCounter.indiceMem).Take(4).ToArray());
                            outputVal = valSDW;
                            break;

                        case "RE":
                            double valRe = S7.Net.Types.Double.FromByteArray(RawInput.Skip(areaCounter.indiceMem).Take(4).ToArray());
                            outputVal = valRe;
                            break;

                        default:
                            break;
                    }
                }
            }
            else if (memAddrPzCount.StartsWith("SPEC"))
            {
                // inizio verifica area memoria/parametro levando prima parte codice
                memAddrPzCount = memAddrPzCount.Replace("SPEC.", "");
                // verifico se si tratta di lettura area DB... formato tipo SPEC.DB700.DBB22.W
                if (memAddrPzCount.StartsWith("DB"))
                {
                    memAreaSiemens areaCounter = new memAreaSiemens(memAddrPzCount);

                    if (isVerboseLog)
                    {
                        lgInfo("SPEC [0] area memoria: {1}.{2}.{3}", memAddrPzCount, areaCounter.DbNum, areaCounter.indiceMem, areaCounter.tipoMem);
                    }
                    // leggo i dati SPECIFICI...
                    byte[] MemBlockPZ = new byte[8];
                    bool fatto = false;
                    // copio da blocco LEGGENDO con switch x tipo dati --> tipo lettura... e salvo
                    // ultimo conteggio rilevato
                    switch (areaCounter.tipoMem)
                    {
                        case "B":
                            MemBlockPZ = new byte[1];
                            fatto = S7ReadBB(ref MemBlockPZ, memAddrPzCount, 1);
                            outputVal = MemBlockPZ[0];
                            break;

                        case "W":
                            MemBlockPZ = new byte[2];
                            fatto = S7ReadBB(ref MemBlockPZ, memAddrPzCount, 2);
                            ushort valW = S7.Net.Types.Word.FromByteArray(MemBlockPZ.ToArray());
                            outputVal = valW;
                            break;

                        case "DW":
                            MemBlockPZ = new byte[4];
                            fatto = S7ReadBB(ref MemBlockPZ, memAddrPzCount, 4);
                            uint valDW = S7.Net.Types.DWord.FromByteArray(MemBlockPZ.ToArray());
                            outputVal = valDW;
                            break;

                        case "DI":
                            MemBlockPZ = new byte[4];
                            fatto = S7ReadBB(ref MemBlockPZ, memAddrPzCount, 4);
                            int valSDW = S7.Net.Types.DInt.FromByteArray(MemBlockPZ.ToArray());
                            outputVal = valSDW;
                            break;

                        case "RE":
                            MemBlockPZ = new byte[4];
                            fatto = S7ReadBB(ref MemBlockPZ, memAddrPzCount, 4);
                            double valRe = S7.Net.Types.Double.FromByteArray(MemBlockPZ.ToArray());
                            outputVal = valRe;
                            break;

                        default:
                            break;
                    }
                }
            }
            strOutVal = $"{outputVal}";
            return strOutVal;
        }

        #endregion Private Methods
    }
}