using RestSharp; using IOB_UT_NEXT; using MapoSDK; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Net.NetworkInformation; using System.IO; using System.Windows.Forms; using System.Threading; using S7.Net.Types; using System.Text.RegularExpressions; namespace IOB_WIN_NEXT.IobRest { /// /// Adapter base per sviluppo chiamate con servizi REST /// public class Base : Iob.Generic { #region Public Constructors /// /// Costruttore dell'IOB Rest generico /// /// AdapterForm chiamante /// Configurazione IOB per avvio public Base(AdapterForm caller, IobConfiguration IOBConf) : base(caller, IOBConf) { lgInfo($"Richiesto Adapter IobRest.Base con i parametri seguenti | ADDR: {IOBConf.cncIpAddr} | PORT: {IOBConf.cncPort}"); lastPING = DateTime.Now.AddHours(-1); // predispongo configurazione specifica Rest... if (!string.IsNullOrEmpty(getOptPar("REST_CONF"))) { setupRestConf(getOptPar("REST_CONF")); } } #endregion Public Constructors #region Public Methods /// /// Implementazione custom esecuzione task specifici /// /// /// public override Dictionary executeTasks(Dictionary task2exe) { /*--------------------------------------- * gestione execute task SPECIFICI x pesa: * - salva i parametri richiesta (RM, cod1..cod6) * - esegue metodo richiesta (IN/OUT) *---------------------------------------*/ // Verificare il protocollo: dovrebbe togliere SOLO i task eseguiti... Dictionary taskDone = new Dictionary(); #if false if (task2exe != null) { lgTrace($"executeTasks: richiesta esecuzione {task2exe.Count} task"); // controllo se memMap != null... if (memMap != null) { bool taskOk = false; string taskVal = ""; // cerco task specifici: qui sono NON standard... foreach (var item in task2exe) { lgInfo($"TASK | {item.Key} --> {item.Value}"); taskOk = false; taskVal = ""; // converto richiesta in enum... taskType tName = taskType.nihil; Enum.TryParse(item.Key, out tName); // controllo sulla KEY... switch (tName) { case taskType.setParameter: // richiedo da URL i parametri WRITE da popolare lgInfo("Chiamata setParameter --> 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 = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | SKIPPED | NO EXEC"; lgInfo($"Chiamata senza processing: taskOk: {taskOk} | taskVal: {taskVal}"); break; } // aggiungo task! taskDone.Add(item.Key, taskVal); } } else { lgError($"Attenzione! memMap è nullo, non posso eseguire task2exe!"); } } #endif return taskDone; } /// /// Recupero dati dinamici... /// public override Dictionary getDynData() { // valore non presente in vers default... se gestito fare override Dictionary outVal = new Dictionary(); #if false // controllo se ho indicato ancora che ci siano pesate già lette da inviare... if (num2send() > 0) { // preparo oggetti x confronto ... List listaArch = listPesateArchivio; // se non ho nulla in archivio prendo ultima pesata (essendo DESC è la prima cronologicamente) if (listaArch.Count == 0) { WeightRec lastRec = listPesateCurr.LastOrDefault(); // aggiungo... SavePesata(ref outVal, lastRec); } // ora il confronto è cronologico sulle pesate + recenti... else { // prendo prima della lista pesate archiviate = più recente come dt da cui partire... DateTime dtRif = listaArch.FirstOrDefault().DtEvent; var firstRec = listPesateCurr .Where(x => x.DtEvent > dtRif) .OrderBy(x => x.DtEvent) .FirstOrDefault(); if (firstRec != null) { SavePesata(ref outVal, firstRec); } } // processo comunque le aree memoria READ... if (memMap.mMapRead.Count > 0) { foreach (var item in memMap.mMapRead) { var currVal = getCurrProdData(item.Key, ""); if (!outVal.ContainsKey(item.Key)) { outVal.Add(item.Key, $"{currVal}"); } // se fosse logReq --> resetto... if (item.Key == "logReq" && !string.IsNullOrEmpty(currVal)) { upsertKey(item.Key, ""); } } } } // altrimenti rileggo e cerco se ci siano else { Stopwatch sw = new Stopwatch(); sw.Start(); try { // test lettura elenco pesate... se NON nullo --> OK! var weightArray = RestConn.reqWeightList("ALL", dataFrom, dataTo); // riordino DESC listPesateCurr = WeightRec.ConvertPesate(weightArray.OrderByDescending(x => x.dateIn).ToList()); } catch (Exception exc) { lgError($"Eccezione in RestConn.reqWeightList{Environment.NewLine}{exc}"); } sw.Stop(); lgInfo($"getDynData | SOAP: effettuata chiamata reqWeightList in {sw.Elapsed.TotalMilliseconds}ms | {dataFrom} --> {dataTo} | {listPesateCurr.Count} rec"); } #endif // indico esecuzione e proseguo lastReadPLC = DateTime.Now; return outVal; } /// /// Effettua lettura semafori principale /// Parametri da aggiornare x display in form /// public override void readSemafori(ref newDisplayData currDispData) { DateTime adesso = DateTime.Now; lastReadPLC = adesso; // verifico non sia in veto invio iniziale... if (queueInEnabCurr) { try { if (verboseLog) { lgInfo("inizio read semafori"); } currDispData.semIn = Semaforo.SV; // effettua refresh dati da leggere SPECIFICi x citizen... refreshData(); // decodifica e gestione decodeToBaseBitmap(ref currDispData); // display reportRawInput(ref currDispData); } catch (Exception exc) { currDispData.semIn = Semaforo.SR; lgError($"Eccezione in readSemafori:{Environment.NewLine}{exc}"); } } else { lgDebug($"[VETO readSemafori] | veto attivo alle {adesso:yyyy.MM.dd HH:mm:ss}"); checkVetoQueueIn(); } } /// /// Override connessione /// public override void tryConnect() { if (!connectionOk) { // controllo che il ping sia stato tentato almeno pingTestSec fa... if (DateTime.Now.Subtract(lastPING).TotalSeconds > utils.CRI("pingTestSec")) { if (verboseLog || periodicLog) { lgInfo("Rest: ConnKO - tryConnect"); } // in primis salvo data ping... lastPING = DateTime.Now; // se passa il ping faccio il resto... if (testPingMachine == IPStatus.Success) { string szStatusConnection = ""; try { // ora provo connessione... parentForm.commPlcActive = true; // chiamo metodo connect var checkResp = ExecuteCallGet(GetUrlResource("GetConnection")); // forse va eliminato... lgInfo($"GetConnection | {checkResp}"); if (checkResp != null && checkResp.ToLower().Contains("true")) { connectionOk = true; } else { lgError($"Errore check connessione | checkResp: {checkResp}"); } // refresh stato connessione!!! if (connectionOk) { queueInEnabCurr = true; if (adpRunning) { lgInfo("Connessione OK"); } } else { lgError("Impossibile procedere, connessione mancante..."); } } catch (Exception exc) { lgFatal($"Errore nella connessione all'Adapter IobRest.Base: {szStatusConnection}{Environment.NewLine}{exc}"); connectionOk = false; lgInfo($"Eccezione in TryConnect, Adapter IobRest.Base NON running, pausa di {utils.CRI("waitRecMSec")} msec prima di ulteriori tentativi di riconnessione"); } } else { // loggo no risposta ping ... connectionOk = false; if (verboseLog || periodicLog) { lgInfo($"Attenzione: Rest controllo PING fallito per IP {cIobConf.cncPingAddr}"); } } } } else { needRefresh = true; } } public override void tryDisconnect() { // registro solo che è disconnesso connectionOk = false; queueInEnabCurr = false; } #endregion Public Methods #region Protected Fields /// /// Api Url di base x chiamate REST /// protected string apiUrl = "http://localhost:8733"; /// /// Timeout chiamate REST /// protected int tOutSec = 60; #endregion Protected Fields #region Protected Properties /// /// Parametri specifici Client Rest /// protected RestParamConf restParams { get; set; } = new RestParamConf(); #endregion Protected Properties #region Protected Methods /// /// verifica stato ok ovvero connected oppure open /// /// /// protected bool checkStateOk(System.ServiceModel.CommunicationState currState) { bool answ = false; #if false answ == System.ServiceModel.CommunicationState.Opened || currState == System.ServiceModel.CommunicationState.Created; #endif return answ; } /// /// Esecuzione chiamata Rest tipo GET /// /// /// protected string ExecuteCallGet(string resource) { string answ = ""; if (!string.IsNullOrEmpty(resource)) { try { // client chiamate rest using (var client = new RestClient(restOptStd)) { var currReq = new RestRequest(resource, Method.Get); currReq.AddHeader("Content-type", "application/json"); // effettuo vera chiamata var currResp = client.Get(currReq); if (currResp.StatusCode == System.Net.HttpStatusCode.OK && currResp.Content != null) { answ = currResp.Content ?? ""; } } } catch(Exception exc) { lgError($"Eccezione in ExecuteCallGet{Environment.NewLine}{exc}"); } } return answ; } /// /// restituisce URL della risorsa eventualmente completato con token o altro /// /// /// protected string GetUrlResource(string resName) { string answ = ""; if (restParams != null && restParams.CallList != null && restParams.CallList.Count > 0) { if (restParams.CallList.ContainsKey(resName)) { answ = restParams.CallList[resName].Url; // eseguo eventuale sostituzione (se trovo "[[") if (answ.Contains("[[")) { string pattern = @"\[\[.*\]\]"; Regex regex = new Regex(pattern); Match match = regex.Match(resName); if (match.Success) { string key = match.Value.Replace("[[", "").Replace("]]", ""); // cerco nella LUT... if (RestDataLUT.ContainsKey(key)) { // sostituisco! answ = answ.Replace($"[[{key}]]", RestDataLUT[key]); } } } } } return answ; } /// /// Upsert in cache LUT del parametro /// /// /// protected void RestLutUpsert(string key, string value) { if (RestDataLUT.ContainsKey(key)) { RestDataLUT[key] = value; } else { RestDataLUT.Add(key, value); } } /// /// Recupero valore da cache LUT /// /// /// protected string RestLutGet(string key) { string value = ""; if (RestDataLUT.ContainsKey(key)) { value = RestDataLUT[key]; } return value; } /// /// Metodo da overridare x scrivere DAVVERO i parametri sul PLC /// /// protected override void plcWriteParams(ref List updatedPar) { lgTrace($"plcWriteParams: richiesta per {updatedPar.Count} params"); foreach (var item in updatedPar) { lgInfo($"ITEM | {item.uid} | {item.value}"); // salvo i valori di setup x prox pesata... upsertKey(item.uid, item.value); #if false bool fatto = false; bool isPesata = false; bool isIN = false; gestWeightOut answ = new gestWeightOut(); // se è richiesta pesata IN/OUT --> mando chiamata if (item.uid == "reqPesata") { isPesata = true; isIN = item.reqValue.ToUpper() == "IN"; //isIN = item.value.ToUpper() == "IN"; try { answ = reqWeight(isIN); } catch (Exception exc) { lgError($"Eccezione in plcWriteParams.reqWeight | isIn: {isIN}{Environment.NewLine}{exc}"); } } else if (item.uid == "RM" || item.uid.StartsWith("Cod")) { // comunque segno fatto x altri casi fatto = true; } // se è pesata... if (isPesata) { // se è OK if (answ.feedback == "C") { lgInfo($"reqWeight | Effettuato richiesta | {answ.feedback} | {answ.notes}"); // resetto pesata upsertKey(item.uid, ""); } else { lgError($"reqWeight | Errore in richiesta peso Rest | {answ.feedback} | {answ.notes}"); } item.value = ""; item.reqValue = ""; item.lastRead = DateTime.Now; item.UM = ""; // salvo esito richiesta comunque upsertKey("logReq", $"{answ.feedback} | {answ.notes}"); // faccio in modo di eseguire subito getDynData demFactDynData = 1; processDynData(); } else { // se fatto --> aggiorno! if (fatto) { //item.value = item.reqValue; item.reqValue = ""; item.lastRead = DateTime.Now; item.UM = ""; } } #endif } } protected void setupRestConf(string restConfFile) { // se ho file specifico if (!string.IsNullOrEmpty(getOptPar("REST_CONF"))) { string rawData = ""; // leggo file e decodifico... string confPath = $"{Application.StartupPath}/DATA/CONF/{restConfFile}"; lgInfo($"Apertura file {confPath}"); using (StreamReader sr = new StreamReader(confPath)) { rawData = sr.ReadToEnd().Replace("\n", "").Replace("\r", ""); } // continuo decodifica if (!string.IsNullOrEmpty(rawData)) { restParams = JsonConvert.DeserializeObject(rawData); if (restParams != null) { // inizializzo dal file di conf le variabili necessarie... tOutSec = restParams.timeOutSec; apiUrl = restParams.apiUrl ?? "http://localhost:8733"; } } } // sistemo conf standard x call REST restOptStd = new RestClientOptions { Timeout = TimeSpan.FromSeconds(tOutSec), BaseUrl = new Uri(apiUrl) }; } #endregion Protected Methods #region Private Fields /// /// LookUpTable informazioni raccolte /// protected Dictionary RestDataLUT = new Dictionary(); /// /// Conf client RestSharp standard: /// - timeout 1 min /// private RestClientOptions restOptStd = new RestClientOptions { Timeout = TimeSpan.FromSeconds(60) }; #endregion Private Fields #region Private Methods /// /// Effettua decodifica aree memoria alla bitmap usata x MAPO /// protected virtual void decodeToBaseBitmap(ref newDisplayData currDispData) { // init a zero... B_input = 0; if (queueInEnabCurr) { /* ----------------------------------------------------- * bitmap MAPO STD 60 * 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) ----------------------------------------------------- */ // per prima cosa controllo ping e se sia connesso... #if false if (connectionOk) { B_input = 1; currDispData.semIn = Semaforo.SV; // se ho pesate in memoria nel periodo richiesto --> RUN if (listPesateCurr != null && listPesateCurr.Count > 0) { B_input += (1 << 1); } // metto manuale else { B_input += (1 << 4); } // accodo NON emergenza B_input += (1 << 7); } else { B_input = 0; currDispData.semIn = Semaforo.SR; } #endif #if false // Controllo booleano PING e POWERON... string currPowerOn = getDataItemValue(mtcParams.condPowerOn.keyName); // se valido il check ping lo eseguo... altrimenti lo do x buono bool isPingOk = mtcParams.pingAsPowerOn && (testPingMachine == IPStatus.Success); // verifico da target value richiesto... bool checkPowerOn = (currPowerOn == mtcParams.condPowerOn.targetValue); // bit 0 (poweron) imposto a 1 SE pingo o PowerOn=="ON"... B_input = (isPingOk || checkPowerOn) ? 1 : 0; // variabili RUN... string currRun = getDataItemValue(mtcParams.keyRunMode); // controllo RUN MODE preliminare... CABLATO - è GENERALE x MTC if (currRun == "AUTOMATIC" || currRun == "SEMI_AUTO" || currRun == "SEMI_AUTOMATIC") { int numCond = mtcParams.condWork.Count; int numCondOk = 0; // cerco nell'elenco delle condizioni che indicano lavora se sono ok faccio +1 conteggio...... foreach (var item in mtcParams.condWork) { if (getDataItemValue(item.keyName) == item.targetValue) { numCondOk++; } } // se tutte condizioni rispettate --> lavora! if (numCond == numCondOk) { // RUN = LAVORA! B_input += (1 << 1); } } // se ho almeno 1 allarme E NON SONO IN AUTO --> ALARM! else if (hasError) { B_input += (1 << 3); } // 2024.01.90: gestione dati UNAVAILABLE che indicano poweroff... else if (hasUnavailableData && unavailPoweroff) { B_input = 0; } else { // se ho run mode != auto --> manual B_input += (1 << 4); } // emergenza armata da riportare con bit True/ 1 if (mtcParams.emergencyArmedTrue) { //se NON premuta lazo il bit if (!hasEStopTriggered) { B_input += (1 << 5); } } // emergenza armata da riportare come False/0 (!mtcParams.emergencyArmedTrue) else { // se premuta alzo il bit... if (hasEStopTriggered) { B_input += (1 << 5); } } DateTime adesso = DateTime.Now; int vFactor = 1; // controllo SE HO dati per fare verifiche... if (string.IsNullOrEmpty(currRun)) { // se ho parametro x gestione reset... if (enableMtcRestart) { // controllo se ho ricevuto il current da OLTRE 1 minuto... if (lastCurrent.AddMinutes(3) < adesso) { lastCurrent = adesso; // stop... lgInfo("Fermato MTC_ref per mancanza dati current"); MTC_ref.Stop(); Thread.Sleep(1000); // restart lgInfo("Riavviato MTC_ref per mancanza dati current"); MTC_ref.Start(); } } } else { vFactor = 6; } // solo se non ho veto check if (vetoCheckStatus < adesso) { lgInfo($"Stato variabili: currRun: {currRun}"); // imposto veto per vetoSeconds... vetoCheckStatus = adesso.AddSeconds(vetoSeconds * vFactor); } // log opzionale! if (verboseLog) { lgInfo($"Trasformazione B_input: {B_input} | currRun = {currRun}"); } #endif } else { lgDebug($"[VETO getDataItemValue] | veto attivo alle {DateTime.Now:yyyy.MM.dd HH:mm:ss}"); } } /// /// Esegue lettura dati + salvataggio in LUT /// protected virtual void refreshData() { } #endregion Private Methods } }