using EasyModbus; using IOB_UT_NEXT; using MapoSDK; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.NetworkInformation; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace IOB_WIN_NEXT { /* -------------------------------------------------------------------------------- * Controlli ModBusTCP COMECA * - protocollo ModBus TCP * * -------------------------------------------------------------------------------- */ public class IobModbusTCP : IobGeneric { #region Private Fields /// /// Valore delta x gestione min/MAX e valore rilevato /// private double deltaVal = 0; #endregion Private Fields #region Protected Fields protected ModbusClient currPLC; /// /// Copia locale dei valori in Holding Registry, come array chiave (int) valori int[] letti tramite ModBus, da convertire secondo tipo /// protected Dictionary HoldingRegisterLUT = new Dictionary(); /// /// Ultimo controllo ping x evitare ping flood... /// protected DateTime lastPingConn = DateTime.Now.AddMinutes(-10); /// /// Esito ultimo ping /// protected bool lastPingOk = false; /// /// Setup blocchi memorie read (indirizzo inizio, size) /// protected Dictionary memSetR = new Dictionary(); /// /// parametri di connessione /// protected connParamModBusTCP parametri; /// /// Oggetto cronometro x test vari... /// protected Stopwatch sw = new Stopwatch(); #endregion Protected Fields #region Public Constructors /// Classe base con i metodi x ModBusTCP /// /// /// public IobModbusTCP(AdapterForm caller, IobConfiguration IOBConf) : base(caller, IOBConf) { lgInfo("NEW IOB ModBus TCP"); memMap = new plcMemMap(); if (IOBConf != null) { // gestione invio ritardato contapezzi pzCountDelay = utils.CRI("pzCountDelay"); lastPzCountSend = DateTime.Now; lastWarnODL = DateTime.Now; // inizializzo parametri... parametri = new connParamModBusTCP() { ipAdrr = "127.0.0.1", port = 502, pingMsTimeout = IOBConf.pingMsTimeout, memAddrRead = "40001", memAddrWrite = "41001", memSizeRead = 0, memSizeWrite = 0 }; setParamPlc(); // salvo info su conf IOB... string iobConfSer = ""; try { iobConfSer = JsonConvert.SerializeObject(IOBConf, Formatting.Indented); } catch { } // finito! lgInfo($"Init IOB, con {iobConfSer}"); } else { lgError("Impossibile avviare, IOBConf nullo/non valido!"); } } #endregion Public Constructors #region Protected Properties protected bool hasAlarms { get { bool answ = false; int numErrors = 0; int currStatus = 0; int[] listInt = new int[2]; if (alarmMaps != null) { // leggo a ciclo le aree degli allarmi CONFIGURATI, se ne trovo --> segnalo allarme... foreach (var item in alarmMaps) { // in primis decremento eventuali blink... item.decreaseBlinkCounter(); // banchi in array Int16 --> scompongo for (int i = 0; i < item.size / 2; i++) { // verifico se usare LUT bool useLUT = memSetR != null && memSetR.Count > 0; if (useLUT) { int lutAddress = 40000 + item.index; 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); } currStatus = ModbusClient.ConvertRegistersToInt(listInt); //currStatus = listInt[0]; // 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, (uint)currStatus)) { answ = currStatus > 0; if (answ) { 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)); } } } } } return answ; } } /// /// 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 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: if (size == 4) { valore = ModbusClient.ConvertRegistersToDouble(listInt); } else if (size == 2) { valore = ModbusClient.ConvertRegistersToFloat(listInt); } break; case plcDataType.Int: default: if (size == 4) { valore = ModbusClient.ConvertRegistersToLong(listInt); } else if (size == 2) { 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; } 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 = 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; } /// /// se si disconnette --> processo tentativo riconnessione.. /// /// private void CurrPLC_ConnectedChanged(object sender) { if (!currPLC.Connected) { tryDisconnect(); } } /// /// Recupero dati da singole letture /// /// Valori da processare /// 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) { // attesa 50 ms prima di procedere x evitare burst dati if (!useLUT) { Thread.Sleep(50); } 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: listInt = readInputReg(item.Value.index, item.Value.size); valore = convertFromReg(listInt, item.Value.size, item.Value.tipoMem); break; case modBusAddrType.HoldingRegister: if (useLUT) { int lutAddress = 40000 + item.Value.index; if (HoldingRegisterLUT.ContainsKey(lutAddress)) { try { listInt = HoldingRegisterLUT[lutAddress]; } 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); } valore = convertFromReg(listInt, item.Value.size, item.Value.tipoMem); break; default: break; } // verifica limite... con delta da impianto dataOk = (valore > (item.Value.minVal + deltaVal) && valore < (item.Value.maxVal - deltaVal)); if (dataOk) { // moltiplico x fattore conversione... valoreScal = valore * item.Value.factor; saveValue(ref outVal, valoreScal, item.Key); lgInfo($"getDynData: valore ricevuto | {item.Key} | val: {valore} | valoreScal: {valoreScal}"); } else { lgError($"getDynData: 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); lgInfo($"--> rimesso in coda lettura | parametro: {item.Key} | index: {item.Value.index} | size: {item.Value.size}"); } } return readErrorList; } /// /// 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 HoldingRegisterLUT /// /// Indirizzo di aprtenza /// NUm registri da leggere (max 120) private void readBlockHoldingReg(int startAddr, int numReg) { 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 baseAddr = 40001; int[] rawData = new int[2]; try { stopwatch.Restart(); rawData = currPLC.ReadHoldingRegisters(startAddr - baseAddr, numReg); stopwatch.Stop(); lgInfo($"Lettura in blocco HoldingRegisters| startAddr: {startAddr} | numReg {numReg} | rawData.lenght: {rawData.Length} | {stopwatch.ElapsedMilliseconds}ms"); // 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) { currReadErrors++; allOk = false; lgError($"Eccezione in readBlockHoldingReg | currReadErrors: {currReadErrors}{Environment.NewLine}{exc}"); } } else { lgError($"Attenzione, plc disconnesso... currReadErrors: {currReadErrors}"); tryDisconnect(); } // se tutto ok --> riduco contatore errori di 1... if (allOk) { currReadErrors = currReadErrors >= 1 ? currReadErrors-- : 0; } else { // se > max errori --> disconnetto if (currReadErrors > maxReadErrors) { lgInfo($"Superato limite errori Read ({currReadErrors}) --> tryDisconnect"); currReadErrors = 0; tryDisconnect(); } else { // altrimenti pausa forzata Thread.Sleep(300); } } } #endregion Private Methods #region Protected Methods /// /// 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 lo scopo specifico IN REALTA' non conta lo stato macchina.... ma lo inviamo lo stesso /// protected virtual void decodeToBaseBitmap() { // init a zero... B_input = 0; } /// /// 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); CurrVal = ModbusClient.ConvertIntToRegisters(valInt); fatto = writeInputReg(currMem.index, CurrVal); break; case plcDataType.DInt: valUInt = getScaledUInt(currMem); CurrVal = ModbusClient.ConvertLongToRegisters(valInt); fatto = writeInputReg(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: valDouble = getScaledDouble(currMem); CurrVal = ModbusClient.ConvertFloatToRegisters((float)valDouble, ModbusClient.RegisterOrder.HighLow); fatto = writeInputReg(currMem.index, CurrVal); 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($"---------------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; } #if false // 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)}"); } } #endif } 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}"); } } } } /// /// Imposto parametri PLC /// protected override void setParamPlc() { // Creo oggetto connessione NC parentForm.commPlcActive = true; lgInfo($"Start init Adapter ModBus TCP all'IP {cIobConf.cncIpAddr} | port: {cIobConf.cncPort} | --> IOB {cIobConf.codIOB}"); // SE è necessario refresh... if (needRefresh) { lgInfo("Refreshing connection..."); if (parametri != null) { try { parametri.ipAdrr = cIobConf.cncIpAddr; parametri.port = int.Parse(cIobConf.cncPort); // leggo file init... lgInfo("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); // salvo vettori memoria... lgInfo("Set RawInput dimensions..."); RawInput = new byte[parametri.memSizeRead]; RawOutput = new byte[parametri.memSizeWrite]; // salvo parametri conn! lgInfo(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"); } // carico conf vettore memoria... loadMemConf(); // avvio conf blocchi memoria setupMemBlocks(); // aggiungo DELTA x calcolo min/MAX... string deltaValStr = getOptPar("DELTA_VAL"); if (!string.IsNullOrEmpty(deltaValStr)) { double.TryParse(deltaValStr, out deltaVal); } bool enableByApp = utils.CRB("enableContapezzi"); bool enableByIob = (getOptPar("ENABLE_PZCOUNT") == "TRUE"); bool disableByIob = (getOptPar("DISABLE_PZCOUNT") == "TRUE"); if ((enableByApp || enableByIob) && !(disableByIob)) { lgInfo("ModBus TCP: inizio gestione contapezzi"); try { // verifico quale modalità sia richiesta: STD (6711) oppure BIT (Custom, con indicazione area) if (cIobConf.optPar.Count > 0 && !string.IsNullOrWhiteSpace(getOptPar("PZCOUNT_MODE"))) { if (getOptPar("PZCOUNT_MODE").StartsWith("STD")) { lgInfo("Init contapezzi ModBusTCP: pzCntReload(true)"); pzCntReload(true); // refresh associazione Macchina - IOB sendM2IOB(); // per adesso imposto lettura PLC == contapezzi (poi farà vera lettura...) contapezziPLC = contapezziIOB; } else { contapezziIOB = 0; lgInfo("Contapezzi STD disabilitato: modalità {0}", getOptPar("PZCOUNT_MODE")); } } else { contapezziIOB = 0; lgInfo("Parametro mancante PZCOUNT_MODE"); } } catch (Exception exc) { lgError(exc, "Errore in contapezzi ModBusTCP"); } } // ora tento avvio PLC... SE PING OK... 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.ConnectedChanged += CurrPLC_ConnectedChanged; // disconnetto e connetto... if (isVerboseLog) { lgInfo("ModBus TCP: tryDisconnect"); } tryDisconnect(); // lo ripeto x evitare che ci sia un loop... e tryConnect richiami la procedura corrente... needRefresh = false; lgInfo("ModBus TCP: tryConnect"); tryConnect(); lgInfo("End init Adapter ModBusTCP"); if (isVerboseLog) { lgInfo("ModBus TCP CONNESSIONE AVVENUTA"); } } catch (Exception exc) { lgError(exc, "Errore in INIT ModBusTCP"); } } else { lgError($"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}"); } } } } } /// /// 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 * cIobConf.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; } #endregion Protected Methods #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) { 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.setProg: case taskType.sendWatchDogMes2Plc: case taskType.startSetup: case taskType.stopSetup: case taskType.setPzComm: case taskType.setArt: case taskType.setComm: taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | SKIPPED | NO EXEC"; break; case taskType.setParameter: // richiedo da URL i parametri WRITE da popolare lgInfo("Chiamata processMemWriteRequests"); taskVal = processMemWriteRequests(); // se restituiscce "" faccio altra prova... if (string.IsNullOrEmpty(taskVal)) { // i parametri me li aspetto come stringa composta paramName|paramvalue if (item.Value.Contains("|")) { string[] paramsJob = item.Value.Split('|'); taskVal = $"REQUEST SET PARAMETERS: {paramsJob[0]} --> {paramsJob[1]}"; } else { taskVal = $"WRONG REQUEST FOR SET PARAMETERS: {item.Value} doesnt contain pipe for splitting key/value"; } } break; default: taskVal = "SKIPPED | NO EXEC"; 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() { // 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) { readBlockHoldingReg(item.Key, item.Value); Thread.Sleep(100); } } // 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) { lgInfo($"Attesa prima di rilettura | LUT: {useLUT}"); // attendo 3 sec Thread.Sleep(3000); lgInfo($"Effettuo rilettura per {readErrorList.Count} variabili | LUT: {useLUT}"); readErrorListRepeat = getDataDictionary(readErrorList, useLUT, ref outVal); } // se avessi ancora errori --> disconnetto if (readErrorListRepeat.Count > 0) { lgInfo($"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 { lgInfo($"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}"); tryDisconnect(); outVal = new Dictionary(); tryConnect(); } } } else { lgInfo($"Non processo getDynData: enableTSVC = false"); } if (periodicLog || outVal.Count > 0) { lgInfo($"Esito getDynData: {outVal.Count} valori VALIDI in outVal"); } return outVal; } /// /// Lettura valori Coils (1...) /// /// /// /// public bool[] readCoil(int startAddr, int qty) { bool[] answ = new bool[1]; if (currPLC.Connected) { answ = currPLC.ReadCoils(startAddr, qty); } return answ; } /// /// Lettura valori DiscreteInputs (2...) /// /// /// /// public bool[] readDiscrInputs(int startAddr, int qty) { bool[] answ = new bool[1]; if (currPLC.Connected) { answ = currPLC.ReadDiscreteInputs(startAddr, qty); } return answ; } /// /// Lettura valori Holding Register (3...) /// /// /// /// public int[] readHoldReg(int startAddr, int qty) { int[] answ = new int[2]; if (currPLC.Connected) { answ = currPLC.ReadHoldingRegisters(startAddr, qty); } 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); } } 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) { // 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) { lgInfo("inizio read semafori"); } 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 (Exception exc) { currDispData.semIn = Semaforo.SR; lgError($"Eccezione in readSemafori{Environment.NewLine}{exc}"); } } /// /// Effettua salvataggio in LUT del valore ricevuto (double) /// /// /// /// /// public override void saveValue(ref Dictionary outVal, double valore, string chiave) { //check obj preliminare if (outVal == null) { outVal = new Dictionary(); } bool scaduto = stackVal_TSVC(chiave, valore); // recupero VC valore = getVal_TSVC(chiave, scaduto); if (scaduto) { outVal.Add(chiave, $"{valore:N3}"); } LastTSVC[chiave] = valore; } /// /// 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"); // reimporto 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) { if (adpRunning) { lgInfo($"Connessione OK: {connectionOk} | 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) { lgInfo($"Attenzione: ModBusTCP controllo PING fallito per IP {cIobConf.cncIpAddr}"); } } } } // se non è ancora connesso faccio procesisng memoria caso disconnesso... if (!connectionOk) { // processo semafori ed invio... processMemoryDiscon(); } } /// /// Override disconnessione /// public override void tryDisconnect() { 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; } /// /// 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 Input Register (4...) /// /// /// Valore in formato INT da registri /// public bool writeInputReg(int startAddr, int[] currRegVal) { bool answ = false; if (currPLC.Connected) { try { currPLC.WriteMultipleRegisters(startAddr, currRegVal); answ = true; } catch { } } return answ; } #endregion Public Methods } }