using IOB_UT_NEXT; using MapoSDK; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using static IOB_UT_NEXT.CustomObj; namespace IOB_WIN_NEXT.Iob { /// /// Configuraizone eventi da simulare /// public class simPar { #region Public Fields /// /// Durata dell'evento /// public int duration = 1; /// /// DateTime ultimo evento /// public DateTime lastEv = DateTime.Now; /// /// Attesa per evento /// public int wait = 10; #endregion Public Fields } public class Simula : Iob.Generic { #region Public Fields /// /// Ora spegniemnto (standard) /// public int tOff = 22; /// /// Ora dia ccensione (standard) /// public int tOn = 6; #endregion Public Fields #region Public Constructors /// /// estende l'init della classe base... /// /// /// public Simula(AdapterForm caller, IobConfiguration IOBConf) : base(caller, IOBConf) { // gestione invio ritardato contapezzi pzCountDelay = utils.CRI("pzCountDelay"); DateTime adesso = DateTime.Now; lastPzCountSend = adesso; lastWarnODL = adesso; lastEvCheck = adesso; lastSimData = adesso; lastRedDuration = adesso; // abilito subito scrittura coda IN queueInEnabCurr = true; // setup allarmi simulati for (int i = 1; i <= 8; i++) { alarmMessages.Add($"Allarme {i:000}"); } // sistemo parametri x simulazione... if (cIobConf.optPar.Count > 0) { if (!string.IsNullOrEmpty(getOptPar("PER_BASE"))) { int.TryParse(getOptPar("PER_BASE"), out periodoMSec); // aggiungo NOISE... +/- 10% Random rnd = new Random(); int noise = rnd.Next(1, periodoMSec / 5); periodoMSec += noise - (periodoMSec / 10); } simPowerOnOff = false; bool.TryParse(getOptPar("SIM_POW_ON_OFF"), out simPowerOnOff); int.TryParse(getOptPar("T_ON"), out tOn); int.TryParse(getOptPar("T_OFF"), out tOff); bit2 = setupSimPar("SIM_PZCNT"); bit3 = setupSimPar("SIM_ALARM"); bit4 = setupSimPar("SIM_MANU"); bit5 = setupSimPar("SIM_SLOW"); bit6 = setupSimPar("SIM_WUCD"); bit7 = setupSimPar("SIM_EMRG"); int.TryParse(getOptPar("MIN_DURATA_ODL"), out minDurataODL); var strDisableSim = getOptPar("DISABLE_SIM_STATUS"); if (!string.IsNullOrEmpty(strDisableSim)) { bool.TryParse(strDisableSim, out disableSimStatus); } // simulazioni azioni utente simRC = setupSimPar("SIM_RC"); simRS = setupSimPar("SIM_RS"); simDich = setupSimPar("SIM_DICH"); int.TryParse(getOptPar("SIM_MATR_OPR"), out matrOpr); } string sFluxOnRead = getOptPar("sendFluxOnRead"); if (!string.IsNullOrEmpty("sendFluxOnRead")) { bool.TryParse(sFluxOnRead, out sendFluxOnRead); } // riesegue setup PLC setParamPlc(); // ricarico da server i dati dei pezzi fatti... lgInfo("Init contapezzi SIMULA: pzCntReload(true)"); if (!isMulti) { pzCntReload(true); } // imposto pezzi CNC ai pezzi contati da server... contapezziPLC = contapezziIOB; lgInfo($"Impostazione iniziale contatori: contapezzi macchina contapezziPLC: {contapezziPLC} | contapezziIOB: {contapezziIOB}"); // parto processando dynData processDynData(); if (!string.IsNullOrEmpty(getOptPar("FTP_AT_START"))) { // simulazione processo FTP (Cimolai) - FIXME TODO DeleteME processDataSync(); } // test ricetta if (hasRecipe) { Calendar cal = new CultureInfo("it-IT").Calendar; int week = cal.GetWeekOfYear(adesso, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday); string tempPath = Path.Combine(pathList["path-locBase"], pathList["path-01-Temp"]); string archPath = Path.Combine(pathList["path-locBase"], pathList["path-02-Sent"], $"{adesso:yyyy}", $"{week:00}"); string remoPath = Path.Combine(pathList["path-locBase"], pathList["path-04-remReq"]); baseUtils.checkDir(tempPath); baseUtils.checkDir(archPath); try { // recupero elenco PODL da processare, check PODL già inviati, save locale bool create = RecipeReqWriteLocal(tempPath, useLocalRecipe); // invio ricette a impianto bool trasmitted = RecipeSend(tempPath, archPath, remoPath); } catch (Exception exc) { lgError($"Eccezione in TEST processDataSync{Environment.NewLine}{exc}"); } } // test gestione fileImport... if (!string.IsNullOrEmpty(fileImportFolder)) { processFileImport(); } if (hasRecipe) { try { // effettua process ritorno ricette processRecipeFileRet(); } catch (Exception exc) { lgError($"Eccezione in TEST processRecipeFileRet{Environment.NewLine}{exc}"); } try { // eseguo verifica/lancio ricette RecipeDoProcCons(); } catch (Exception exc) { lgError($"Eccezione in TEST RecipeDoProcCons{Environment.NewLine}{exc}"); } } // commentato post test x Soitaab if (EnabelPodlManFull) { // test decodifica log (tipo soitaab) try { // eseguo subito un ciclo acquisizione log + processing bool acquireLog = getRemoteLog(); if (acquireLog) { bool sentSignLog = processSignLogTable(adesso); bool sentFluxLog = processFluxLogTable(adesso); } } catch (Exception exc) { lgError($"Eccezione in IobFileSoitaab{Environment.NewLine}{exc}"); } } } #endregion Public Constructors #region Public Methods /// /// Processo i task richiesti e li elimino dalla coda 1:1 (in realtà SOLO forceSetPzCount x ora) /// /// public override Dictionary executeTasks(Dictionary task2exe) { // Verificare il protocollo: dovrebeb togliere SOLO i task eseguiti... Dictionary taskDone = new Dictionary(); // controlo SE sia disabilitata simulazione principale if (!disableSimStatus) { string taskVal = ""; // verifico non sia null 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.setArt: case taskType.setComm: case taskType.setProg: case taskType.setPzComm: taskVal = processMemWriteRequests(); 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; case taskType.nihil: case taskType.fixStopSetup: case taskType.sendWatchDogMes2Plc: case taskType.startSetup: case taskType.stopSetup: taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | SKIPPED | NO EXEC"; break; case taskType.forceResetPzCount: // forzo sul SIM il valore a ZERO x l'IOB... contapezziPLC = 0; taskVal = "RESET contapezziPLC: 0"; break; case taskType.forceSetPzCount: // forzo sul SIM il valore pzCount dell'IOB... int newPzCount = contapezziPLC; bool fatto = int.TryParse(item.Value, out newPzCount); if (fatto) { // forzo update contapezziPLC = newPzCount; taskVal = $"Set new contapezziPLC: {contapezziPLC}"; #if false // verifico SE sia ammesso il cambio ... int deltaPzCount = newPzCount - contapezziPLC; double maxDelta = DateTime.Now.Subtract(plcLastPzRead).TotalMinutes / (plcAvgTc / 60); // se incremento superiore del doppio atteso --> segnalo errore // e NON accetto if (deltaPzCount > (maxDelta * maxPzDeltaPerc) / 100) { lgError($"[DELTA CHECK]: intremento contapezziPLC troppo elevato: lettura {newPzCount} | contapezzi attuale: {contapezziPLC} | ultima lettura PLC: {plcLastPzRead} | TCiclo medio: {plcAvgTc}s | incremento accettato "); } else { contapezziPLC = newPzCount; taskVal = $"Set new contapezziPLC: {contapezziPLC}"; } #endif } break; case taskType.syncDbData: processDataSync(); lgInfo("Richiesta syncDbData"); break; default: taskVal = "SKIPPED | NO EXEC"; break; } // aggiungo task! taskDone.Add(item.Key, taskVal); } } } return taskDone; } /// /// Recupera e processa allarmi CNC... /// public override Dictionary getCncAlarms() { Dictionary outVal = new Dictionary(); return outVal; } /// /// Recupero dati dinamici... /// public override Dictionary getDynData() { bool useLUT = true; // valore non presente in vers default... se gestito fare override Dictionary outVal = new Dictionary(); // verificare periodo SIM parametri... se passato li invio altrimenti NO... FIX a 20 sec if (lastSimData.AddSeconds(waitSimPar) < DateTime.Now) { Random rnd = new Random(); // controllo conf memorie json (se ci sono...) try { if (memMap.mMapWrite.Count > 0) { foreach (var item in memMap.mMapWrite) { if (useLUT) { saveValueString(ref outVal, item.Key, item.Value.value); } else { outVal.Add(item.Key, item.Value.value); } } } if (memMap.mMapRead.Count > 0) { foreach (var item in memMap.mMapRead) { // se il TIPO di valore è livello --> simulo variazione da MAX --> min, // con un delta in CALO pari a factor * (80-120)% if (item.Value.name == "SIM_LEVEL") { // verifico last value double lastVal = 0; double.TryParse(item.Value.value, out lastVal); if (lastVal == 0) { lastVal = item.Value.maxVal - (double)item.Value.factor; } // decremento casuale... double newVal = lastVal - ((double)item.Value.factor * rnd.Next(40, 120) / 100); // se inferiore a minimo --> massimo! if (newVal < item.Value.minVal) { newVal = item.Value.maxVal; } // salvo il suo VALUE... item.Value.value = $"{newVal}"; if (useLUT) { saveValue(ref outVal, item.Key, newVal); } else { outVal.Add(item.Key, $"{newVal}"); } } // altrimenti simulazione random walk... else { double randVal = 0; if (item.Value.factor == 1) { // uso factor come valore MAX ammesso randVal = rnd.Next(item.Value.minVal, item.Value.maxVal); } else { // uso factor come fattore di divisione x simulare decimali randVal = ((float)rnd.Next(item.Value.minVal, item.Value.maxVal)) / (float)item.Value.factor; } // verifico uso LUT if (useLUT) { saveValue(ref outVal, item.Key, randVal); } else { outVal.Add(item.Key, $"{randVal}"); } } } } } catch { } lastSimData = DateTime.Now; } return outVal; } /// /// Recupero programma in lavorazione /// /// public override string getPrgName() { // NOME DEL SIM! string prgName = $"PROG_{cIobConf.codIOB}"; return prgName; } /// /// Recupero info sistema generiche /// /// public override Dictionary getSysInfo() { // valore non presente in vers default... se gestito fare override Dictionary outVal = new Dictionary(); outVal.Add("MACHINE", "IOB_SIM"); return outVal; } /// /// Effettua vero processing contapezzi /// public override void processContapezzi() { } /// /// Processo contatori eventi... /// public override void processVHF() { if (lastEvCheck.AddMilliseconds(periodoMSec) < DateTime.Now) { // decremento contatore ultimo evento try { if (bit2 != null) bit2.wait--; if (bit3 != null) bit3.wait--; if (bit4 != null) bit4.wait--; if (bit5 != null) bit5.wait--; if (simDich != null) simDich.wait--; if (simRC != null) simRC.wait--; if (simRS != null) simRS.wait--; } catch { } lastEvCheck = DateTime.Now; } } /// /// Effettua lettura semafori principale Parametri da /// aggiornare x display in form /// public override void readSemafori(ref newDisplayData currDispData) { base.readSemafori(ref currDispData); // controlo SE sia disabilitata simulazione principale if (!disableSimStatus) { // decodifica e gestione decodeToBaseBitmap(); decodeOtherData(); reportRawInput(ref currDispData); } } /// /// Effettua reset del contapezzi /// /// public override bool resetcontapezziPLC() { bool answ = false; // ...SE abilitato da conf IOB if (cIobConf.optPar.Count > 0 && getOptPar("ENABLE_PZ_RESET") == "TRUE") { // fingo di aver fatto... answ = true; } return answ; } public override void tryConnect() { base.tryConnect(); connectionOk = true; } public override void tryDisconnect() { base.tryDisconnect(); connectionOk = false; } #endregion Public Methods #region Protected Fields /// /// Parametri simulazione oscillazione bit 2 /// protected simPar bit2; /// /// Parametri simulazione oscillazione bit 3 /// protected simPar bit3; /// /// Parametri simulazione oscillazione bit 4 /// protected simPar bit4; /// /// Parametri simulazione oscillazione bit 5 /// protected simPar bit5; /// /// Parametri simulazione oscillazione bit 6 /// protected simPar bit6; /// /// Parametri simulazione oscillazione bit 7 /// protected simPar bit7; /// /// pallet corrente /// protected int cP = 1; protected bool disableSimStatus = false; /// /// ultimo controllo decremento eventi /// protected DateTime lastEvCheck; /// /// Ultimo istante in cui sono stati ridotti dati simulazione duration /// protected DateTime lastRedDuration; /// /// Ultimo istante in cui sono stati generati dati di simulazione /// protected DateTime lastSimData; protected string logDirPath = "C:\\MesData\\TestSoitaab"; /// /// Cartella logfile x test import (tipo Soitaab) /// /// /// Matricola OPR simulato /// protected int matrOpr = 1; /// /// Durata minima ODL x reset quando pezzi iob > pezzi macchina... /// protected int minDurataODL = 480; /// /// pallet successivo (next) /// protected int nP = 1; /// /// periodo base del simulatore (in millisecondi) /// protected int periodoMSec = 1000; protected bool sendFluxOnRead = false; /// /// variabile di appoggio x stato segnale contapezzo /// protected bool sigPzCount = false; /// /// Simulazione registrazione dichiarazioni utente /// protected simPar simDich; /// /// BOOL: indica se simulare powerOn/Off (bit 0 e 1) compresi WarmUp e CoolDown /// protected bool simPowerOnOff; /// /// Simulazione effettuazione controlli utente /// protected simPar simRC; /// /// Simulazione registro scarti utente /// protected simPar simRS; /// /// Tempo di MINIMO attesa x simulazione parametri /// protected int waitSimPar = utils.CRI("waitSimPar"); #endregion Protected Fields #region Protected Methods /// /// Recupera file log da analizzare /// /// protected override bool getRemoteLog() { bool answ = false; // leggo direttamente il file di test in area C:\MesData\TestSoitaab if (Directory.Exists(logDirPath)) { string filePath = Path.Combine(logDirPath, $"Report-{DateTime.Today.Day}.txt"); bool hasDaily = File.Exists(filePath); // cerco file quotidiano... if (!hasDaily) { var fileList = Directory.GetFiles(logDirPath, "Report-*.txt"); DateTime lastMod = DateTime.Today.AddYears(-1); foreach (var cFile in fileList) { var tempFile = Path.Combine(logDirPath, cFile); var lwTime = File.GetLastWriteTime(tempFile); if (lwTime >= lastMod) { lastMod = lwTime; filePath = tempFile; } } } // leggo file string rawVal = File.ReadAllText(Path.Combine(logDirPath, filePath)); if (!string.IsNullOrEmpty(rawVal)) { // salvo in redis string redKey = redisMan.redHash($"IOB:CurrData:{cIobConf.codIOB}:LogFile:Act"); redisMan.setRSV(redKey, rawVal); answ = true; } } return answ; } /// /// Recupera da server set di dati specifici x IOB : qui per compilare files CSV /// /// protected override bool iobGetDataFromServer() { bool answ = false; bool okArt = false; bool okDoss = false; bool okPodl = false; bool okLVFasi = false; List listaPODL = new List(); List listaDoss = new List(); List listaArt = new List(); List anagLVFasi = new List(); Dictionary dictAF = new Dictionary(); // recupera dati da server tramite chiamate REST a MP/IO/IOB... var rawListArt = callUrl(urlGetCurrArt, false); var rawListDOSS = callUrl(urlGetCurrDOSS, false); var rawListPODL = callUrl(urlGetCurrPODL, false); var rawLVFasi = callUrl(urlGetListValFasiPodl, false); if (!string.IsNullOrEmpty(rawListArt)) { try { listaArt = JsonConvert.DeserializeObject>(rawListArt); okArt = listaArt.Count > 0; } catch (Exception exc) { lg.Error($"Errore: chiamata elenco ART ha restituito errore{Environment.NewLine}{exc}"); } } else { lg.Error($"Errore: chiamata elenco ART ({urlGetCurrArt}) ha restituito valore vuoto"); } if (!string.IsNullOrEmpty(rawListDOSS)) { try { listaDoss = JsonConvert.DeserializeObject>(rawListDOSS); okDoss = listaDoss.Count > 0; } catch (Exception exc) { lg.Error($"Errore: chiamata elenco DOSSIER ha restituito errore{Environment.NewLine}{exc}"); } } else { lg.Error($"Errore: chiamata elenco DOSSIER ({urlGetCurrDOSS}) ha restituito valore vuoto"); } if (!string.IsNullOrEmpty(rawListPODL)) { try { listaPODL = JsonConvert.DeserializeObject>(rawListPODL); okPodl = listaPODL.Count > 0; } catch (Exception exc) { lg.Error($"Errore: chiamata elenco DOSSIER ha restituito errore{Environment.NewLine}{exc}"); } } else { lg.Error($"Errore: chiamata elenco PODL ({urlGetCurrPODL}) ha restituito valore vuoto"); } if (!string.IsNullOrEmpty(rawLVFasi)) { try { anagLVFasi = JsonConvert.DeserializeObject>(rawLVFasi); dictAF = anagLVFasi.ToDictionary(x => x.value, x => x.label); okLVFasi = listaArt.Count > 0; } catch (Exception exc) { lg.Error($"Errore: chiamata elenco ListVal ha restituito errore{Environment.NewLine}{exc}"); } } else { lg.Error($"Errore: chiamata elenco ListVal ({urlGetListValFasiPodl}) ha restituito valore vuoto"); } answ = okPodl && okDoss && okArt && okLVFasi; if (answ) { // predispongo dati PODL ListaJobs = listaPODL .Select(x => new JobRow() { Matricola = x.CodArticolo, Commessa = $"PODL{x.IdxPromessa:00000000}", Articolo = x.CodArticolo, Descrizione = x.CodArticolo, DataIns = $"{x.InsertDate:dd/MM/yyyy}", OraIns = $"{x.InsertDate:HH:mm}", Lavorazione = $"{getLV(dictAF, x.KeyRichiesta)} {x.Note}".Trim() }) .ToList(); // predispongo dati articoli ListaArticoli = listaArt .Select(a => new ArtRow() { Matricola = a.CodArticolo, Articolo = a.Disegno, Descrizione = a.DescArticolo, LimiteVel = 100 }) .Distinct() .ToList(); // completo con dati DOSSIER foreach (var item in ListaArticoli) { string codArt = item.Matricola; var currDoss = listaDoss.Where(x => x.CodArticolo == codArt).FirstOrDefault(); if (currDoss != null) { DossierFluxLogDTO resultSet = JsonConvert.DeserializeObject(currDoss.Valore); // traduco AD MENTULAM... item.Peso_01 = getFluxValInt(resultSet, "POS_X"); item.Peso_02 = getFluxValInt(resultSet, "POS_Y"); item.Peso_03 = getFluxValInt(resultSet, "RAPID_OVER"); item.Peso_04 = getFluxValInt(resultSet, "POS_Z"); item.PosizCarrello_01 = getFluxValInt(resultSet, "TEMP_01"); item.PosizCarrello_02 = getFluxValInt(resultSet, "RAPID_OVER"); item.PosizCarrello_03 = getFluxValInt(resultSet, "POWER_01"); item.PosizCarrello_04 = getFluxValInt(resultSet, "POS_X"); } } } return answ; } /// /// Salvo valori PLC /// /// protected override void plcWriteParams(ref List updatedPar) { dataConf currMem = null; int byteSize = 0; byte[] MemBlock = new byte[1]; string memAddrWrite = ""; string serObj = ""; if (updatedPar != null) { // controllo i parametri... ne gestisco 4... foreach (var item in updatedPar) { try { memAddrWrite = ""; // cerco in area memMapWrite... if (memMap.mMapWrite.ContainsKey(item.uid)) { // recupero! currMem = memMap.mMapWrite[item.uid]; byteSize = currMem.size; memAddrWrite = currMem.memAddr; // salvo in memMap... currMem.value = item.reqValue; // registro x invio item.value = item.reqValue; item.reqValue = ""; MemBlock = new byte[byteSize]; 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}---------------"); lgInfo($"---------------MemBlock data---------------{Environment.NewLine}{BitConverter.ToString(MemBlock)}{Environment.NewLine}--------------- END data ---------------"); // salvo upsertKey(item.uid, item.value); } 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}"); } } } } /// /// Effettua sync dati /// protected override void processDataSync() { try { // fixme todo: test scrittura file csv... var taskGet = iobGetDataFromServer(); var taskWrite = iobWriteLocalCSV(); var taskSend = iobSendFTP(""); // richiesta check autoODL processAutoOdl(); // richiesta generazione quotidiana dossiers processAutoDossier(); // effettua gestione import file... processFileImport(); // effettua process ritorno ricette processRecipeFileRet(); } catch (Exception exc) { lgError($"Eccezione in processDataSync:{Environment.NewLine}{exc}"); } } /// /// Decremento duration del bit indicato secondo stesse regole di ritardo dei bit di wait /// /// protected void tryRedDuration(int bit2proc) { if (lastRedDuration.AddMilliseconds(periodoMSec / 20) < DateTime.Now) { switch (bit2proc) { case 2: bit2.duration--; break; case 3: bit3.duration--; break; case 4: bit4.duration--; break; case 5: bit5.duration--; break; case 6: bit6.duration--; break; case 7: bit7.duration--; break; default: break; } lastRedDuration = DateTime.Now; } } #endregion Protected Methods #region Private Fields private List alarmMessages = new List(); private int currSimAlarmCode = 0; private Random rnd = new Random(); #endregion Private Fields #region Private Methods /// /// Decodifica signal log x soitaab | FixMe ToDo: eliminare /// /// /// private static int decodeSoitaabLog(string val2test) { // di default NON EMERGENZA... int valInt = 128; switch (val2test) { case "START;1": valInt = 1 + 2 + 128; break; case "END;1": valInt = 1 + 128; break; case "START;0": case "END;0": case "HOLD;": valInt = 1 + 16 + 128; break; default: break; } return valInt; } /// /// Decodifica il resto dell'area x i dati accessori (allarmi, ...) /// private void decodeOtherData() { } //private int /// /// Effettua decodifica aree memoria alla bitmap usata x MAPO /// private void decodeToBaseBitmap() { // init a zero... B_input = 0; bool sendContapezzi = false; /* ----------------------------------------------------- * bitmap MAPO * B0: POWER_ON * B1: RUN * B2: pzCount * B3: allarme * B4: manuale * B5: allarme TCiclo (SLOW) * B6: WarmUp_CoolDown * B7: EmergArmed (1 = NON emergenza, 0 = emergenza) * B8: pallet 1 (SE doppio pallet) * B9: pallet 2 (SE doppio pallet) ----------------------------------------------------- */ // di base macchina in RUN B_input = 3; /*---------------------------------------- * Simulazione segnali con priorità: * - Power ON / OFF (bit0/1) * - ALLARMI * - MANUALE * - SLOW * - contapezzi * - emergenza * *----------------------------------------*/ // se simulo PowerOn/Off --> spegnimento con CoolDown e accensione con WarmUp.. if (simPowerOnOff) { DateTime adesso = DateTime.Now; // se l'orario è dopo le tOff (tipicamente 22) --> NO RUN... if (adesso.Hour >= tOff || adesso.Hour <= tOn) { // se prima/ultima mezz'ora è ancora accesa NON in run... if (adesso.AddMinutes(-30).Hour < tOff || adesso.AddMinutes(30).Hour > tOn) { B_input = 1; } else { B_input = 0; } // aggiungo NON emergenza... B_input += (1 << 7); } } // in primis verifico SE posso inviare in blocco i pezzi...... SE MP online e SE NON E' MULTI if (MPOnline) { // SE IOB online... if (IobOnline) { // se il contapezzi è OLTRE il valore inviato.... if (contapezziPLC > contapezziIOB) { string machName = ""; if (isMulti) { machName = $"{cIobConf.codIOB}|TAV_{nP}"; } // invio SOLO SE sono OLTRE i numSim pz e li invio TUTTI in blocco if ((contapezziPLC - contapezziIOB) > minSendPzCountBlock) { sigPzCount = true; trySendPzCountBlock(machName); sigPzCount = false; } // altrimenti invio 1 segnale else { // se NON STAVA inviando di già... if (!sigPzCount) { // segnalo BIT (1 pz) B_input += (1 << 2); sigPzCount = true; // salvo nuovo contapezzi (incremento di 1...) + richiesta refresh conteggio contapezziIOB++; needRefreshPzCount = true; // invio conferma contapezzi.. string fullUrl = $"{urlSetPzCount}{contapezziIOB}"; if (isMulti) { fullUrl = $"{urlSetPzCount.Replace(cIobConf.codIOB, $"{cIobConf.codIOB}|TAV_{nP}")}{contapezziPLC}"; } string retVal = utils.callUrl(fullUrl); // verifica salvataggio if (retVal != contapezziIOB.ToString()) { // errore salvataggio contapezzi lgInfo($"Errore salvataggio Contapezzi SIMULA 01: contapezziIOB {contapezziIOB} | risposta: {retVal}"); // rileggo il counter pezzi da server pzCntReload(true, machName); } } else { string retVal = utils.callUrl($"{urlSetPzCount}{contapezziIOB}"); if (retVal != contapezziIOB.ToString()) { // errore salvataggio contapezzi lgInfo($"Errore salvataggio Contapezzi SIMULA 02: contapezziIOB {contapezziIOB} | risposta: {retVal}"); if (!isMulti) { // rileggo il counter pezzi da server pzCntReload(true, machName); } } sigPzCount = false; } } lgInfo($"S01: Valori contatori: contapezzi macchina contapezziPLC: {contapezziPLC} | contapezziIOB: {contapezziIOB} | contapezziPLC > contapezziIOB"); } } else { // imposto currODL a vuoto! currODL = ""; if (periodicLog) { lgInfo($"SIMULA | Lettura ODL non effettuata: IobOnline: {IobOnline} | currODL impostato a vuoto"); } } } // questa parte la processo SOLO SE sono in run --> B_input == 3 if (B_input == 3 || B_input == 7) { // di base NON emergenza B_input += (1 << 7); // ora controllo il resto... if (bit3 != null && bit3.wait <= 0) { // segnalo BIT B_input += (1 << 3); // se non ancora fatto, simulo insorgenza allarme if (currSimAlarmCode == 0) { // simulo una bitmap di max 8 allarmi, 1..255 currSimAlarmCode = rnd.Next(1, 255); // invio allarme, 0 --> sim sendAlarmVariations("SIM_BANK", 0, 0, (uint)currSimAlarmCode, alarmMessages); } // decremento duration tryRedDuration(3); // controllo se sia scaduta la duration... in quel caso reset... if (bit3.duration <= 0) { bit3 = setupSimPar("SIM_ALARM"); // invio fine allarme: sim --> 0 sendAlarmVariations("SIM_BANK", 0, (uint)currSimAlarmCode, 0, alarmMessages); // simulo terminazione allarme currSimAlarmCode = 0; } tryConfirm(); } else if (bit6 != null && bit6.wait <= 0) { // segnalo BIT B_input += (1 << 6); // decremento duration tryRedDuration(6); // controllo se sia scaduta la duration... in quel caso reset... if (bit6.duration <= 0) { bit6 = setupSimPar("SIM_WUCD"); } // in warm up: provo split ODL processAutoOdl(); } else if (bit7 != null && bit7.wait <= 0) { // segnalo BIT NEGATO (emergenza) B_input -= (1 << 7); // decremento duration tryRedDuration(7); // controllo se sia scaduta la duration... in quel caso reset... if (bit7.duration <= 0) { bit7 = setupSimPar("SIM_EMRG"); } // in emergenza: provo split ODL processAutoOdl(); } else if (bit4 != null && bit4.wait <= 0) { // segnalo BIT B_input += (1 << 4); // decremento duration tryRedDuration(4); // controllo se sia scaduta la duration... in quel caso reset... if (bit4.duration <= 0) { bit4 = setupSimPar("SIM_MANU"); } // in manuale: provo split ODL processAutoOdl(); } else if (bit2 != null && bit2.wait <= 0) { // salvo nuovo contapezziPLC var rand = new Random(); // se online vero delta (1..10) altrimenti 1 int delta = IobOnline ? rand.Next(1, 10) : 1; if (MPOnline) { // solo se MP online... contapezziPLC += delta; lgInfo($"S01: Valori contatori: contapezzi macchina contapezziPLC: {contapezziPLC} | contapezziIOB: {contapezziIOB} | aggiunto delta {delta}"); } // SOLO SE sono online... if (IobOnline) { // se multipallet --> cP a zero! if (isMulti) { cP = 0; } // se NON Multi fa contapezzi... else { // SE NON SONO GIA' OLTRE il contapezzi if (contapezziIOB < contapezziPLC) { // segnalo BIT (1 pz) B_input += (1 << 2); } } // decremento duration tryRedDuration(2); // controllo se sia scaduta la duration... in quel caso reset... if (bit2.duration <= 0) { bit2 = setupSimPar("SIM_PZCNT"); // salvo nuovo contapezzi (incremento di 1...) + richiesta refresh conteggio contapezziIOB++; needRefreshPzCount = true; lgInfo($"S01: Valori contatori: contapezzi macchina contapezziPLC: {contapezziPLC} | contapezziIOB: {contapezziIOB} - incremento contapezzi per bit2.duration <= 0"); // invio SOLO SE il contapezzi PLC è != contapezzi IOB... if (contapezziPLC != contapezziIOB) { sendContapezzi = true; } } if (sendContapezzi) { // controllo se ALMENO sia pingabile il server if (checkServerAlive) { string fullUrl = $"{urlSetPzCount}{contapezziIOB}"; // invio a server contapezzi (aggiornato) if (isMulti) { fullUrl = $"{urlSetPzCount.Replace(cIobConf.codIOB, $"{cIobConf.codIOB}|TAV_{nP}")}{contapezziIOB}"; } string retVal = utils.callUrl(fullUrl); // verifica se tutto OK if (retVal != contapezziIOB.ToString()) { // errore salvataggio contapezzi lgInfo($"Errore salvataggio Contapezzi SIMULA 03: contapezziIOB {contapezziIOB} | risposta: {retVal}"); // se non sono multi... if (!isMulti) { // rileggo il counter pezzi da server pzCntReload(true); } } // resetto timer... lastPzCountSend = DateTime.Now; } } // provo a fare split ODL... processAutoOdl(); } else { // decremento duration tryRedDuration(2); if (bit2.duration <= 0) { bit2 = setupSimPar("SIM_PZCNT"); } } } else if (bit5 != null && bit5.wait <= 0 || tcMan.alarmDelayTC) { // segnalo BIT B_input += (1 << 5); // decremento duration tryRedDuration(5); // controllo se sia scaduta la duration... in quel caso reset... if (bit5.duration <= 0) { bit5 = setupSimPar("SIM_SLOW"); } } // se multi gestisco il bit delle tavole... if (isMulti) { // se sono in fase di fronte d'uscita (invio contapezzi) INVERTO nP... if (sendContapezzi) { nP = nP == 1 ? 2 : 1; // assegno a cP il valore nP... cP = nP; contapezziPLC++; // invio contapezzi... string fullUrl = $"{urlSetPzCount.Replace(cIobConf.codIOB, $"{cIobConf.codIOB}|TAV_{nP}")}{contapezziIOB}"; string retVal = utils.callUrl(fullUrl); } // se cP > 0 --> segnalo bit tavola... if (cP == 1) { B_input += (1 << 8); } else if (cP == 2) { B_input += (1 << 9); } } // gestione simulazione utente if (simRC != null && simRC.wait <= 0) { // preparo record controlli... guasto se mi esce un secondo divisibile x 25 DateTime adesso = DateTime.Now; int esitoNum = adesso.Second % 25 == 0 ? 0 : 1; string note = esitoNum == 1 ? "" : $"SIM Controllo fallito alle {DateTime.Now:yyyy-MM-dd HH:mm:ss}"; string sVal = $"MatrOpr: {matrOpr} | Esito: {esitoNum} | note: {note}"; // accodo x invio accodaUserLog(sVal, qEncodeULog("RC", note, matrOpr, "", esitoNum)); // decremento duration simRC.duration--; // controllo se sia scaduta la duration... in quel caso reset... if (simRC.duration <= 0) { simRC = setupSimPar("SIM_RC"); } } if (simRS != null && simRS.wait <= 0) { // preparo record controlli... guasto se mi esce un secondo divisibile x 25 DateTime adesso = DateTime.Now; int causaleInt = adesso.Second % 7 + 1; int numSca = adesso.Second % 5 + 1; string causale = $"{causaleInt:00}"; string note = $"SIM Scarto [{causale} x {numSca}] {DateTime.Now:yyyy-MM-dd HH:mm:ss}"; string sVal = $"MatrOpr: {matrOpr} | Causale: {causale} | note: {note}"; // accodo x invio accodaUserLog(sVal, qEncodeULog("RS", note, matrOpr, causale, numSca)); // decremento duration simRS.duration--; // controllo se sia scaduta la duration... in quel caso reset... if (simRS.duration <= 0) { simRS = setupSimPar("SIM_RS"); } } if (simDich != null && simDich.wait <= 0) { // preparo record dichiarazione... DateTime adesso = DateTime.Now; bool recLogin = adesso.Second % 5 == 0 ? false : true; string note = recLogin ? "SIM Login Utente STEAMWARE USER" : $"SIM Nota automatica alle {DateTime.Now:yyyy-MM-dd HH:mm:ss}"; string codTag = recLogin ? "UserLogin" : "Nota"; string sVal = $"MatrOpr: {matrOpr} | codTag: {codTag} | nota: {note}"; // accodo x invio accodaUserLog(sVal, qEncodeULog("DI", note, matrOpr, codTag, 0)); // decremento duration simDich.duration--; // controllo se sia scaduta la duration... in quel caso reset... if (simDich.duration <= 0) { simDich = setupSimPar("SIM_DICH"); } } // init obj display newDisplayData currDispData = new newDisplayData(); currDispData.counter = contapezziIOB; currDispData.semOut = Semaforo.SV; raiseRefresh(currDispData); } } /// /// Esegue processing + invio dati tab SignLog | FixMe ToDo: eliminare /// /// /// private bool processFluxLogTable(DateTime adesso) { bool fatto = false; // leggo eventuali dati di fluxlog ed invio... if (fluxLogData != null && fluxLogData.Count > 0) { string sVal = ""; foreach (var fLog2send in fluxLogData) { var kvpFlux = fLog2send.valString.Split(';'); if (kvpFlux.Count() > 1) { sVal = $"[DYNDATA] |{fLog2send.dtRif:yyyy-MM-dd HH:mm:ss}|{kvpFlux[0]}|{kvpFlux[1]}"; // chiamo accodamento con dataora corretta... accodaFLog(sVal, qEncodeFLog(fLog2send.dtRif, kvpFlux[0], kvpFlux[1])); } } // svuoto coda invio fluxlog... fluxLogData = new List(); // fatto! fatto = true; } return fatto; } /// /// Esegue processing + invio dati tab SignLog /// /// /// private bool processSignLogTable(DateTime adesso) { bool fatto = false; // init oggetto x processing Dictionary sigLogFromFile = new Dictionary(); // recupero i dati dai logs files, attuale e nuovo... string fileAct = redisMan.getRSV(redKeyLogfileAct); string fileLast = redisMan.getRSV(redKeyLogfileLast); // confronto con i dati dell'ultimo LOG FILE processato int lenghtAct = fileAct != null ? fileAct.Length : 0; int lenghtLast = fileLast != null ? fileLast.Length : 0; //if (lenghtAct > 0 && lenghtAct != lenghtLast) if (lenghtAct > 0) { List logNew = new List(); List sigLogData = new List(); List logLast = getGenLogFromMachineLog(fileLast); List logAct = getGenLogFromMachineLog(fileAct); // SE c'è prendo ultima riga inviata x confronto... var fileLastEnd = logLast.LastOrDefault(); if (fileLastEnd != null) { // ora prendo SOLAMENTE le righe nuove logNew = logAct .Where(x => x.dtRif > fileLastEnd.dtRif) .ToList(); } else { logNew = logAct; } // solo SE ho nuovi dati.... if (logNew.Count > 0) { // processo le righe nuove e le accodo in redis come elenco FluxLog da inviare // (appoggio in redis)... andando ad accodarle... var currFLD = fluxLogData; currFLD.AddRange(logNew); fluxLogData = currFLD; // estraggo solo info x sig log sigLogData = logNew .Where(x => x.valString.StartsWith("START") || x.valString.StartsWith("HOLD") || x.valString.StartsWith("END")) .OrderBy(x => x.dtRif) .ToList(); // processo le righe nuove trasformando al volo SOLO eventi... foreach (var sLog2send in sigLogData) { /* ----------------------------------------------------- * bitmap MAPO STANDARD 60 * B0: 001 POWER_ON * B1: 002 RUN * B2: 004 pzCount * B3: 008 allarme * B4: 016 manuale * B5: 032 slowTC * B6: 064 WarmUpCoolDown * B7: 128 EmergArmata * ----------------------------------------------------- */ int valInt = decodeSoitaabLog(sLog2send.valString); string currVal = getEncodSigLog(sLog2send.dtRif, valInt, counterSigIN); // verifico non sia in veto invio iniziale... if (queueInEnabCurr) { // --> accodo (valore già formattato)! QueueIN.Enqueue(currVal); // loggo! lgTrace(string.Format("[QUEUE-IN] {0}", currVal)); counterSigIN++; if (counterSigIN > 9999) { counterSigIN = 0; } } else { lgDebug($"[VETO FOR QUEUE-IN] | {currVal} - MESSAGE NOT SENT | {adesso:yyyyMMdd_HHmmss}"); checkVetoQueueIn(); } } /* ---------------------------------------------------------- * processo righe x costruire una lista di eventi produzione: * - elenco di eventi contapezzi * - lista xODL come articolo + quantità prodotta (_1, _2, ...) * - eventuale ultimo xODL che resta aperto * * successivamente processing + invio info raccolte * - recupero PODL aperti * - apertura/chiusura PODL esistenti * - creazione PODL mancanti * - dichiarazione contapezzi * * ---------------------------------------------------------- */ string sVal = ""; string descrArt = ""; List elencoProdBatch = new List(); ProdBatchData lastProdBatch = new ProdBatchData(); // ora processo TUTTO x il generico caso fluxLog...... foreach (var fLog2send in logNew) { // separo per ";" var currData = fLog2send.valString.Split(';'); if (currData.Length > 1) { if (sendFluxOnRead) { // per prima cosa fluxLog a prescindere... sVal = $"[LOGFILE]{currData[0]}|{currData[1]}"; // ...e chiamo accodamento accodaFLog(sVal, qEncodeFLog(fLog2send.dtRif, currData[0], currData[1])); } switch (currData[0]) { case "END": // se è un end --> è comunque NON + running isRunningState = false; // chiudo batch precedente + in elenco (SE era valido...) if (!string.IsNullOrEmpty(lastProdBatch.codArt)) { lastProdBatch.dtEnd = fLog2send.dtRif; elencoProdBatch.Add(lastProdBatch); } // reset articolo.. lastArtDescr = ""; // nuovo batch VUOTO lastProdBatch = new ProdBatchData(); contapezziPLC = 0; break; case "START": isRunningState = currData[1] == "1"; break; case "PARTCODE": // inizio confronto articolo con precedente x capire se sia NUOVO descrArt = currData[1]; // devo scartare i "doppi percorsi" if (descrArt != lastArtDescr) { // verifico se sia DAVVERO cambiato articolo... prendo // articolo senza indice (_1, _2, _3, ...) var artDataLast = lastArtDescr.Split('_'); var artDataNew = descrArt.Split('_'); // nuovo articolo (NON vuoto) if (!string.IsNullOrEmpty(artDataNew[0]) && artDataLast[0] != artDataNew[0]) { // chiudo batch precedente + in elenco (SE era valido...) if (!string.IsNullOrEmpty(lastProdBatch.codArt)) { // chiudo prod lastProdBatch.dtEnd = fLog2send.dtRif; elencoProdBatch.Add(lastProdBatch); } // nuovo batch lastProdBatch = new ProdBatchData() { codArt = artDataNew[0], dtStart = fLog2send.dtRif, numPz = 1 }; } // solo nuova copia articolo else { // aumento numPezzi batch lastProdBatch.numPz++; } // salvo nuovo art processato lastArtDescr = descrArt; } break; default: break; } } } // recupero elenco PODL corrente... List reqPOdlList = MachineNextPodl(); // adesso processo tutti i prodBatch collezionati x invio foreach (var item in elencoProdBatch) { int currIdxPOdl = 0; // cerco se articolo sia in elenco PODL var listPOdl = reqPOdlList .Where(x => x.CodArticolo.Equals(item.codArt, StringComparison.InvariantCultureIgnoreCase)) .ToList(); // se trovato --> invio start/stop PODL if (listPOdl != null && listPOdl.Count > 0) { var currPodl = listPOdl.OrderBy(x => x.IdxPromessa).FirstOrDefault(); if (currPodl != null) { currIdxPOdl = currPodl.IdxPromessa; // elimino da elenco PODL reqPOdlList.Remove(currPodl); } } else { // se NON trovato --> creo PODL, poi avvio/chiudo currIdxPOdl = TryCreatePodl(item.codArt, CodGruppoIob, item.numPz); } // se idxPOdl valido if (currIdxPOdl > 0) { // mando apertura PODL SendStartPodl(currIdxPOdl, item.dtStart); // chiudo PODL... if (item.dtEnd != null) { // invio pezzi 3 sec prima di chiusura SendPzIncrAtDate(item.numPz, ((DateTime)item.dtEnd).AddSeconds(-3)); // chiudo SendClosePOdl(currIdxPOdl, (DateTime)item.dtEnd); } } } // salvo in last il valore di act... redisMan.setRSV(redKeyLogfileLast, fileAct); } fatto = true; } return fatto; } /// /// Setup parametri di simulazione per BIT indicato /// /// private simPar setupSimPar(string keyName) { simPar answ = new simPar(); if (cIobConf.optPar.Count > 0) { if (cIobConf.optPar.ContainsKey(keyName)) { string fullVal = getOptPar(keyName); if (!string.IsNullOrEmpty(fullVal) && fullVal.IndexOf("|") > 0) { string[] param = fullVal.Split('|'); int.TryParse(param[0], out answ.wait); int.TryParse(param[1], out answ.duration); // simulo valore wait: in media target, range +/- 80% answ.wait = rnd.Next(answ.wait / 5, answ.wait * 9 / 5); // simulo valore duration: in media target, range +/- 50% answ.duration = rnd.Next(answ.duration / 2, answ.duration * 3 / 2); // verifico siano > 0... answ.wait = answ.wait > 1 ? answ.wait : 2; answ.duration = answ.duration > 1 ? answ.duration : 2; } } } return answ; } /// /// Provo ad effettuare conferma pezzi SE sono oltre uno scalino del 10% dall'ultima conferma... /// /// private void tryConfirm() { } /// /// provo a chiamare split ODL /// private void trySplitOdl() { //if (!isMulti) //{ // solo se ODL è in lavorazione da ALMENO minDurataODL minuti... DateTime inizioOdl = DateTime.Now.AddDays(-1); string rawDataInizio = callUrl(urlInizioOdlIob, false); DateTime.TryParse(rawDataInizio, out inizioOdl); if (DateTime.Now.Subtract(inizioOdl).TotalMinutes > minDurataODL) { // invio reset ODL... forceSplitOdl(); } //} } #endregion Private Methods } }