using EasyModbus; using IOB_UT_NEXT; using IOB_UT_NEXT.Config; using IOB_UT_NEXT.Config.Mem; using IOB_UT_NEXT.Objects; using IOB_UT_NEXT.Services.Core; using IOB_UT_NEXT.Services.Files; using IOB_UT_NEXT.Services.Utility; using MapoSDK; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net.NetworkInformation; using System.Threading; using System.Windows.Forms; namespace IOB_WIN_MBUS.IobModbusTCP { /* -------------------------------------------------------------------------------- * Controlli ModBusTCP (primo: COMECA) * - protocollo ModBus TCP * * -------------------------------------------------------------------------------- */ public class ModbusTCP : Iob.GenericNext { #region Public Constructors /// /// Classe base con i metodi x ModBusTCP /// /// Form chiamante /// Configurazione (legacy) /// Configurazione (v 4.x) public ModbusTCP(AdapterFormNext caller, IobConfTree IobConfFull) : base(caller, IobConfFull) { lgInfo("NEW IOB ModBus TCP"); DateTime adesso = DateTime.Now; memMap = new plcMemMapExt(); if (IOBConfFull != null) { // gestione invio ritardato contapezzi lastPzCountSend = adesso; lastWarnODL = adesso; // inizializzo parametri... parametri = new ModBusTcpParamConf(); #if false { ipAdrr = "127.0.0.1", port = 502, pingMsTimeout = IOBConfFull.Device.Connect.PingMsTimeout, holdRegBaseAddr = 40001, memAddrRead = "40001", memAddrWrite = "41001", memSizeRead = 0, memSizeWrite = 0 }; #endif setParamPlc(); // string sMinWait = getOptPar("minWait"); if (!string.IsNullOrEmpty(sMinWait)) { int.TryParse(sMinWait, out minWait); } string sMaxWait = getOptPar("maxWait"); if (!string.IsNullOrEmpty(sMaxWait)) { int.TryParse(sMaxWait, out maxWait); } // salvo info su conf IOB... string iobConfSer = ""; try { iobConfSer = JsonConvert.SerializeObject(IOBConfFull, Formatting.Indented); } catch { } // finito! lgInfoStartup($"Init IOB, con {iobConfSer}"); } else { lgError("Impossibile avviare, IOBConf nullo/non valido!"); } } /// /// Min attesa chiamate consecutive ModBus /// private int minWait = 40; /// /// Max attesa chiamate consecutive ModBus /// private int maxWait = 60; #endregion Public Constructors #region Public Methods /// /// Metodo dispose x il currPLC contenuto /// public void Dispose() { currPLC.Disconnect(); } /// /// Processo i task richiesti e li elimino dalla coda 1:1 /// /// public override Dictionary executeTasks(Dictionary task2exe, string codTav) { lgInfo($"Chiamata executeTasks specifica ModBus TCP: {task2exe.Count} task ricevuti"); // Verificare il protocollo: dovrebeb togliere SOLO i task eseguiti... Dictionary taskDone = new Dictionary(); string taskVal = ""; if (task2exe != null) { // cerco task specifici foreach (var item in task2exe) { taskVal = ""; // converto richiesta in enum... taskType tName = taskType.nihil; Enum.TryParse(item.Key, out tName); // controllo sulla KEY switch (tName) { case taskType.nihil: case taskType.fixStopSetup: case taskType.forceResetPzCount: case taskType.forceSetPzCount: case taskType.sendWatchDogMes2Plc: case taskType.startSetup: case taskType.stopSetup: taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | SKIPPED | NO EXEC"; break; case taskType.setProg: case taskType.setPzComm: case taskType.setArt: case taskType.setComm: // cerco se ho configurato parametri x invio... if (memMap.mMapWrite.ContainsKey(item.Key)) { memMap.mMapWrite[item.Key].value = item.Value; // preparo obj oparametro da scrivere... List updatedPar = new List(); objItem newParam = new objItem() { uid = item.Key, name = item.Key, value = item.Value, reqValue = "", writable = true, lastRequest = DateTime.Now, displOrdinal = memMap.mMapWrite[item.Key].displOrdinal }; updatedPar.Add(newParam); // richiamo scrittura parametri su PLC plcWriteParams(ref updatedPar); taskVal = $"REQUEST SET val: {item.Key} --> {item.Value}"; } else { taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | NOT FOUND IN mMapWrite List | NO EXEC"; } break; case taskType.setParameter: // richiedo da URL i parametri WRITE da popolare lgDebug("Chiamata processMemWriteRequests"); taskVal = processMemWriteRequests(); // se restituiscce "" faccio altra prova... if (string.IsNullOrEmpty(taskVal)) { // i parametri me li aspetto come stringa composta paramName|paramvalue if (item.Value.Contains("|")) { string[] paramsJob = item.Value.Split('|'); taskVal = $"REQUEST SET PARAMETERS: {paramsJob[0]} --> {paramsJob[1]}"; } else { taskVal = $"WRONG REQUEST FOR SET PARAMETERS: {item.Value} doesnt contain pipe for splitting key/value"; } } break; case taskType.processOtherInfo: bool okProc = ProcessOtherInfo(item.Key, item.Value); taskVal = okProc ? $"OK ProcessOtherInfoAsync | {item.Key} | {item.Value}" : $"ERROR ProcessOtherInfoAsync | {item.Key} | {item.Value}"; break; case taskType.syncDbData: ProcessDataSync(); break; default: taskVal = "SKIPPED | NO EXEC"; break; } // aggiungo task! taskDone.Add(item.Key, taskVal); } } return taskDone; } /// /// Decodifica tipo indirizzo dal codice vedere ad esempio https://www.fernhillsoftware.com/help/drivers/modbus/data-address-format.html /// /// /// public modBusAddrType getAddrType(string memAddr) { modBusAddrType answ = modBusAddrType.Coil; // leggo prima cifra... answ = (modBusAddrType)Enum.Parse(typeof(modBusAddrType), memAddr.Substring(0, 1)); return answ; } /// /// Recupero dati dinamici... ATTENZIONE factor usato come FONDOSCALA /// /// Esempio: /// - RealVal : 0 - 28000 /// - ReadVal : 0 - 100 /// - Factor : 28000 / 100 = 280 /// public override Dictionary getDynData() { Random rnd = new Random(); // valore non presente in vers default... se gestito fare override Dictionary outVal = new Dictionary(); if (utils.CRB("enableTSVC")) { // processing SOLO SE ho in memoria abbastanza dati... if (RawInput.Length < parametri.memSizeRead) { lgError($"Impossibile processare getDynData x ModBus TCP PLC, vettore memoria troppo piccolo: {RawInput.Length} byte / {parametri.memSizeRead} byte presenti/richiesti)"); } else { Dictionary readErrorList = new Dictionary(); Dictionary readErrorListRepeat = new Dictionary(); bool useLUT = memSetR != null && memSetR.Count > 0; // se configurato leggo IN BLOCCHI da memoria... if (useLUT) { foreach (var item in memSetR) { // verifico il tipo di dati da leggere e salvare... FIXME TODO FARE !!! readMemoryBlock(item.Key, item.Value); int waitTime = rnd.Next(minWait, maxWait); Thread.Sleep(waitTime); } } // procedo ... try { // processo x ogni valore configurato... if (memMap.mMapRead.Count > 0) { var memItemList = memMap.mMapRead; readErrorList = getDataDictionary(memItemList, useLUT, ref outVal); // se qualcosa è andato storto riprovo a caricare SOLO gli errori... 1 // sola volta if (readErrorList.Count > 0) { lgDebug($"Attesa prima di rilettura | LUT: {useLUT}"); // attendo 3 sec Thread.Sleep(3000); lgDebug($"Effettuo rilettura per {readErrorList.Count} variabili | LUT: {useLUT}"); readErrorListRepeat = getDataDictionary(readErrorList, useLUT, ref outVal); } // se avessi ancora errori --> disconnetto if (readErrorListRepeat.Count > maxErrorRead) { lgError($"Trovati valori non validi al secondo tentativo | LUT: {useLUT} --> invalido valori letti e resetto adapter con tryDisconnect!"); tryDisconnect(); // invalido output outVal = new Dictionary(); tryConnect(); } else { lastReadPLC = DateTime.Now; } } else { lgDebug($"getDynData: {memMap.mMapRead.Count} record in mMapRead | LUT: {useLUT}"); } } catch (Exception exc) { lgError(exc, $"Errore in getDynData x ModBus TCP PLC --> ciclo disconnect/reconnect | LUT: {useLUT}{Environment.NewLine}{exc}"); tryDisconnect(); outVal = new Dictionary(); tryConnect(); } } } else { lgDebug($"Non processo getDynData: enableTSVC = false"); } if (periodicLog || outVal.Count > 0) { lgDebug($"Esito getDynData: {outVal.Count} valori VALIDI in outVal"); } return outVal; } /// /// Gestione contapezzi /// public override void processContapezzi() { if (enablePzCountByApp) { if (IOBConfFull.OptPar.Count > 0 && !string.IsNullOrWhiteSpace(IOBConfFull.Device.PzCountMode)) { // cerco in lastDynData... string pzCountKey = IOBConfFull.Device.PzCountMode.Replace("STD.", ""); if (lastDynData.ContainsKey(pzCountKey)) { string rawVal = lastDynData[pzCountKey]; double cntDouble = -1; if (!string.IsNullOrEmpty(rawVal)) { //double.TryParse(rawVal, out cntDouble); bool success = double.TryParse(rawVal, NumberStyles.Any, CultureInfo.InvariantCulture, out cntDouble); if (success) { contapezziPLC = (int)cntDouble; } } } } } else { lgTrace($"processContapezzi escluso: enablePzCountByApp = {enablePzCountByApp} | disablePzCountByIob: {disablePzCountByIob}"); } } /// /// Lettura valori Coils (1...) /// /// /// /// public bool[] readCoil(int startAddr, int qty) { bool[] answ = new bool[1]; try { if (currPLC.Connected) { answ = currPLC.ReadCoils(startAddr, qty); trackReadData(qty / 8, sw.Elapsed.TotalMilliseconds / 1000); } } catch (Exception exc) { lgError($"Errore in readCoil{Environment.NewLine}{exc}"); } return answ; } /// /// Lettura valori DiscreteInputs (2...) /// /// /// /// public bool[] readDiscrInputs(int startAddr, int qty) { bool[] answ = new bool[1]; try { if (currPLC.Connected) { answ = currPLC.ReadDiscreteInputs(startAddr, qty); trackReadData(qty / 8, sw.Elapsed.TotalMilliseconds / 1000); } } catch (Exception exc) { lgError($"Errore in readDiscrInputs{Environment.NewLine}{exc}"); } return answ; } /// /// Lettura valori Holding Register (3...) /// /// /// /// public int[] readHoldReg(int startAddr, int qty) { int[] answ = new int[2]; try { if (currPLC.Connected) { answ = currPLC.ReadHoldingRegisters(startAddr, qty); trackReadData(qty * 2, sw.Elapsed.TotalMilliseconds / 1000); } } catch (Exception exc) { lgError($"Errore in readHoldReg{Environment.NewLine}{exc}"); } return answ; } /// /// Lettura valori Input Register (4...) /// /// /// /// public int[] readInputReg(int startAddr, int qty) { int[] answ = new int[2]; try { if (currPLC.Connected) { answ = currPLC.ReadInputRegisters(startAddr, qty); trackReadData(qty * 2, sw.Elapsed.TotalMilliseconds / 1000); } } catch (Exception exc) { lgError($"Errore in readInputReg{Environment.NewLine}{exc}"); } return answ; } /// /// Effettua lettura semafori principale Parametri da /// aggiornare x display in form /// public override void readSemafori(ref newDisplayData currDispData) { if (!IOBConfFull.Device.DisabStateCh) { // NON eseguo x NON indicare data ora ultima lettura se NON fatta davvero //base.readSemafori(ref currDispData); byte[] MemBlock = new byte[2]; try { currDispData.semIn = Semaforo.SV; if (verboseLog) { lgDebug("inizio read semafori"); } if (verboseLog) { lgTrace(string.Format("RawInput[0]: {0}", utils.binaryForm(RawInput[0]))); } // salvo il solo BYTE dell'input decifrando il semaforo... decodeToBaseBitmap(); decodeOtherData(); // riporto bitmap... reportRawInput(ref currDispData); } catch (Exception exc) { currDispData.semIn = Semaforo.SR; lgError($"Eccezione in readSemafori{Environment.NewLine}{exc}"); } } } /// /// Override connessione /// public override void tryConnect() { bool doLog = (verboseLog || periodicLog); lgInfo("ModBus TCP: tryConnect step 01"); if (!connectionOk) { // SE è necessario refresh... if (needRefresh) { lgInfo("ModBus TCP: tryConnect step 02"); // reimposto parametri PLC se necessario... setParamPlc(); } lgInfo("ModBus TCP: tryConnect step 03"); // controllo che il ping sia stato tentato almeno pingTestSec fa... if (DateTime.Now.Subtract(lastPING).TotalSeconds > utils.CRI("pingTestSec")) { if (doLog) { lgInfo("ModBus TCP: ConnKO - tryConnect"); } lgInfo("ModBus TCP: tryConnect step 04"); // in primis salvo data ping... lastPING = DateTime.Now; // se passa il ping faccio il resto... if (testPingMachine == IPStatus.Success) { string szStatusConnection = "ND"; try { // ora provo connessione... parentForm.commPlcActive = true; currPLC.Connect(); szStatusConnection = "OPEN"; parentForm.commPlcActive = false; connectionOk = currPLC.Connected; lgInfo($"StatusConnection: {szStatusConnection}"); // refresh stato allarmi!!! if (connectionOk) { //queueInEnabCurr = true; lgInfo($"Connessione OK: {connectionOk}"); if (adpRunning) { lgInfo($"AdpRunning: {adpRunning}"); } } else { lgError("Impossibile procedere, connessione mancante..."); } } catch (Exception exc) { lgFatal($"Errore in TryConnect adapter ModBusTCP | szStatusConnection {szStatusConnection}{Environment.NewLine}{exc}"); connectionOk = false; needRefresh = true; } } else { // loggo no risposta ping ... connectionOk = false; if (doLog) { lgError($"Attenzione: ModBusTCP controllo PING fallito per IP {IOBConfFull.Device.Connect.PingIpAddr}"); } } } } // se non è ancora connesso faccio procesisng memoria caso disconnesso... if (!connectionOk) { // processo semafori ed invio... processMemoryDiscon(); } } /// /// Override disconnessione /// public override void tryDisconnect() { if (currPLC != null && currPLC.Connected) { lgInfo("Richiesta disconnessione adapter ModBusTCP!"); try { if (currPLC.Connected) { currPLC.Disconnect(); } lgInfo("Effettuata disconnessione adapter ModBusTCP!"); } catch (Exception exc) { lgFatal($"Errore nella disconnessione dall'adapter ModBusTCP{Environment.NewLine}{exc}"); } } connectionOk = false; queueInEnabCurr = false; // resetto last ping... lastPING = DateTime.Now.AddMinutes(-1); } /// /// Scrittura di un singolo valore COIL (1...) /// /// /// /// public bool writeCoil(int startAddr, bool currValue) { bool answ = false; if (currPLC.Connected) { try { currPLC.WriteSingleCoil(startAddr, currValue); answ = true; } catch { } } return answ; } /// /// Scrittura di un valore Holding Register (4...) /// /// /// Valore in formato INT da registri /// public bool writeHoldingReg(int startAddr, int[] currRegVal) { bool answ = false; if (currPLC.Connected) { try { currPLC.WriteMultipleRegisters(startAddr + deltaBase + indexLutCorr, currRegVal); answ = true; } catch { } } return answ; } #endregion Public Methods #region Protected Fields /// /// Copia locale dei valori in COIL (OUT) | 0.000/09.999, oppure 0.000/65.536 = 0xFFFF, come /// array chiave (int) valori int[] letti tramite ModBus, da convertire secondo tipo /// protected Dictionary CoilLUT = new Dictionary(); protected ModbusClient currPLC; /// /// Copia locale dei valori in Discrete Inputs (IN) | 10.000/19.999, oppure 100.000/165.536 /// = 1xFFFF, come array chiave (int) valori int[] letti tramite ModBus, da convertire /// secondo tipo /// protected Dictionary DiscreteInputLUT = new Dictionary(); /// /// Copia locale dei valori in Holding Registers | 40.000/49.999, oppure 400.000/465.536 = /// 4xFFFF, come array chiave (int) valori int[] letti tramite ModBus, da convertire secondo tipo /// protected Dictionary HoldingRegisterLUT = new Dictionary(); /// /// Copia locale dei valori in Input Registers | 30.000/39.999, oppure 300.000/365.536 = /// 3xFFFF, come array chiave (int) valori int[] letti tramite ModBus, da convertire secondo tipo /// protected Dictionary InputRegisterLUT = new Dictionary(); /// /// Ultimo controllo ping x evitare ping flood... /// protected DateTime lastPingConn = DateTime.Now.AddMinutes(-10); /// /// Esito ultimo ping /// protected bool lastPingOk = false; /// /// num max di errori lettura permessi prima di disconnettere /// protected int maxErrorRead = 10; /// /// Setup blocchi memorie read (indirizzo inizio, size) /// protected Dictionary memSetR = new Dictionary(); /// /// parametri di connessione /// protected ModBusTcpParamConf parametri; #endregion Protected Fields #region Protected Properties /// /// Dizionario delle ultime operazioni di scrittura per OGNI memoria (in modo che fa log /// ogni x sec...) /// protected Dictionary lastMemWrite { get; set; } = new Dictionary(); #endregion Protected Properties #region Protected Methods /// /// Classe di base implementazione traduzione di una LUT da memoria come valore BIT /// (multipli) a valore esplicito x FLog /// protected override void checkTranslateBit() { // verifico se devo processare decodifica di qualche valore... if (OptVar2TranslBit != null && OptVar2TranslBit.Count > 0) { Dictionary valUpdated = new Dictionary(); // ciclo x ogni valore da tradurre foreach (var item in OptVar2TranslBit) { int reqAddr = 0; // traduco in int... int.TryParse(item.Key, out reqAddr); int lutAddr = reqAddr + indexLutCorr; int valInt = 0; int[] listInt = new int[2]; // cerco valore nella LUT... se è pari cerco "direttamente" altrimenti dal pari // inferiore e 2° INT[] if (lutAddr % 2 == 0) { listInt = HoldingRegisterLUT[lutAddr]; valInt = listInt[0]; } else { lutAddr--; listInt = HoldingRegisterLUT[lutAddr]; valInt = listInt[1]; } // gestisco a bitmap --> salvo valore INT x check.. string sVal = $"{valInt}"; // se è variato rispetto precedente --> calcolo valori string, accodo in FLog e salvo... if (item.Value != sVal) { valUpdated.Add(item.Value, valInt); } } // se ho valori aggiornati --> aggiorno dictionary e invio if (valUpdated.Count > 0) { DateTime locTStamp = DateTime.Now; foreach (var item in valUpdated) { // salvo il valore modificato OptVar2TranslInt[item.Key] = $"{item.Value}"; List listVal = new List(); // si tratta di valori bit --> devo cercare OGNI bit attivo (0..8 --> 0.255) for (int i = 0; i < 8; i++) { if (BitUtils.isActive(item.Value, i)) { // traduco ed accodo... listVal.Add(itemTranslation($"{item.Key}.{i}")); } } // costruisco stringa finale... string trad = string.Join(",", listVal); string sVal = $"Change {item.Key}: {locTStamp.ToString()} | Val: {item.Value} | trad: {trad}"; bool sent = accodaFLog(item.Key, sVal, qEncodeFLog(item.Key, trad)); if (sent) { trackDynData(item.Key, trad); } } } } } /// /// Classe di base implementazione traduzione di una LUT da memoria come valore INT a valore /// esplicito x FLog /// protected override void checkTranslateInt() { if (OptVar2TranslInt != null && OptVar2TranslInt.Count > 0) { Dictionary valUpdated = new Dictionary(); // ciclo x ogni valore da tradurre foreach (var item in OptVar2TranslInt) { int reqAddr = 0; // traduco in int... int.TryParse(item.Key, out reqAddr); int lutAddr = reqAddr + indexLutCorr; int valInt = 0; int[] listInt = new int[2]; // cerco valore nella LUT... se è pari cerco "direttamente" altrimenti dal pari // inferiore e 2° INT[] if (lutAddr % 2 == 0) { listInt = HoldingRegisterLUT[lutAddr]; valInt = listInt[0]; } else { lutAddr--; listInt = HoldingRegisterLUT[lutAddr]; valInt = listInt[1]; } // cerco traduzione... string trad = itemTranslation(item.Key, $"{valInt}"); // se è variato rispetto precedente --> accodo in FLog e salvo... if (item.Value != trad) { valUpdated.Add(item.Value, trad); } } // se ho valori aggiornati --> aggiorno dictionary e invio if (valUpdated.Count > 0) { string sVal = ""; DateTime locTStamp = DateTime.Now; foreach (var item in valUpdated) { OptVar2TranslInt[item.Key] = item.Value; sVal = $"Change {item.Key}: {locTStamp.ToString()} | Val: {item.Value}"; bool sent = accodaFLog(item.Key, sVal, qEncodeFLog(item.Key, item.Value)); if (sent) { trackDynData(item.Key, item.Value); } } } } } /// /// Decodifica il resto dell'area x i dati accessori (allarmi, ...) /// protected virtual void decodeOtherData() { } /// /// Effettua decodifica aree memoria alla bitmap usata x MAPO/GWMS /// - per macchine base (GWMS) IN REALTA' non gestito lo stato macchina.... previsto comunque /// protected virtual void decodeToBaseBitmap() { // init a zero... B_input = 0; } /// /// Recupero allarmi specifico x ModbusTCP (16 e 32 bit...) /// /// /// protected override int getAlarmStatus(BaseAlarmConf item) { int answ = 0; int[] listInt = new int[2]; // verifico se usare LUT bool useLUT = memSetR != null && memSetR.Count > 0; if (useLUT) { int lutAddress = parametri.holdRegBaseAddr + item.index + deltaBase + indexLutCorr; if (HoldingRegisterLUT.ContainsKey(lutAddress)) { try { listInt = HoldingRegisterLUT[lutAddress]; } catch (Exception exc) { lgError($"Errore in lettura da HoldingRegisterLUT per indirizzo {lutAddress} | item {item.memAddr}{Environment.NewLine}{exc}"); } } } else { listInt = readInputReg(item.index, item.size); } answ = ModbusClient.ConvertRegistersToInt(listInt); return answ; } /// /// Override metodo x scrittura parametri su PLC /// /// protected override void plcWriteParams(ref List updatedPar) { dataConf currMem = null; int byteSize = 0; int[] CurrVal = new int[1]; string memAddrWrite = ""; bool fatto = false; string serObj = ""; if (updatedPar != null) { // controllo i parametri... ne gestisco 4... foreach (var item in updatedPar) { try { memAddrWrite = ""; int valInt = 0; uint valUInt = 0; double valDouble = 0; // cerco in area memMapWrite... if (memMap.mMapWrite.ContainsKey(item.uid)) { // recupero! currMem = memMap.mMapWrite[item.uid]; byteSize = currMem.size; memAddrWrite = currMem.memAddr; CurrVal = new int[byteSize]; // faccio preliminarmente upsertKey... upsertKey(currMem.name, currMem.value); serObj = JsonConvert.SerializeObject(item, Formatting.Indented); lgInfo($"Inizio processing plcWriteParams per {currMem.name} | valore richiesto {currMem.value}{Environment.NewLine}---------------UPDATED PARAM---------------{Environment.NewLine}{serObj}{Environment.NewLine}---------------"); serObj = JsonConvert.SerializeObject(currMem, Formatting.Indented); lgInfo($"---------------MEMORY CONTENT---------------{Environment.NewLine}{serObj}{Environment.NewLine}---------------"); switch (currMem.tipoMem) { case plcDataType.Boolean: break; case plcDataType.Int: valInt = getScaledInt(currMem, true); if (byteSize == 1) { CurrVal[0] = valInt; } else { CurrVal = ModbusClient.ConvertIntToRegisters(valInt); } fatto = writeHoldingReg(currMem.index, CurrVal); break; case plcDataType.IntLH: valInt = getScaledInt(currMem, true); if (byteSize == 1) { CurrVal[1] = valInt; } else { CurrVal = ModbusClient.ConvertIntToRegisters(valInt); Array.Reverse(CurrVal); } fatto = writeHoldingReg(currMem.index, CurrVal); break; case plcDataType.DInt: valUInt = getScaledUInt(currMem, true); CurrVal = ModbusClient.ConvertLongToRegisters(valInt); fatto = writeHoldingReg(currMem.index, CurrVal); break; case plcDataType.DIntLH: valUInt = getScaledUInt(currMem, true); CurrVal = ModbusClient.ConvertLongToRegisters(valInt); Array.Reverse(CurrVal); fatto = writeHoldingReg(currMem.index, CurrVal); break; //case plcDataType.Word: // valUInt = getScaledUInt(currMem); // saveWordOnMemBlock(ref MemBlock, 0, valInt.ToString()); // break; //case plcDataType.DWord: // valUInt = getScaledUInt(currMem); // saveDWordOnMemBlock(ref MemBlock, 0, valInt.ToString()); // break; case plcDataType.Real: case plcDataType.RealLH: valDouble = getScaledDouble(currMem); CurrVal = ModbusClient.ConvertFloatToRegisters((float)valDouble, ModbusClient.RegisterOrder.LowHigh); fatto = writeHoldingReg(currMem.index, CurrVal); break; case plcDataType.FloatBADC: case plcDataType.RealHL: valDouble = getScaledDouble(currMem); CurrVal = ModbusClient.ConvertFloatToRegisters((float)valDouble, ModbusClient.RegisterOrder.HighLow); fatto = writeHoldingReg(currMem.index, CurrVal); break; case plcDataType.String: CurrVal = ModbusClient.ConvertStringToRegisters(currMem.value.PadRight(currMem.size * 2)); var regLen = ModbusClient.ConvertIntToRegisters(currMem.size * 2 - 2); Array.Copy(regLen, 0, CurrVal, CurrVal.Length - regLen.Length, regLen.Length); //CurrVal[CurrVal.Length - 2] = regLen[1]; //CurrVal[CurrVal.Length - 1] = regLen[0]; fatto = writeHoldingReg(currMem.index, CurrVal); break; default: break; } lgInfo($"---------------MemBlock data---------------{Environment.NewLine}CurrVal: {CurrVal}{Environment.NewLine}--------------- END data ---------------"); if (!string.IsNullOrEmpty(memAddrWrite)) { // se fatto --> aggiorno! if (fatto) { item.value = item.reqValue; item.reqValue = ""; item.lastRead = DateTime.Now; item.UM = currMem.unit; } } else { lgError($"Errore: memAddrWrite vuoto!"); } } else { lgError($"Errore uid non trovato in area write memory: {item.uid}, ci sono {memMap.mMapWrite.Count} in area write"); } } catch (Exception exc) { lgError($"Eccezione in fase di plcWriteParams per item {item.uid} con valore {item.value}{Environment.NewLine}{exc}"); } } } } /// /// Imposto parametri PLC /// protected override void setParamPlc() { DateTime adesso = DateTime.Now; // Creo oggetto connessione NC parentForm.commPlcActive = true; lgInfo($"Start init Adapter ModBus TCP all'IP {IOBConfFull.Device.Connect.IpAddr} | port: {IOBConfFull.Device.Connect.Port} | --> IOB {IOBConfFull.General.CodIOB}"); // SE è necessario refresh... if (needRefresh) { lgInfo("Refreshing connection..."); if (parametri != null) { try { lgInfoStartup("Init parametri speciali da IobConfTree..."); parametri.ipAdrr = IOBConfFull.Device.Connect.IpAddr; parametri.port = int.Parse(IOBConfFull.Device.Connect.Port); if (IOBConfFull.Special.ModbusConf != null) { // ora leggo valori speciali parametri.memAddrRead = IOBConfFull.Special.ModbusConf.memAddrRead; parametri.memAddrWrite = IOBConfFull.Special.ModbusConf.memAddrWrite; parametri.memSizeRead = IOBConfFull.Special.ModbusConf.memSizeRead; parametri.memSizeWrite = IOBConfFull.Special.ModbusConf.memSizeWrite; parametri.holdRegBaseAddr = IOBConfFull.Special.ModbusConf.holdRegBaseAddr; parametri.useCalcBaseAddr = IOBConfFull.Special.ModbusConf.useCalcBaseAddr; // valore delta base e shift deltaBase = IOBConfFull.Special.ModbusConf.deltaBase; indexLutCorr = IOBConfFull.Special.ModbusConf.indexLutCorr; // verifico eventuale parametro memoria estesa (non 0...10'000 ma 0...xFFFF=65536) modbusExtReg = IOBConfFull.Special.ModbusConf.modbusExtReg; } #if false // leggo file init... lgInfoStartup("Reading ini file..."); IniFile fIni = new IniFile(cIobConf.iniFileName); // ora leggo valori speciali parametri.memAddrRead = fIni.ReadString("MEMORY", "ADDR_READ", ""); parametri.memAddrWrite = fIni.ReadString("MEMORY", "ADDR_WRITE", ""); parametri.memSizeRead = fIni.ReadInteger("MEMORY", "SIZE_READ", 0); parametri.memSizeWrite = fIni.ReadInteger("MEMORY", "SIZE_WRITE", 0); parametri.holdRegBaseAddr = fIni.ReadInteger("MEMORY", "HR_BASE_ADDR", 40001); parametri.useCalcBaseAddr = fIni.ReadBoolean("MEMORY", "CALC_BASE_ADDR", true); // valore delta base e shift deltaBase = fIni.ReadInteger("MEMORY", "DELTA_BASE", 0); indexLutCorr = fIni.ReadInteger("MEMORY", "INDEX_LUT_CORR", 0); // verifico eventuale parametro memoria estesa (non 0...10'000 ma 0...xFFFF=65536) modbusExtReg = fIni.ReadBoolean("MEMORY", "MODBUS_EXT_REG"); #endif // salvo vettori memoria... lgInfoStartup("Set RawInput dimensions..."); RawInput = new byte[parametri.memSizeRead]; RawOutput = new byte[parametri.memSizeWrite]; // salvo parametri conn! lgInfoStartup($"Parametri memoria: memAddrRead: {parametri.memAddrRead} | memAddrWrite: {parametri.memAddrWrite} | memSizeRead: {parametri.memSizeRead} | memSizeWrite: {parametri.memSizeWrite} | deltaBase: {deltaBase}"); } catch (Exception exc) { lgError(exc, "Errore in parse parametri da IOBConf"); } // carico conf vettore memoria... loadMemConf(); fixDefaultPar(); // avvio conf blocchi memoria setupMemBlocks(); // aggiungo DELTA x calcolo min/MAX... string deltaValStr = getOptPar("DELTA_VAL"); if (!string.IsNullOrEmpty(deltaValStr)) { double.TryParse(deltaValStr.Replace(".", ","), out deltaVal); } // 2025.05.28: evita contapezzi dove NON configurato esplicitamente (versione "Imperativa" da nuova conf) bool enabPzCnt = IOBConfFull.Device.EnabPzCount; #if false bool enableByApp = utils.CRB("enableContapezzi"); bool enableByIob = (getOptPar("ENABLE_PZCOUNT") == "TRUE"); bool disableByIob = (getOptPar("DISABLE_PZCOUNT") == "TRUE"); #endif var strMaxError = getOptPar("MAX_ERROR_READ"); if (!string.IsNullOrEmpty(strMaxError)) { int.TryParse(strMaxError, out maxErrorRead); } //if ((enableByApp || enableByIob) && !(disableByIob)) if (enabPzCnt) { lgInfoStartup("ModBus TCP: inizio gestione contapezzi"); try { // verifico quale modalità sia richiesta: STD (6711) oppure BIT (Custom, // con indicazione area) if (IOBConfFull.OptPar.Count > 0 && !string.IsNullOrWhiteSpace(IOBConfFull.Device.PzCountMode)) { if (IOBConfFull.Device.PzCountMode.StartsWith("STD")) { lgInfoStartup("Init contapezzi ModBusTCP: pzCntReload(true)"); pzCntReload(true); // refresh associazione Macchina - IOB SendM2IOB(); // invio altri dati accessori... SendMachineConf(); // gestione allarmi: controllo se deve rileggere da REDIS if (resetAlarmOnStart) { SendAlarmReset(); } // per adesso imposto lettura PLC == contapezzi (poi farà vera lettura...) contapezziPLC = contapezziIOB; } else { contapezziIOB = 0; lgDebug("Contapezzi STD disabilitato: modalità {0}", IOBConfFull.Device.PzCountMode); } } else { contapezziIOB = 0; lgError("Parametro mancante PZCOUNT_MODE"); } } catch (Exception exc) { lgError(exc, "Errore in contapezzi ModBusTCP"); } } // verifico se sia connessa... if (!connectionOk) { // ora tento avvio PLC... SE PING OK... lastPING = adesso; IPStatus esitoPing = testPingMachine; if (esitoPing == IPStatus.Success) { needRefresh = false; try { //Ip-Address and Port of Modbus-TCP-Server currPLC = new ModbusClient(parametri.ipAdrr, parametri.port); currPLC.ConnectionTimeout = 5000; currPLC.ConnectedChanged += CurrPLC_ConnectedChanged; // disconnetto e connetto... if (isVerboseLog) { lgInfoStartup("ModBus TCP: tryDisconnect"); } tryDisconnect(); // tolgo needRefresh x evitare un loop... (tryConnect richiama setParamPlc) needRefresh = false; lgInfoStartup("ModBus TCP: tryConnect"); tryConnect(); lgInfoStartup("End init Adapter ModBusTCP"); if (isVerboseLog) { lgInfo("ModBus TCP CONNESSIONE AVVENUTA"); } } catch (Exception exc) { lgError(exc, "Errore in INIT ModBusTCP"); } } else { lgError($"ModBusTCP IOB | Errore in ping: esito {esitoPing}"); } } parentForm.commPlcActive = false; } else { lgError("Parametri null!"); } } } /// /// effettua il setup dei memblock da gestire (NON leggo intera memoria ma tanti blocchi...) /// protected void setupMemBlocks() { // se configurato -_> deserializzo string confFile = getOptPar("MEM_BLOCK"); if (!string.IsNullOrEmpty(confFile)) { string jsonFileName = $"{Application.StartupPath}/DATA/CONF/{confFile}"; lgInfo($"Apertura file {jsonFileName}"); using (StreamReader reader = new StreamReader(jsonFileName)) { string jsonData = reader.ReadToEnd(); if (!string.IsNullOrEmpty(jsonData)) { lgInfo($"File json MemBlock composto da {jsonData.Length} caratteri"); try { var currMem = JsonConvert.DeserializeObject(jsonData); memSetR = currMem.ReadBlocks; } catch (Exception exc) { lgError($"Errore in setupMemBlock{Environment.NewLine}{exc}"); } } } } } /// /// Testa la condition modbus da LUT + configurazione, valori tipo BIT /// /// /// protected bool testBitCondition(string cKey) { bool answ = false; if (OptCheckCondBit.ContainsKey(cKey)) { int currStatus = 0; int[] listInt = new int[2]; listInt = HoldingRegisterLUT[OptCheckCondBit[cKey].BaseAddr]; currStatus = ModbusClient.ConvertRegistersToInt(listInt); // controllo valore del bit richiesto answ = ((currStatus & (1 << OptCheckCondBit[cKey].BitNum)) == OptCheckCondBit[cKey].ValOk); lgTrace($"testBitCondition for {cKey} | BaseAddr: {OptCheckCondBit[cKey].BaseAddr} | BitNum: {OptCheckCondBit[cKey].BitNum} | ValOk: {OptCheckCondBit[cKey].ValOk}"); } else { lgTrace($"testBitCondition error: {cKey} not found"); } return answ; } /// /// Effettua ciclo letture di ogni area configurata /// /// indirizzo di partenza /// num valori da leggere protected void testBlockReadExt(int baseAddr, int numVals) { int baseHold = deltaBase; int[] readTestInt = new int[2]; bool[] readTestBool = new bool[2]; modbusMemType currMemType = modbusMemType.NotDefined; // valori setup secondo conf memoria: memoria estesa... 0 ... xFFFF per ogni set da 100'000... int maxVal = modbusExtReg ? 65536 : 10000; int baseMult = modbusExtReg ? 100000 : 10000; lgTrace($"-------------------- Inizio test lettura EXT {baseAddr} --------------------"); try { sw.Restart(); // calcolo tipo memoria currMemType = getMemType(baseAddr, maxVal, baseMult); switch (currMemType) { case modbusMemType.Coil: readTestBool = currPLC.ReadCoils(baseAddr - baseHold, numVals); trackReadData(numVals / 8, sw.Elapsed.TotalMilliseconds / 1000); break; case modbusMemType.DiscreteInput: readTestBool = currPLC.ReadDiscreteInputs(baseAddr - 1 * baseMult + baseHold, numVals); trackReadData(numVals / 8, sw.Elapsed.TotalMilliseconds / 1000); break; case modbusMemType.InputRegister: readTestInt = currPLC.ReadInputRegisters(baseAddr - 3 * baseMult + baseHold, numVals); trackReadData(numVals * 2, sw.Elapsed.TotalMilliseconds / 1000); break; case modbusMemType.HoldingRegister: readTestInt = currPLC.ReadHoldingRegisters(baseAddr - 4 * baseMult + baseHold, numVals); trackReadData(numVals * 2, sw.Elapsed.TotalMilliseconds / 1000); break; case modbusMemType.NotDefined: default: break; } sw.Stop(); lgTrace($"Stats lettura | {readTestInt.Length} val | {sw.ElapsedMilliseconds}ms"); } catch { } switch (currMemType) { case modbusMemType.Coil: case modbusMemType.DiscreteInput: for (int i = 0; i < readTestBool.Length; i++) { lgTrace($"{currMemType} | {baseAddr + i:000} | Val: {readTestBool[i]}"); } break; case modbusMemType.InputRegister: case modbusMemType.HoldingRegister: for (int i = 0; i < readTestInt.Length / 2; i++) { int[] thisSet = new int[2]; Array.Copy(readTestInt, i * 2, thisSet, 0, 2); lgTrace($"{currMemType} | {baseAddr + i * 2:000} | Valori: {thisSet[0]} / {thisSet[1]} | Val Int: {ModbusClient.ConvertRegistersToInt(thisSet)} | Val Real: {ModbusClient.ConvertRegistersToFloat(thisSet):N6}"); } // provo anche come stringa intera lgTrace($"{currMemType} | {baseAddr:000} | Val string: {ModbusClient.ConvertRegistersToString(readTestInt, 0, readTestInt.Length)}"); break; case modbusMemType.NotDefined: default: lgTrace($"Errore: tipo memoria non definitito per {baseAddr} / {numVals}"); break; } lgTrace($"-------------------- Completato test lettura {baseAddr} --------------------"); } /// /// Test connessione CNC /// /// protected bool testCncConn() { bool answ = currPLC.Connected; if (!answ) { // riduco i controlli ping.. li faccio solo ogni 5 ping period se precedente positivo... DateTime adesso = DateTime.Now; if (lastPingOk && adesso.Subtract(lastPingConn).TotalMilliseconds < 5 * parametri.pingMsTimeout) { answ = lastPingOk; } else { IPStatus pingStatus = testPingMachine; // se non ok riprovo 1 volta dopo attesa if (pingStatus != IPStatus.Success) { Thread.Sleep(2 * IOBConfFull.Device.Connect.PingMsTimeout); pingStatus = testPingMachine; } // se non passa ancora errore! if (pingStatus != IPStatus.Success) { lgError($"Errore in testCncConn | reply Status {pingStatus} | IP: {parametri.ipAdrr} | T.Out: {parametri.pingMsTimeout}ms"); } // se passa il ping faccio il resto... else { if (!currPLC.Connected) { currPLC.Connect(); } if (!currPLC.Available(500)) { lgError($"PLC ModBus NON disponibile: {currPLC.IPAddress} | {currPLC.Port}"); } else { if (!currPLC.Connected) { lgError($"PLC ModBus NON connesso:{currPLC.IPAddress} | {currPLC.Port}"); } else { // tutto ok parentForm.updateComStats("Connection OK"); answ = true; } } } // salvo stato ping lastPingConn = adesso; } lastPingOk = answ; } return answ; } /// /// Testa la condition modbus da LUT + configurazione, valori tipo BIT tramite DiscreteInput /// (0x02 - 10000+) /// /// /// protected bool testDisInBitCondition(string cKey) { bool answ = false; if (OptCheckCondBit.ContainsKey(cKey)) { if (DiscreteInputLUT.Count > 0) { int idxMem = OptCheckCondBit[cKey].BaseAddr + indexLutCorr; if (DiscreteInputLUT.ContainsKey(idxMem)) { answ = DiscreteInputLUT[idxMem] == (OptCheckCondBit[cKey].ValOk != 0); lgTrace($"testDisInBitCondition for {cKey} | BaseAddr: {OptCheckCondBit[cKey].BaseAddr} | BitNum: {OptCheckCondBit[cKey].BitNum} | ValOk: {OptCheckCondBit[cKey].ValOk} | actual: {answ}"); } } } else { lgTrace($"testDiscrInputBitCondition error: {cKey} not found"); } return answ; } /// /// Testa la condition modbus da LUT + configurazione, valori tipo INT /// /// /// protected bool testIntCondition(string cKey) { bool answ = false; if (OptCheckCondInt.ContainsKey(cKey)) { int currStatus = 0; int[] listInt = new int[2]; listInt = HoldingRegisterLUT[OptCheckCondInt[cKey].BaseAddr]; // leggo da conf OptCheckCondInt[cKey].IntIndex if (OptCheckCondInt[cKey].IntIndex < 2) { currStatus = listInt[OptCheckCondInt[cKey].IntIndex]; } else { currStatus = ModbusClient.ConvertRegistersToInt(listInt); } //answ = (currStatus == OptCheckCondInt[cKey].ValOk); // controllo se il valore sia in elenco di quelli validi... answ = OptCheckCondInt[cKey].ValOk.Contains(currStatus); lgTrace($"testIntCondition for {cKey} | BaseAddr: {OptCheckCondInt[cKey].BaseAddr} | currStatus: {currStatus} | ValOk: {string.Join(",", OptCheckCondInt[cKey].ValOk)}"); } else { lgTrace($"testIntCondition error: {cKey} not found"); } return answ; } /// /// Test di lettura dei blocchi di memoria ESTESO (considerato memorie xFFFF e tutti e 4 i /// tipi di dati modbus) /// protected void testReadExt() { foreach (var item in memSetR) { testBlockReadExt(item.Key, item.Value); } } #endregion Protected Methods #region Private Fields /// /// Valore delta x gestione min/MAX e valore rilevato /// private double deltaVal = 0; #endregion Private Fields #region Private Properties /// /// Valore da gestire come delta degli indirizzi configurati /// private int deltaBase { get; set; } = 0; /// /// Valore da gestire come delta degli index x lettura LUT /// private int indexLutCorr { get; set; } = 0; /// /// Indica se usare gli indirizzi estesi per ogni blocco: false = 0...10'000 true = /// 0...65'536 (= xFFFF) /// private bool modbusExtReg { get; set; } = false; #endregion Private Properties #region Private Methods /// /// converte valore in tipo desiderato /// /// /// /// /// private static double convertFromReg(int[] listInt, int size, plcDataType tipoMem) { double valore = 0; //verifico se sia INT o real x convertire... switch (tipoMem) { case plcDataType.Real: case plcDataType.RealLH: case plcDataType.FloatABCD: if (size == 4) { valore = ModbusClient.ConvertRegistersToDouble(listInt); } else if (size == 2) { valore = ModbusClient.ConvertRegistersToFloat(listInt, ModbusClient.RegisterOrder.LowHigh); } break; case plcDataType.RealHL: case plcDataType.FloatCDAB: if (size == 4) { //// inverto array... //Array.Reverse(listInt); //valore = ModbusClient.ConvertRegistersToDouble(listInt); valore = ModbusClient.ConvertRegistersToDouble(listInt, ModbusClient.RegisterOrder.HighLow); } else if (size == 2) { //// inverto array... //Array.Reverse(listInt); //valore = ModbusClient.ConvertRegistersToFloat(listInt); valore = ModbusClient.ConvertRegistersToFloat(listInt, ModbusClient.RegisterOrder.HighLow); } break; // caso speciale x Cedax, che usa HighVal moltiplicato x 32768... case plcDataType.HLPInt: if (size == 4) { valore = ModbusClient.ConvertRegistersToLong(listInt); } else if (size == 2) { int fact = Int16.MaxValue + 1; valore = listInt[1] * fact + listInt[0]; } break; case plcDataType.Int: default: if (size == 4) { valore = ModbusClient.ConvertRegistersToLong(listInt); } else if (size == 2) { valore = ModbusClient.ConvertRegistersToInt(listInt); } else if (size == 1) { valore = listInt[0]; } break; case plcDataType.IntLH: case plcDataType.DIntLH: if (size == 4) { Array.Reverse(listInt); valore = ModbusClient.ConvertRegistersToLong(listInt); } else if (size == 2) { Array.Reverse(listInt); valore = ModbusClient.ConvertRegistersToInt(listInt); } break; } return valore; } private static double getScaledDouble(dataConf currMem) { double valDouble; // prima faccio eventuale fattore di scala... double.TryParse(currMem.value, out valDouble); if (currMem.factor != 1) { valDouble = valDouble * (double)currMem.factor; } return valDouble; } /// /// Effettua scalatura valore secondo fattore Read --> * (moltiplico) Write --> / (divido) /// /// /// /// private static int getScaledInt(dataConf currMem, bool isWrite = false) { int valInt = 0; double temp; // prima faccio eventuale fattore di scala... double.TryParse(currMem.value.Replace(".", ","), out temp); //int.TryParse(currMem.value, out valInt); if (currMem.factor != 1) { if (isWrite) { valInt = (int)(temp * 10 / currMem.factor) / 10; } else { valInt = (int)(temp * 10 * currMem.factor) / 10; } } else { valInt = (int)temp; } return valInt; } /// /// Effettua scalatura valore secondo fattore Read --> * (moltiplico) Write --> / (divido) /// /// /// private static uint getScaledUInt(dataConf currMem, bool isWrite = false) { uint valUInt; // prima faccio eventuale fattore di scala... uint.TryParse(currMem.value, out valUInt); if (currMem.factor != 1) { if (isWrite) { valUInt = valUInt / (uint)currMem.factor; } else { valUInt = valUInt * (uint)currMem.factor; } } return valUInt; } /// /// se si disconnette --> processo tentativo riconnessione.. /// /// private void CurrPLC_ConnectedChanged(object sender) { if (!currPLC.Connected) { tryDisconnect(); } } /// /// Recupero dati da singole letture /// /// Valori da processare /// Impiego della LookUpTable x diminuire accessi /// Dizionario valori in uscita /// Errori di lettura private Dictionary getDataDictionary(Dictionary memItemList, bool useLUT, ref Dictionary outVal) { Dictionary readErrorList = new Dictionary(); // inizializzo i valori bool[] listBool = new bool[1]; int[] listInt = new int[2]; // procedo x ogni valore configurato... foreach (var item in memItemList) { try { listInt = new int[item.Value.size]; // attesa 50 ms prima di procedere x evitare burst dati if (!useLUT) { Thread.Sleep(50); } string valString = ""; double valore = 0; double valoreScal = 0; bool dataOk = false; // in primis DEVO determinare di quale TIPO di valore ho bisogno... dalla PRIMA // cifra di memAddr... modBusAddrType memAddrType = getAddrType(item.Value.memAddr); // in base al tipo leggo array... switch (memAddrType) { case modBusAddrType.Coil: listBool = readCoil(item.Value.index, item.Value.size); valore = listBool[0] ? 1 : 0; break; case modBusAddrType.DiscreteInput: listBool = readDiscrInputs(item.Value.index, item.Value.size); valore = listBool[0] ? 1 : 0; break; case modBusAddrType.InputRegister: if (useLUT) { int lutAddress = (parametri.holdRegBaseAddr / 4 * 3) + item.Value.index + deltaBase + indexLutCorr; // se size = 1 --> devo cambiare modo recupero if (item.Value.size == 1) { //if (item.Value.index % 2 == 0) if (lutAddress % 2 == 0) { //Array.Copy(HoldingRegisterLUT[lutAddress], listInt, 1); Array.Copy(InputRegisterLUT[lutAddress], 0, listInt, 0, 1); } else { lutAddress--; Array.Copy(InputRegisterLUT[lutAddress], 1, listInt, 0, 1); } } else { if (InputRegisterLUT.ContainsKey(lutAddress)) { try { if (listInt.Length == 2) { listInt = InputRegisterLUT[lutAddress]; } else { // faccio ciclo x copiare 2 alla volta... for (int i = 0; i < listInt.Length; i += 2) { Array.Copy(InputRegisterLUT[lutAddress + i], 0, listInt, i, 2); } } } catch (Exception exc) { lgError($"Errore in lettura da InputRegisterLUT per indirizzo {lutAddress} | item {item.Key}{Environment.NewLine}{exc}"); } } } } else { listInt = readInputReg(item.Value.index, item.Value.size); } if (item.Value.tipoMem == plcDataType.String) { // valore string... valString = ModbusClient.ConvertRegistersToString(listInt, 0, item.Value.size); } else { // valori numerici... valore = convertFromReg(listInt, item.Value.size, item.Value.tipoMem); } break; case modBusAddrType.HoldingRegister: if (useLUT) { int lutAddress = parametri.holdRegBaseAddr + item.Value.index + deltaBase + indexLutCorr; // se size = 1 --> devo cambiare modo recupero if (item.Value.size == 1) { //if (item.Value.index % 2 == 0) if (lutAddress % 2 == 0) { Array.Copy(HoldingRegisterLUT[lutAddress], 0, listInt, 0, 1); } else { lutAddress--; Array.Copy(HoldingRegisterLUT[lutAddress], 1, listInt, 0, 1); } } else { if (HoldingRegisterLUT.ContainsKey(lutAddress)) { try { if (listInt.Length == 2) { listInt = HoldingRegisterLUT[lutAddress]; } else { // faccio ciclo x copiare 2 alla volta... for (int i = 0; i < listInt.Length; i += 2) { Array.Copy(HoldingRegisterLUT[lutAddress + i], 0, listInt, i, 2); } } } catch (Exception exc) { lgError($"Errore in lettura da HoldingRegisterLUT per indirizzo {lutAddress} | item {item.Key}{Environment.NewLine}{exc}"); } } } } else { listInt = readHoldReg(item.Value.index - 1, item.Value.size); } if (item.Value.tipoMem == plcDataType.String) { // valore string... valString = ModbusClient.ConvertRegistersToString(listInt, 0, item.Value.size); valString = valString.Replace("\n", "").Replace("\r", "").Replace("\0", string.Empty).Trim(); } else { valore = convertFromReg(listInt, item.Value.size, item.Value.tipoMem); } break; default: break; } if (item.Value.tipoMem == plcDataType.String) { saveValueString(ref outVal, item.Key, valString); lgDebug($"getDataDictionary: valore ricevuto | {item.Key} | val: {valString}"); } else { // verifica limite... con delta da impianto dataOk = (valore >= (item.Value.minVal + deltaVal) && valore <= (item.Value.maxVal - deltaVal)); if (dataOk || disDynDataRangeCheck) { // verifica valori EPOCH x convertirli in datetime... if (item.Value.unit == "EPOCH") { valString = TimeUtils.epochConvert(valore).ToString("yyyy-MM-dd HH:mm:ss"); saveValueString(ref outVal, item.Key, valString); lgDebug($"getDataDictionary: valore EPOCH ricevuto | {item.Key} | val: {valore} | valString: {valString}"); } else { // moltiplico x fattore conversione... valoreScal = valore * item.Value.factor; saveValue(ref outVal, item.Key, valoreScal); lgDebug($"getDataDictionary: valore ricevuto | {item.Key} | val: {valore} | valoreScal: {valoreScal}"); } } else { lgError($"getDataDictionary: valore scartato x limiti min/max | {item.Key} | val: {valore} | valoreScal: {valoreScal} | min-max: {item.Value.minVal}-{item.Value.maxVal} | deltaVal: {deltaVal}"); readErrorList.Add(item.Key, item.Value); lgDebug($"--> rimesso in coda lettura | parametro: {item.Key} | index: {item.Value.index} | size: {item.Value.size}"); } } } catch (Exception exc) { lgError($"Eccezione in lettura variabile | item: {item.Key} | mem: {item.Value.memAddr}{Environment.NewLine}{exc}"); } } return readErrorList; } /// /// Calcola tipo memoria modbus dato indirizzo, dati parametri maxVal (slot) + base moltiplica /// /// Indirizzo memoria /// Valore massimo ammesso /// Base moltiplica slot memoria /// private modbusMemType getMemType(int baseAddr, int maxVal, int baseMult) { modbusMemType currMemType = modbusMemType.NotDefined; // determino tipo di memoria if (baseAddr < maxVal) { currMemType = modbusMemType.Coil; } else if (baseAddr < baseMult + maxVal) { currMemType = modbusMemType.DiscreteInput; } else if (baseAddr < 3 * baseMult + maxVal) { currMemType = modbusMemType.InputRegister; } else { currMemType = modbusMemType.HoldingRegister; } return currMemType; } /// /// Verifica SE sia il caso di fare il log della memoria indicata /// /// /// private void maybeLogWrite(string memAddrWrite, string logValue) { bool doWrite = true; DateTime adesso = DateTime.Now; if (!lastMemWrite.ContainsKey(memAddrWrite)) { lastMemWrite.Add(memAddrWrite, adesso.AddMinutes(-1)); } // ora mi leggo valore ultimas crittura e confronto con adesso try { doWrite = (lastMemWrite[memAddrWrite].AddSeconds(vetoSeconds) < adesso); } catch (Exception exc) { lgError($"Eccezione in maybeLogWrite{Environment.NewLine}{exc}"); } // se encessario --> LOG! if (doWrite) { lgInfo(logValue); lastMemWrite[memAddrWrite] = adesso; } } /// /// Effettua lettura blocco memoria indicato e lo salva in copia locale in CoilLUT /// /// Indirizzo di partenza /// Num registri da leggere (max 120) private bool readMemBlockCoil(int startAddr, int numReg, int baseAddr) { bool allOk = false; if (currPLC.Connected) { allOk = true; // fix massima lunghezza pacchetto if (numReg > 120) { lgError($"Attenzione richiesta lettura blocco troppo grande: numReg {numReg} --> 120"); numReg = 120; } bool[] rawData = new bool[2]; try { sw.Restart(); rawData = currPLC.ReadCoils(startAddr - baseAddr, numReg); sw.Stop(); lgTrace($"Lettura in blocco Colis | startAddr: {startAddr} | numReg {numReg} | rawData.lenght: {rawData.Length} | {sw.ElapsedMilliseconds}ms"); // salvo statistica... trackReadData(rawData.Length / 8, sw.Elapsed.TotalMilliseconds / 1000); lastReadPLC = DateTime.Now; // salvo in LUT i dati... if (rawData.Length > 0) { for (int i = 0; i < rawData.Length; i++) { if (rawData.Length >= i) { // salvo nel registro... int currAddr = startAddr + i; if (CoilLUT.ContainsKey(currAddr)) { CoilLUT[currAddr] = rawData[i]; } else { CoilLUT.Add(currAddr, rawData[i]); } } else { currReadErrors++; allOk = false; lgError($"CoilLUT | Impossibile copiare dati, array di origine troppo corto: rawData.lenght: {rawData.Length} | base addr richiesto i: {i} | errori currReadErrors: {currReadErrors}"); } } } } catch (Exception exc) { currReadErrors++; allOk = false; lgError($"Eccezione in readMemBlockCoil | startAddr: {startAddr} | baseAddr: {baseAddr} | numReg: {numReg} | currReadErrors: {currReadErrors} |{Environment.NewLine}{exc}"); } } else { lgError($"readMemBlockCoil | Attenzione, PLC disconnesso... currReadErrors: {currReadErrors}"); tryDisconnect(); } return allOk; } /// /// Effettua lettura blocco memoria indicato e lo salva in copia locale in DiscreteInputLUT /// /// Indirizzo di partenza /// Num registri da leggere (max 120) private bool readMemBlockDiscreteIn(int startAddr, int numReg, int baseAddr) { bool allOk = false; if (currPLC.Connected) { allOk = true; // fix massima lunghezza pacchetto if (numReg > 120) { lgError($"Attenzione richiesta lettura blocco troppo grande: numReg {numReg} --> 120"); numReg = 120; } bool[] rawData = new bool[2]; try { sw.Restart(); int memAddr = startAddr - baseAddr; rawData = currPLC.ReadDiscreteInputs(memAddr, numReg); sw.Stop(); lgTrace($"Lettura in blocco DiscreteInput | startAddr: {startAddr} | numReg {numReg} | rawData.lenght: {rawData.Length} | {sw.ElapsedMilliseconds}ms"); // salvo statistica... trackReadData(rawData.Length / 8, sw.Elapsed.TotalMilliseconds / 1000); lastReadPLC = DateTime.Now; // salvo in LUT i dati... if (rawData.Length > 0) { for (int i = 0; i < rawData.Length; i++) { if (rawData.Length >= i) { // salvo nel registro... int currAddr = startAddr + i; if (DiscreteInputLUT.ContainsKey(currAddr)) { DiscreteInputLUT[currAddr] = rawData[i]; } else { DiscreteInputLUT.Add(currAddr, rawData[i]); } } else { currReadErrors++; allOk = false; lgError($"DiscreteInputLUT | Impossibile copiare dati, array di origine troppo corto: rawData.lenght: {rawData.Length} | base addr richiesto i: {i} | errori currReadErrors: {currReadErrors}"); } } } } catch (Exception exc) { currReadErrors++; allOk = false; lgError($"Eccezione in readMemBlockDiscreteIn | startAddr: {startAddr} | baseAddr: {baseAddr} | numReg: {numReg} | currReadErrors: {currReadErrors} |{Environment.NewLine}{exc}"); } } else { lgError($"readMemBlockDiscreteIn | Attenzione, PLC disconnesso... currReadErrors: {currReadErrors}"); tryDisconnect(); } return allOk; } /// /// Effettua lettura blocco memoria indicato e lo salva in copia locale in HoldingRegisterLUT /// /// Indirizzo di partenza /// Num registri da leggere (max 120) /// Indirizzo di base (da sottrarre all'indirizzo richiesto) private bool readMemBlockHoldingReg(int startAddr, int numReg, int baseAddr) { bool allOk = false; if (currPLC.Connected) { allOk = true; // fix massima lunghezza pacchetto if (numReg > 120) { lgError($"Attenzione richiesta lettura blocco troppo grande: numReg {numReg} --> 120"); numReg = 120; } // si lavora rispetto all'indirizzo base 40001 int[] rawData = new int[2]; try { sw.Restart(); int memAddr = startAddr - baseAddr; rawData = currPLC.ReadHoldingRegisters(memAddr, numReg); sw.Stop(); lgTrace($"Lettura in blocco HoldingRegisters| startAddr: {startAddr} | numReg {numReg} | rawData.lenght: {rawData.Length} | {sw.Elapsed.TotalMilliseconds}ms"); // salvo statistica... trackReadData(rawData.Length * 2, sw.Elapsed.TotalMilliseconds / 1000); lastReadPLC = DateTime.Now; // se risposta troppo rapida (< minRespTimeMs, tipicamente 5 msse non impostato in OptPar) indico NON ok e errori... if (sw.Elapsed.TotalMilliseconds < minRespTimeMs) { allOk = false; currReadErrors += 10; } // se leggo tutti zero --> indico +5 read error! bool allZero = (rawData.Where(x => x > 0).Count() == 0); if (allZero) { currReadErrors += 5; } // salvo in LUT la versione ESPLOSA 2 byte alla volta... if (rawData.Length > 0) { for (int i = 0; i < rawData.Length / 2; i++) { int[] thisSet = new int[2]; if (rawData.Length >= (i + 1) * 2) { Array.Copy(rawData, i * 2, thisSet, 0, 2); // salvo nel registro... int currAddr = startAddr + i * 2; if (HoldingRegisterLUT.ContainsKey(currAddr)) { HoldingRegisterLUT[currAddr] = thisSet; } else { HoldingRegisterLUT.Add(currAddr, thisSet); } } else { currReadErrors++; allOk = false; lgError($"Impossibile copiare dati, array di origine troppo corto: rawData.lenght: {rawData.Length} | base addr richiesto i*2: {i * 2} | errori currReadErrors: {currReadErrors}"); } } } } catch (Exception exc) { // eccezione aggiunge 10 read errors! currReadErrors += 10; allOk = false; lgError($"Eccezione in readMemBlockHoldingReg | startAddr: {startAddr} | baseAddr: {baseAddr} | numReg: {numReg} | currReadErrors: {currReadErrors} |{Environment.NewLine}{exc}"); } } else { lgError($"readMemBlockHoldingReg | Attenzione, PLC disconnesso... currReadErrors: {currReadErrors}"); tryDisconnect(); } // all ok: se non scattato verifico se errori < 50... if (allOk) { allOk = currReadErrors < 10; } return allOk; } /// /// Effettua lettura blocco memoria indicato e lo salva in copia locale in InputRegisterLUT /// /// Indirizzo di partenza /// Num registri da leggere (max 120) private bool readMemBlockInputReg(int startAddr, int numReg, int baseAddr) { bool allOk = false; if (currPLC.Connected) { allOk = true; // fix massima lunghezza pacchetto if (numReg > 120) { lgError($"Attenzione richiesta lettura blocco troppo grande: numReg {numReg} --> 120"); numReg = 120; } // si lavora rispetto all'indirizzo base 40001 int[] rawData = new int[2]; try { sw.Restart(); int memAddr = startAddr - baseAddr; rawData = currPLC.ReadInputRegisters(memAddr, numReg); sw.Stop(); lgTrace($"Lettura in blocco InputRegisters| startAddr: {startAddr} | numReg {numReg} | rawData.lenght: {rawData.Length} | {sw.ElapsedMilliseconds}ms"); // salvo statistica... trackReadData(rawData.Length * 2, sw.Elapsed.TotalMilliseconds / 1000); lastReadPLC = DateTime.Now; // salvo in LUT la versione ESPLOSA 2 byte alla volta... if (rawData.Length > 0) { for (int i = 0; i < rawData.Length / 2; i++) { int[] thisSet = new int[2]; if (rawData.Length >= (i + 1) * 2) { Array.Copy(rawData, i * 2, thisSet, 0, 2); // salvo nel registro... int currAddr = startAddr + i * 2; if (InputRegisterLUT.ContainsKey(currAddr)) { InputRegisterLUT[currAddr] = thisSet; } else { InputRegisterLUT.Add(currAddr, thisSet); } } else { currReadErrors++; allOk = false; lgError($"Impossibile copiare dati, array di origine troppo corto: rawData.lenght: {rawData.Length} | base addr richiesto i*2: {i * 2} | errori currReadErrors: {currReadErrors}"); } } } } catch (Exception exc) { currReadErrors++; allOk = false; lgError($"Eccezione in readMemBlockInputReg | startAddr: {startAddr} | baseAddr: {baseAddr} | numReg: {numReg} | currReadErrors: {currReadErrors} |{Environment.NewLine}{exc}"); } } else { lgError($"readMemBlockInputReg | Attenzione, PLC disconnesso... currReadErrors: {currReadErrors}"); tryDisconnect(); } return allOk; } /// /// Verifica il tipo di memoria e di conseguenza effettua chiama la procedura di lettura /// /// Indirizzo di partenza /// Num registri da leggere (max 120) private void readMemoryBlock(int startAddr, int numReg) { bool allOk = false; modbusMemType currMemType = modbusMemType.NotDefined; // valori setup secondo conf memoria: memoria estesa... 0 ... xFFFF per ogni set da 100'000... int maxVal = modbusExtReg ? 65536 : 10000; int baseMult = modbusExtReg ? 100000 : 10000; int baseAddr = parametri.holdRegBaseAddr; // calcolo tipo memoria currMemType = getMemType(startAddr, maxVal, baseMult); // in base al tipo di memoria procedo switch (currMemType) { case modbusMemType.Coil: if (parametri.useCalcBaseAddr) { baseAddr = 0 * baseMult; } allOk = readMemBlockCoil(startAddr, numReg, baseAddr); break; case modbusMemType.DiscreteInput: if (parametri.useCalcBaseAddr) { baseAddr = 1 * baseMult; } allOk = readMemBlockDiscreteIn(startAddr, numReg, baseAddr); break; case modbusMemType.InputRegister: if (parametri.useCalcBaseAddr) { baseAddr = 3 * baseMult; } allOk = readMemBlockInputReg(startAddr, numReg, baseAddr); break; case modbusMemType.HoldingRegister: if (parametri.useCalcBaseAddr) { baseAddr = 4 * baseMult; } allOk = readMemBlockHoldingReg(startAddr, numReg, baseAddr); break; case modbusMemType.NotDefined: default: break; } // se tutto ok --> riduco contatore errori di 2... if (allOk) { currReadErrors = currReadErrors >= 2 ? currReadErrors - 2 : 0; } else { // se > max errori --> disconnetto if (currReadErrors > maxReadErrors) { lgError($"Read Errors: Superato limite --> tryDisconnect | currReadErrors: {currReadErrors} | maxReadErrors: {maxReadErrors}"); currReadErrors = 0; tryDisconnect(); } else { // altrimenti pausa forzata Thread.Sleep(1000); } } } #endregion Private Methods } }