using IOB_UT_NEXT; using IOB_UT_NEXT.Config; using IOB_UT_NEXT.Objects; using IOB_UT_NEXT.Services.Files; using IOB_UT_NEXT.Services.Protocols; using MapoSDK; using Newtonsoft.Json; using System; using System.Globalization; using System.IO; using System.Text.RegularExpressions; using System.Threading; using System.Windows.Forms; namespace IOB_WIN_FILE.IobFile { public class FileEurom63 : IobFile.FileGen { #region Public Constructors /// /// Estende l'init della classe base... /// /// Form chiamante /// Configurazione (v 4.x) public FileEurom63(AdapterFormNext caller, IobConfTree IobConfFull) : base(caller, IobConfFull) { lgInfo("INIT IobFileEurom63"); appPath = Path.GetDirectoryName(Application.ExecutablePath); string MAX_DELAY_SEC = getOptPar("MAX_DELAY_SEC"); string CACHE_MULT = getOptPar("CACHE_MULT"); int.TryParse(MAX_DELAY_SEC, out maxDelaySec); int.TryParse(CACHE_MULT, out cacheMult); #if DEBUG maxDelaySec = 60 * 60 * 24; #endif } #endregion Public Constructors #region Public Methods /// /// Effettua vero processing contapezzi /// public override void processContapezzi() { if (utils.CRB("enableContapezzi")) { try { // controllo se sono in sampling della produzione if (actLevel >= Eurom63.ComLevel.ProdRequested) { /************************************************************ * * EXAMPLE * DATE, TIME, ActCntCyc, ActTimCyc, ActTimFill, @OutXhour, SetDescJob * 20201007, 21:29:52, 5302, 8.61, 0.50, 10058, Nr. 1000987654.01 * * devo prendere il 3° valore * * ************************************************************/ // leggo il file della produzione HARD CODED... var sessProd = confE63.ActiveSessions[5]; string currPzCount = ""; if (sessProd != null) { if (sessProd.Active) { // nome file... string fileName = $"{BaseDir}\\{sessProd.SessionName}.DAT"; if (File.Exists(fileName)) { string rawData = ""; using (var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (var textReader = new StreamReader(fileStream)) { rawData = textReader.ReadToEnd(); } // ora splitto in linee string[] rawLines = Regex.Split(rawData, "\r\n|\r|\n"); int numRow = rawLines.Length; // devo avere almeno 2 righe... if (numRow >= 2) { string[] statusData = rawLines[1].Split(','); currPzCount = statusData[2].Trim(); // salvo se valido if (!string.IsNullOrEmpty(currPzCount)) { int newVal = -1; Int32.TryParse(currPzCount, out newVal); // verifico SE il contapezzi vada moltiplicato x il // fattore pzPallet... if (confE63.PzPallet > 1) { newVal = newVal * confE63.PzPallet; } // aggiorno contapezzi contapezziPLC = newVal > -1 ? newVal : contapezziPLC; } // ora verifico SE siano validi anche le dataora dei valori // letti (< 20 sec ritardo da ora...) string data = statusData[0].Trim(); string ora = statusData[1].Trim(); DateTime adesso = DateTime.Now; DateTime lastPub = adesso.AddMinutes(-1); CultureInfo provider = CultureInfo.InvariantCulture; try { lastPub = DateTime.ParseExact($"{data} {ora}", "yyyyMMdd HH:mm:ss", provider); } catch { } if (Math.Abs(lastPub.Subtract(adesso).TotalSeconds) > maxDelaySec) { sessProd.SessionValidUntil = adesso; // elimino file RSP... cleanupResp(sessProd.SessionName); } } } } else { actLevel = Eurom63.ComLevel.StatusRequested; } } else { actLevel = Eurom63.ComLevel.StatusRequested; } } } catch (Exception exc) { lgError($"Eccezione in processContapezzi:{Environment.NewLine}{exc}"); } } } /// /// Effettua lettura semafori principale Parametri da /// aggiornare x display in form /// public override void readSemafori(ref newDisplayData currDispData) { base.readSemafori(ref currDispData); // in primis controllo status... checkCommStatus(); // init a zero... B_input = 0; string currStatus = "99999"; bool readDone = false; DateTime adesso = DateTime.Now; // leggo il file dela produzione HARD CODED... var sessStatus = confE63.ActiveSessions[4]; // ciclo! try { // controllo se sono in sampling dello stato if (actLevel >= Eurom63.ComLevel.StatusRequested) { /* ----------------------------------------------------- * bitmap MAPO * B0: POWER_ON * B1: RUN * B2: pzCount * B3: allarme * B4: manuale * B5: allarme TCiclo (SLOW) * B6: avvio/spegnimento * B7: Emergenza armata (1= pronto, 0 = emergenza) --> DA INVIARE!!! ----------------------------------------------------- */ /****************************************************************** * * EXAMPLE file content * DATE, TIME, ActStsMach * 20201007, 21:28:10, 0A000 * * Configurazione array status: 5 char status decoding * * Pos1: (status) * 0: poweron * 1: poweroff * * Pos2: (mode) * A: AUTO * S: SEMI auto * M: Manual * U: Setup * H: Hord * C: Maintenance * 0: Unknown * I: Idle * * Pos3: (assist call) * 0: No assistance * 2: Assistance required * * Pos4: (Bad part) * 0: last cycle not bad * 1: last cycle bad * * Pos5: Active Alarm * 0: No alarm * 1: Alarm * * * *******************************************************************/ if (sessStatus != null) { if (sessStatus.Active) { // nome file... string fileName = $"{BaseDir}\\{sessStatus.SessionName}.DAT"; if (File.Exists(fileName)) { string rawData = ""; using (var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (var textReader = new StreamReader(fileStream)) { rawData = textReader.ReadToEnd(); } if (!string.IsNullOrEmpty(rawData)) { // ora splitto in linee string[] rawLines = Regex.Split(rawData, "\r\n|\r|\n"); int numRow = rawLines.Length; // devo avere almeno 2 righe... if (numRow >= 2) { string[] statusData = rawLines[1].Split(','); if (statusData.Length >= 3) { currStatus = statusData[2].Trim(); if (!string.IsNullOrEmpty(currStatus)) { currDispData.semIn = Semaforo.SV; } // salvo in cache! Last_CurrStatus.Value = currStatus; Last_CurrStatus.ValidUntil = DateTime.Now.AddSeconds(maxDelaySec * cacheMult); // ora verifico SE siano validi anche le dataora dei // valori letti (< 20 sec ritardo da ora...) string data = statusData[0].Trim(); string ora = statusData[1].Trim(); DateTime lastPub = adesso.AddMinutes(-1); CultureInfo provider = CultureInfo.InvariantCulture; try { lastPub = DateTime.ParseExact($"{data} {ora}", "yyyyMMdd HH:mm:ss", provider); } catch { } if (Math.Abs(lastPub.Subtract(adesso).TotalSeconds) > maxDelaySec) { sessStatus.SessionValidUntil = adesso; // elimino file RSP... cleanupResp(sessStatus.SessionName); } readDone = true; } else { lgError($"Decodifica StatusData in errore: trovati {statusData.Length} campi in {rawLines}"); // se valido RILEGGO ultimo curr status if (Last_CurrStatus.ValidUntil > adesso) { currStatus = Last_CurrStatus.Value; } } } else { lgError($"Lettura file stato in errore: trovate {numRow} linee"); } } // se valido RILEGGO ultimo curr status if (Last_CurrStatus.ValidUntil > adesso) { currStatus = Last_CurrStatus.Value; } } else { // se valido RILEGGO ultimo curr status if (Last_CurrStatus.ValidUntil > adesso) { currStatus = Last_CurrStatus.Value; } // abbasso status... actLevel--; } } else { // se valido RILEGGO ultimo curr status if (Last_CurrStatus.ValidUntil > adesso) { currStatus = Last_CurrStatus.Value; } } } else { // se valido RILEGGO ultimo curr status if (Last_CurrStatus.ValidUntil > adesso) { currStatus = Last_CurrStatus.Value; } } // processo il currentStatus... parto da poweron B_input = currStatus[0] == '1' ? 0 : 1; // aggiungo il bit NON emergenza B_input += (1 << 7); // ora MODE switch (currStatus[1]) { case 'A': B_input += (1 << 1); break; case 'S': case 'M': case 'U': B_input += (1 << 4); break; default: // loggo cosa trovo (CREDO break; } // ora cerco allarmi if (currStatus[4] == '1') { B_input += (1 << 3); } // controllo se diverso faccio log! if (!B_input.Equals(Last_B_Input.Value)) { lgInfo($"B_Input variato: {Last_B_Input.Value} --> {B_input} | currStatus: {currStatus}"); } // salvo B_Input in cache! Last_B_Input.Value = B_input; Last_B_Input.ValidUntil = DateTime.Now.AddSeconds(maxDelaySec * cacheMult); } // se disponibile riporto B_Input precedente else { if (Last_B_Input.ValidUntil > adesso) { B_input = Last_B_Input.Value; } } // riporto bitmap... reportRawInput(ref currDispData); } catch (Exception exc) { lgError(exc, "Errore in readSemafori x IOB FILE"); if (currDispData != null) currDispData.semIn = Semaforo.SR; } // ultimo controllo... if (!readDone) { // se valido RILEGGO ultimo B_INPUT if (Last_B_Input.ValidUntil > adesso) { B_input = Last_B_Input.Value; } } } /// /// Effettua reset del contapezzi, NON PERMESSO per EM63 (read only) /// /// public override bool resetContapezziPLC(string codTav) { bool answ = false; return answ; } /// /// Effettua IMPOSTAZIONE FORZATA del contapezzi, NON PERMESSO per EM63 (read only) /// /// public override bool setcontapezziPLC(int newPzCount, string codTav) { bool answ = false; return answ; } /// /// Connessione /// public override void tryConnect() { var nextLevel = Eurom63.ComLevel.IsConnected; var connectSession = confE63.ActiveSessions[0]; processSession(nextLevel, ref connectSession); queueInEnabCurr = true; } /// /// Disconnessione /// public override void tryDisconnect() { connectionOk = false; queueInEnabCurr = false; try { abortPrevJob(); cleanupFolder(); } catch (Exception exc) { lgError(exc, "Eccezione in tryDisconnect"); } } #endregion Public Methods #region Internal Methods /// /// Metodi preliminari x comunicazione: /// - richiesta connessione /// - richiesta stato attivo /// internal void checkCommStatus() { // init obj display newDisplayData currDispData = new newDisplayData(); currDispData.semIn = Semaforo.ND; switch (actLevel) { case Eurom63.ComLevel.None: tryConnect(); currDispData.semIn = Semaforo.SS; break; case Eurom63.ComLevel.IsConnected: requestInfo(); currDispData.semIn = Semaforo.SS; break; case Eurom63.ComLevel.HasInfo: setMachineTime(); currDispData.semIn = Semaforo.SS; break; case Eurom63.ComLevel.TimeSet: abortPrevJob(); currDispData.semIn = Semaforo.SG; break; case Eurom63.ComLevel.ChannelOk: requestStatusData(); currDispData.semIn = Semaforo.SG; break; case Eurom63.ComLevel.StatusRequested: requestProdData(); currDispData.semIn = Semaforo.SV; break; case Eurom63.ComLevel.ProdRequested: checkSampling(); currDispData.semIn = Semaforo.SV; break; default: break; } if (utils.CRB("verbose")) { lgInfo($"DONE checkCommStatus | actLevel {actLevel}"); } raiseRefresh(currDispData); } /// /// Pulizia preliminare folder comunicazione /// internal override void cleanupFolder() { // elimino OGNI file per tipo configurato foreach (var cleanExt in confE63.cleanupExt) { string[] file2del = Directory.GetFiles(BaseDir, cleanExt); foreach (var file in file2del) { try { File.Delete(file); } catch { } } } } /// /// Pulizia folder dai file RSP della sessione /// internal void cleanupResp(string sessionName) { string[] file2del = Directory.GetFiles(BaseDir, $"{sessionName}.RSP"); foreach (var file in file2del) { try { File.Delete(file); } catch { } } } /// /// Ricarica conf adapter... /// internal override void reloadAdapterConf() { // init obj display newDisplayData currDispData = new newDisplayData(); lgInfo("BEGIN reloadAdapterConf"); // inizializzo LUT decodifica string jsonConf = getOptPar("LUT_CONF"); if (!string.IsNullOrEmpty(jsonConf)) { string jsonFullPath = $"{Application.StartupPath}/DATA/CONF/{jsonConf}"; lgInfo($"Apertura file {jsonFullPath}"); StreamReader reader = new StreamReader(jsonFullPath); string jsonData = reader.ReadToEnd(); if (!string.IsNullOrEmpty(jsonData)) { try { confE63 = JsonConvert.DeserializeObject(jsonData); // salvo baseUri BaseDir = confE63.BaseDir; lgInfo($"baseDir = {BaseDir}"); // imposto a zero la bitmap x riavvio! B_input = 0; // FORZO invio dati... accodaSigIN(ref currDispData); // loggo! lgInfo($"init input bitmap to zero: {B_input}"); } catch (Exception exc) { lgError(exc, "Eccezione in decodifica conf json"); } } reader.Dispose(); } lgInfo("DONE reloadAdapterConf"); raiseRefresh(currDispData); } #endregion Internal Methods #region Protected Fields protected static DateTime lastStatusDecr = DateTime.Now; /// /// step di comunicazione attivo /// protected Eurom63.ComLevel actLevel = Eurom63.ComLevel.None; /// /// DIrectory eseguibile corrente /// protected string appPath = Directory.GetCurrentDirectory(); /// /// Moltiplicatore durata cache /// protected int cacheMult = 4; /// /// Oggetti decodificati da pagina /// protected Eurom63.ProtoConf confE63; /// /// Valore currStatus validato (per gestione "disconnessioni") /// protected CachedInt Last_B_Input = new CachedInt() { Value = 0 }; /// /// Valore currStatus validato (per gestione "disconnessioni") /// protected CachedString Last_CurrStatus = new CachedString() { Value = "00000" }; /// /// Massimo delay lettura dati prima di considerarli scaduti (30 sec, ma x test 1 gg) /// protected int maxDelaySec = 30; #endregion Protected Fields #region Protected Methods protected void abortPrevJob() { var nextLevel = Eurom63.ComLevel.ChannelOk; var connectSession = confE63.ActiveSessions[3]; processSession(nextLevel, ref connectSession); #if false // qui per sicurezza PULISCE TUTTO cleanupFolder(); #endif } /// /// Verifica una sessione configurata (ovvero la comunicazione su TUTTI i file associati) /// /// /// protected bool checkRequest(Eurom63.Session session) { bool answ = false; string fileName = ""; if (session != null) { fileName = $"{BaseDir}\\{session.SessionName}.REQ"; answ = File.Exists(fileName); } return answ; } /// /// Verifica se ci sia una risposta POSITIVA /// /// /// protected bool checkResp(Eurom63.Session session) { bool answ = false; string fileName = ""; if (session != null) { fileName = $"{BaseDir}\\{session.SessionName}.RSP"; if (File.Exists(fileName)) { // verifico contenuto //string rawData = File.ReadAllText(fileName); string rawData = ""; // lettura in modo NON esclusivo... using (var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (var textReader = new StreamReader(fileStream)) { rawData = textReader.ReadToEnd(); } // se la stringa session.RespOk contiene | significa ci sono + valori ammessi e // li controlla tutti if (session.RespOk.Contains("|")) { // splitto e testo tutti... string[] validRespOk = session.RespOk.Split('|'); foreach (var item in validRespOk) { answ = answ || rawData.Contains(item); } } else { answ = rawData.Contains(session.RespOk); } } } return answ; } /// /// verifica periodica dei campionamenti: /// - i due jobs devono essere attivi e non scaduti /// - non devo aver già resettato... /// protected void checkSampling() { var currSession = confE63.ActiveSessions[4]; checkSessionActive(currSession, Eurom63.ComLevel.ChannelOk); currSession = confE63.ActiveSessions[5]; checkSessionActive(currSession, Eurom63.ComLevel.ChannelOk); } /// /// Elimina i file della sessione indicata (SE è un task ciclico --> solo RSP) /// /// /// protected bool cleanupSession(Eurom63.Session session) { bool answ = false; if (session != null) { // solo se scaduta validità... if (session.SessionValidUntil < DateTime.Now) { string searchPattern = $"{session.SessionName}.*"; // task ciclico? if (session.Cycle) { // solo risposta! searchPattern = $"{session.SessionName}.RSP"; } string[] file2del = Directory.GetFiles(BaseDir, searchPattern); foreach (var file in file2del) { try { File.Delete(file); } catch { } } } } return answ; } /// /// Processa una sessione /// - andando a verificare l'esistenza della REQ + se esito positivo pulizia /// - andando a richeidere di nuovo risposta /// /// /// protected void processSession(Eurom63.ComLevel nextLevel, ref Eurom63.Session connectSession) { if (connectSession != null) { // controllo esistenza directory --> segno connected... connectionOk = Directory.Exists(BaseDir); DateTime adesso = DateTime.Now; if (connectionOk) { // verifico se ci sia risp CONNECT if (checkResp(connectSession)) { // aggiorno livello actLevel = nextLevel; parentForm.displayTaskAndLog($"Adp Level: {nextLevel}"); // elimino file sessione cleanupSession(connectSession); connectSession.Active = connectSession.Cycle; connectSession.Passed = true; } // verifico SE ci sia la richiesta sennò la chiedo... else if (!checkRequest(connectSession)) { copyRequestFiles(connectSession, adesso); } // richiedo SE non ci fosse i dati CONNECT... else { // evito di richiedere SE non fosse già scaduta richiesta... if (adesso > connectSession.RetryVeto || !checkRequest(connectSession)) { // pulisco eventuali risp vecchie cleanupResp(connectSession.SessionName); copyRequestFiles(connectSession, adesso); } } } else { // aspetto prima di riprovare... Thread.Sleep(50); } } else { // aspetto prima di riprovare... Thread.Sleep(50); } } /// /// Processa i file della sessione indicata (copy + transform) /// /// /// protected bool processSessionFile(Eurom63.Session session) { bool answ = false; if (session != null) { string fileFrom = ""; string fileTo = ""; // processo OGNI file sessione x farne copia foreach (var file2Proc in session.FileList) { fileFrom = $"{appPath}\\{file2Proc.Path}"; fileTo = $"{BaseDir}\\{Path.GetFileName(file2Proc.Path)}"; string rawData = File.ReadAllText(fileFrom); if (file2Proc.OprReq == Eurom63.FileOpr.Copy) { // scrivo! File.WriteAllText(fileTo, rawData); } else { // leggo file originale... e processo sostituzioni rawData = rawData.Replace("{DTNow}", DateTime.Now.ToString("HHmmssyyyyMMdd")); // ora in posizione definitiva File.WriteAllText(fileTo, rawData); } } } return answ; } /// /// Effettua richiesta info x macchina (validare startup process) /// protected void requestInfo() { var nextLevel = Eurom63.ComLevel.HasInfo; var connectSession = confE63.ActiveSessions[1]; processSession(nextLevel, ref connectSession); } protected void requestProdData() { var nextLevel = Eurom63.ComLevel.ProdRequested; var connectSession = confE63.ActiveSessions[5]; processSession(nextLevel, ref connectSession); } protected void requestStatusData() { var nextLevel = Eurom63.ComLevel.StatusRequested; var connectSession = confE63.ActiveSessions[4]; processSession(nextLevel, ref connectSession); } protected void setMachineTime() { var nextLevel = Eurom63.ComLevel.TimeSet; var connectSession = confE63.ActiveSessions[2]; processSession(nextLevel, ref connectSession); } #endregion Protected Methods #region Private Methods /// /// Se la sessione fosse scaduta o non attiva --> torna al livello indicato /// /// private void checkSessionActive(Eurom63.Session currSession, Eurom63.ComLevel nextLevel) { // SOLO SE ha senso che controllo (sono in sampling...) if (actLevel > Eurom63.ComLevel.HasInfo) { DateTime adesso = DateTime.Now; // devono essere ATTIVE le sessioni di campionamento... e NON scadute if (!currSession.Active || currSession.SessionValidUntil < adesso) { // controllo ultimo downgrade status if (lastStatusDecr.AddSeconds(3) < adesso) { // elimino TUTTE le risposte... cleanupResp(currSession.SessionName); // registro downgrade status... lastStatusDecr = adesso; // imposto livellotornando indietro di 1 alla volta... senza andare in negativoS actLevel = actLevel - 1; actLevel = actLevel > 0 ? actLevel : 0; lgInfo($"Sessione inattiva, {actLevel + 1} --> {actLevel}"); } } } } /// /// Effettua copia file richeiste + update timing /// /// /// private void copyRequestFiles(Eurom63.Session connectSession, DateTime adesso) { // processo richiesta processSessionFile(connectSession); if (adesso > connectSession.SessionValidUntil) { connectSession.Active = !connectSession.Cycle; } connectSession.Passed = false; connectSession.SessionStarted = adesso; connectSession.SessionValidUntil = adesso.AddMinutes(connectSession.ValidityMinutes); connectSession.RetryVeto = adesso.AddSeconds(connectSession.RetrySec); } #endregion Private Methods } }