using IOB_UT; using MapoSDK; using MTConnect.Clients; using System; using System.Collections.Generic; using System.Net.NetworkInformation; using MTConnectDevices = MTConnect.MTConnectDevices; using MTConnectStreams = MTConnect.MTConnectStreams; namespace IOB_WIN { public class IobMTC : IobGeneric { /// /// LookUpTable di decodifica da CNC a segnali tipo bitmap MAPO /// Dictionary signLUT = new Dictionary(); /// /// Struttura dove vengono memorizzati i dataitem ed i rispettivi valori x processing /// Dictionary dataItemMem = new Dictionary(); /// /// Oggetto MAIN x connessione MTC /// protected MTConnectClient MTC_ref; /// /// Estende l'init della classe base, impiegando il pacchetto Nuget TrackHound /// https://github.com/TrakHound/MTConnect.NET /// /// /// public IobMTC(AdapterForm caller, IobConfiguration IOBConf) : base(caller, IOBConf) { // gestione invio ritardato contapezzi pzCountDelay = utils.CRI("pzCountDelay"); lastPzCountSend = DateTime.Now; lastWarnODL = DateTime.Now; } /// /// Processo i task richiesti e li elimino dalla coda 1:1 /// /// public override Dictionary executeTasks(Dictionary task2exe) { // uso metodo base x ora return base.executeTasks(task2exe); } /// /// Effettua reset del contapezzi, in questo caso il conteggio dei KG /// /// public override bool resetContapezziCNC() { bool answ = false; #if false // ...SE abilitato da conf IOB if (cIobConf.optPar.Count > 0 && getOptPar("ENABLE_PZ_RESET") == "TRUE") { // scrivo valore 0 x il contapezzi try { pzCounter = 0; } catch (Exception exc) { lgError(exc, "Errore in RESET contapezzi MTC"); connectionOk = false; } answ = true; } else { lgError("Impossibile effettuare RESET contapezzi MTC, mancanza parametro OPT:ENABLE_PZ_RESET"); } #endif return answ; } /// /// Effettua IMPOSTAZIONE FORZATA del contapezzi, in questo caso il conteggio dei KG /// /// public override bool setContapezziCNC(int newPzCount) { bool answ = false; #if false // ...SE abilitato da conf IOB if (cIobConf.optPar.Count > 0 && getOptPar("ENABLE_PZ_RESET") == "TRUE") { // scrivo valore 0 x il contapezzi try { pzCounter = newPzCount; } catch (Exception exc) { lgError(exc, "Errore in SET contapezzi MTC"); connectionOk = false; } answ = true; } else { lgError("Impossibile effettuare SET contapezzi MTC, mancanza parametro OPT:ENABLE_PZ_RESET"); } #endif return answ; } /// /// Effettua log di un devices (ed eventualmente dei sub-devices... /// /// protected void logDevicesList(List elencoDevices) { if (elencoDevices != null) { // loggo devices principali... foreach (var item in elencoDevices) { lgInfo($"Device data | ID: {item.Id} | Name: {item.Name} | UUID: {item.Uuid} | # items: {item.DataItems.Count}"); // se ho subItems descrivo pure loro... if (item.DataItems.Count > 0) { logDataItemList(item.DataItems); } if (item.Components.Components.Count > 0) { logComponentsList(item.Components.Components); } } } } /// /// Effettua log di un elenco componenti /// /// protected void logComponentsList(List elencoComponenti) { if (elencoComponenti != null) { foreach (var item in elencoComponenti) { lgInfo($"Component data | ID: {item.Id} | Name: {item.Name} | Type: {item.Type} | # items: {item.DataItems.Count}"); // se ho sottocomponenti richiamo... if (item.SubComponents != null) { if (item.SubComponents.Components.Count > 0) { logComponentsList(item.SubComponents.Components); } } if (item.DataItems.Count > 0) { logDataItemList(item.DataItems); } } } } /// /// Log elenco DataItems /// /// protected void logDataItemList(List elencoItems) { if (elencoItems != null) { // loggo devices principali... foreach (var item in elencoItems) { lgInfo($"Device data | ID: {item.Id} | Name: {item.Name} | Category: {item.Category} | # Type: {item.Type}"); } } } /// /// Vera connessione ad MTC /// /// private short doConnect() { short esitoLink = 0; // avvio un oggetto di comunicazione MTC #if false MTC_ref = new MTConnectClient($"http://{cIobConf.cncIpAddr}:{port}"); #endif short port = 5000; short.TryParse(cIobConf.cncPort, out port); // test probe! try { var probe = new Probe($"http://{cIobConf.cncIpAddr}:{port}").Execute(); // se valido loggo! if (probe != null) { lgInfo($"Effettuata correttamente PROBE per device MTC all'URL {probe.Url} | vers: {probe.Version} | send: {probe.Header.Sender}"); lgInfo($"---------------- Elenco Devices ----------------"); // loggo devices principali... logDevicesList(probe.Devices); } } catch { } // ora avvio try { lgInfo($"Chiamata apertura MTC Client: {cIobConf.cncIpAddr}:{port}"); MTC_ref = new MTConnectClient($"http://{cIobConf.cncIpAddr}:{port}"); // Subscribe to the Event handlers to receive the MTConnect documents MTC_ref.ProbeReceived += DevicesSuccessful; MTC_ref.CurrentReceived += StreamsSuccessful; MTC_ref.SampleReceived += StreamsSuccessful; MTC_ref.Start(); esitoLink = 1; } catch { } return esitoLink; } protected void DevicesSuccessful(MTConnectDevices.Document document) { MtcDataItemExt currDataItem = null; int dSamplePeriod = 0; int threshDBand = 0; string uuid = ""; if (document != null) { foreach (var device in document.Devices) { List dataItems = device.GetDataItems(); if (verboseLog) { foreach (var dataItem in dataItems) { string sVal = $"Category: {dataItem.Category} | Type: {dataItem.Type} | Id: {dataItem.Id} | Name: {dataItem.Name}"; lgInfo(sVal); try { // uuid e parametri secondo categoria... switch (dataItem.Category) { case MTConnect.DataItemCategory.CONDITION: uuid = $"C_{dataItem.Id}"; threshDBand = 0; dSamplePeriod = 0; break; case MTConnect.DataItemCategory.EVENT: uuid = $"E_{dataItem.Id}"; threshDBand = 0; dSamplePeriod = 0; break; case MTConnect.DataItemCategory.SAMPLE: uuid = $"S_{dataItem.Id}"; threshDBand = 1; dSamplePeriod = 60; break; default: break; } // salvo oggetto currDataItem = new MtcDataItemExt() { Id = dataItem.Id, Category = dataItem.Category, Constraints = dataItem.Constraints, CoordinateSystem = dataItem.CoordinateSystem, Name = dataItem.Name, NativeScale = dataItem.NativeScale, NativeUnits = dataItem.NativeUnits, SampleRate = dataItem.SampleRate, Representation = dataItem.Representation, SignificantDigits = dataItem.SignificantDigits, Source = dataItem.Source, Statistic = dataItem.Statistic, SubType = dataItem.SubType, Type = dataItem.Type, TypePath = dataItem.TypePath, Units = dataItem.Units, XPath = dataItem.XPath, uid = uuid, thresholdDeadBand = threshDBand, samplePeriod = dSamplePeriod }; // salvo dataItemMem.Add(dataItem.Id, currDataItem); } catch { } } } } } } /// /// Effettua traduzione ITEM da LUT recuperata da json configurazione (SPECIFICO MTC) /// /// /// /// protected string itemTranslation(string tipo, string id) { // !!!FARE!!! tradurre CORRETTAMENTE da lookuptable... x ora fake acccoda.. string answ = $"{tipo}_{id}"; return answ; } /// /// Verifica / Salva valore generico (NON SAMPLE) /// /// /// protected bool checkSaveItem(MTConnectStreams.DataItem newValue) { bool answ = false; if (newValue != null) { // verifico in memoria se ho l'oggetto condition ed il suo valore.. if (dataItemMem.ContainsKey(newValue.DataItemId)) { // salvo sempre! dataItemMem[newValue.DataItemId].value = newValue.CDATA; dataItemMem[newValue.DataItemId].valueTimestamp = newValue.Timestamp; answ = true; } } return answ; } /// /// Verifica / Salva valore SAMPLE e restitusice SE sia variato (e quindi da inviare...) /// /// /// protected bool checkSaveSample(MTConnectStreams.Sample newValue) { bool answ = false; double oldVal = 0; double newVal = 0; if (newValue != null) { // verifico in memoria se ho l'oggetto condition ed il suo valore.. if (dataItemMem.ContainsKey(newValue.DataItemId)) { MtcDataItemExt currDataItemMem = dataItemMem[newValue.DataItemId]; // controllo SE SIA scaduto il tempo massimo... if (Math.Abs(dataItemMem[newValue.DataItemId].valueTimestamp.Subtract(newValue.Timestamp).TotalSeconds) > currDataItemMem.samplePeriod) //if (dataItemMem[newValue.DataItemId].valueTimestamp.AddSeconds(currDataItemMem.samplePeriod) < newValue.Timestamp) { answ = true; } else { // ALTRIMENTI controllo SE diverso if (dataItemMem[newValue.DataItemId].value != newValue.CDATA) { // controllo SE ho DeadBand... if (dataItemMem[newValue.DataItemId].thresholdDeadBand > 0) { // recupero i valori e testo DeadBand... double.TryParse(dataItemMem[newValue.DataItemId].value, out oldVal); double.TryParse(newValue.CDATA, out newVal); // test deadband! if (Math.Abs(newVal - oldVal) > dataItemMem[newValue.DataItemId].thresholdDeadBand) { // indico da salvare.. answ = true; } } } } if (answ) { // salvo! dataItemMem[newValue.DataItemId].value = newValue.CDATA; dataItemMem[newValue.DataItemId].valueTimestamp = newValue.Timestamp; } } } return answ; } protected void StreamsSuccessful(MTConnectStreams.Document document) { if (document != null) { foreach (var deviceStream in document.DeviceStreams) { string sVal = ""; string descr = ""; #if false // check su dataItems (conditions + events + samples) try { foreach (var dataItem in deviceStream.DataItems) { lgInfo(dataItem.DataItemId + " = " + dataItem.CDATA); } } catch { } #endif // check su Conditions try { // check su dataItems (conditions + events + samples) foreach (var dataItem in deviceStream.Conditions) { descr = itemTranslation("C", dataItem.DataItemId); sVal = $"CONDITION: {dataItem.Timestamp.ToString()} | descr: {descr} | Name: {dataItem.Name} | Val: {dataItem.CDATA}"; lgInfo(sVal); // verifico se salvare bool changed = checkSaveItem(dataItem); if (changed) { accodaFLog(sVal, qEncodeFLog(descr, dataItem.CDATA)); } // devo accodare ed inviare nella coda ALARMS (che POI salva in document MongoDB anche ultimi 3 minuti di FluxLog...) !!!FARE!!! } } catch { } // check su events try { // check su dataItems (conditions + events + samples) foreach (var dataItem in deviceStream.Events) { descr = itemTranslation("E", dataItem.DataItemId); sVal = $"EVENT: {dataItem.Timestamp.ToString()} | descr: {descr} | Name: {dataItem.Name} | Val: {dataItem.CDATA}"; lgInfo(sVal); // verifico se salvare bool changed = checkSaveItem(dataItem); if (changed) { accodaFLog(sVal, qEncodeFLog(descr, dataItem.CDATA)); } } } catch { } // cehck su samples try { // check su dataItems (conditions + events + samples) foreach (var dataItem in deviceStream.Samples) { descr = itemTranslation("S", dataItem.DataItemId); sVal = $"SAMPLE: {dataItem.Timestamp.ToString()} | descr: {descr} | Name: {dataItem.Name} | Val: {dataItem.CDATA}"; lgInfo(sVal); // verifico se salvare bool changed = checkSaveSample(dataItem); if (changed) { accodaFLog(sVal, qEncodeFLog(descr, dataItem.CDATA)); } } } catch { } } } } /// /// Override disconnessione /// public override void tryDisconnect() { if (connectionOk) { string szStatusConnection = ""; try { MTC_ref.Stop(); connectionOk = false; lgInfo(szStatusConnection); lgInfo("Effettuata disconnessione adapter MTC!"); } catch (Exception exc) { lgFatal(exc, "Errore nella disconnessione dall'adapter MTC"); } } else { lgError("IMPOSSIBILE effettuare disconnessione MTC: Connessione non disponibile..."); } } /// /// 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("MTC: ConnKO - tryConnect"); } // in primis salvo data ping... lastPING = DateTime.Now; // se passa il ping faccio il resto... if (testPing == IPStatus.Success) { string szStatusConnection = ""; try { // ora provo connessione... parentForm.commPlcActive = true; short esitoLink = doConnect(); lgInfo($"szStatusConnection MTC, esitoLink: {esitoLink}"); parentForm.commPlcActive = false; connectionOk = true; // refresh stato allarmi!!! if (connectionOk) { if (adpRunning) { // carico status allarmi (completo) lgInfo("Inizio refresh completo stato allarmi..."); forceAlarmCheck(); lgInfo("Completato refresh completo stato allarmi!"); } else { lgInfo("Connessione OK"); } } else { lgError("Impossibile procedere, connessione mancante..."); } } catch (Exception exc) { lgFatal($"Errore nella connessione all'adapter MTC: {szStatusConnection}{Environment.NewLine}{exc}"); connectionOk = false; lgInfo($"Eccezione in TryConnect, Adapter MTC 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: MTC controllo PING fallito per IP {cIobConf.cncIpAddr}"); } } } } else { needRefresh = true; } // se non è ancora connesso faccio procesisng memoria caso disconnesso... if (!connectionOk) { // processo semafori ed invio... processMemoryDiscon(); } } #region Metodi specifici (da verificare/completare in implementazione) /// /// controllo allarmi /// public override void forceAlarmCheck() { // controllo tutta la memoria allarmi SE richiesto } /// /// Effettua vero processing contapezzi /// public override void processContapezzi() { if (utils.CRB("enableContapezzi")) { #if false try { // hard coded... !!!FARE!!! rivedere megio conf lastCountCNC = pzCounter; // verifico quale modalità sia richiesta: STD (6711) oppure BIT (Custom, con indicazione area) if (cIobConf.optPar.Count > 0 && getOptPar("PZCOUNT_MODE") != "") { } } catch (Exception exc) { lgError(exc, "Errore in contapezzi MTC"); } #endif } } /// /// Effettua lettura semafori principale /// Parametri da aggiornare x display in form /// public override void readSemafori(ref newDisplayData currDispData) { base.readSemafori(ref currDispData); try { if (verboseLog) { lgInfo("inizio read semafori"); } currDispData.semIn = Semaforo.SV; // decodifica e gestione decodeToBaseBitmap(); reportRawInput(ref currDispData); } catch { currDispData.semIn = Semaforo.SR; } } /// /// Verifico se abbia ALMENO un errore... /// protected bool hasError { get { bool answ = false; // controllo TUTTE le conditions... foreach (var item in dataItemMem) { // se NON HO GIA' allarmi attivi... if (!answ) { // se è una condition... if (item.Value.Category == MTConnect.DataItemCategory.CONDITION) { // se ha valore !="" --> allarmi attivi if (!string.IsNullOrEmpty(item.Value.value)) { answ = true; } } } } return answ; } } /// /// Effettua decodifica aree memoria alla bitmap usata x MAPO /// private void decodeToBaseBitmap() { // init a zero... B_input = 0; /* ----------------------------------------------------- * bitmap MAPO * B0: POWER_ON * B1: RUN * B2: pzCount * B3: allarme * B4: manuale * * * contapezzi: Path_01_PZ_TOT * * * E_Path_01_RUN_MODE * public enum MtcRunMode * { * UNDEFINED = 0, * AUTOMATIC, * EDIT, * MANUAL_DATA_INPUT, * MANUAL, * SEMI_AUTO * } * * * E_Path_01_EXE_MODE * /// * /// ENUM degli stati EXE del path MTC * /// * public enum MtcExeMode * { * UNDEFINED = 0, * ACTIVE, * READY, * STOPPED, * FEED_HOLD, * OPTIONAL_STOP, * PROGRAM_STOPPED, * PROGRAM_COMPLETED * } * * * LAVORA SOLO SE * Path_01_EXE_MODE = ACTIVE * Path_01_RUN_MODE = AUTOMATIC * ST_Abilitaz_Abrasivo = ACTIVE * ST_Getto_On = ACTIVE * ST_UnOp_01_Status = ACTIVE * ----------------------------------------------------- */ // bit 0 (poweron) imposto a 1 SE pingo... B_input = testPing == IPStatus.Success ? 1 : 0; // variabili appoggio... string currExe = ""; string currRun = ""; string currUnOpStatus = ""; try { var exeMode = dataItemMem["Path_01_EXE_MODE"]; currExe = exeMode.value; } catch { } try { var runMode = dataItemMem["Path_01_RUN_MODE"]; currRun = runMode.value; } catch { } try { var UnOp_Status = dataItemMem["ST_UnOp_01_Status"]; currUnOpStatus = UnOp_Status.value; } catch { } // controllo lavora... if (currRun == "AUTOMATIC") { if (currExe == "ACTIVE") { if (currUnOpStatus == "ACTIVE") { // RUN = LAVORA! B_input += (1 << 1); } } } else { // se ho run mode != auto --> manual B_input += (1 << 4); } // se ho almeno 1 allarme --> ALARM! if (hasError) { B_input += (1 << 3); } #if false // process ODL e contapezzi string currODL = ""; try { currODL = utils.callUrl(urlGetCurrODL); // solo SE HO un ODL... if (currODL == "" || currODL == "0") { if (periodicLog) { lgInfo(string.Format("MTC | Lettura ODL andata a vuoto: currODL: {0}", currODL)); } } else { // se variato o scaduto timeout log... if (periodicLog || (currIdxODL.ToString() != currODL)) { lgInfo(string.Format("MTC | Lettura ODL, currODL: {0} --> currIdxODL prec: {1}", currODL, currIdxODL)); } // provo a salvare nuovo ODL int.TryParse(currODL, out currIdxODL); } } catch (Exception exc) { if (DateTime.Now.Subtract(lastWarnODL).TotalSeconds > 15) { lgError(exc, "Errore in fase di chiamata URL x ODL corrente | URL chiamato: {0}", urlGetCurrODL); lastWarnODL = DateTime.Now; } } #endif // log opzionale! if (verboseLog) { lgInfo(string.Format("Trasformazione B_input: {0}", B_input)); } } /// /// Recupero dati dinamici... /// public override Dictionary getDynData() { // valore non presente in vers default... se gestito fare override Dictionary outVal = new Dictionary(); return outVal; } /// /// OVerride metodo x scrittura parametri su PLC /// /// protected override void plcWriteParams(List updatedPar) { int valInt = 0; if (updatedPar != null) { // controllo i parametri... ne gestisco 4... foreach (var item in updatedPar) { #if false switch (item.uid) { case "kgRich": int.TryParse(item.reqValue, out valInt); pesoRichiesto = valInt; break; case "kgLotto": int.TryParse(item.reqValue, out valInt); quantitaLotto = valInt; break; case "portata": int.TryParse(item.reqValue, out valInt); portata = valInt; break; case "setComm": commessa = item.reqValue; break; default: break; } #endif } } } #endregion } }