using MTC; using NLog; using OPC_UA_REDIS; using System; using System.Collections.Generic; using System.Net; namespace SCMA.AdapterCom { /// /// Gateway di comunicazione secondo diversi standard, partendo da oggetti BASE MTC /// public class Gateway { #region oggetti base /// /// wrapper di log /// public static Logger lg; /// /// Protocollo attivo per la comunicazione dell'adapter /// public gwProtocol protocollo; /// /// STOBE allarmi: /// 1024 bit di strobe degli allarmi attivi (32 word da 4byte/32 bit di flags...) /// public byte[] AlarmFlags; /// /// Oggetto elenco allarmi /// public allarme[] elencoAllarmi; /// /// DataModel di riferimento /// public string DataModel; /// /// Dizionario conversione nome variabili con replace "like" (per gestione REDIS "_" --> ":") /// public Dictionary nameRepRoles = new Dictionary(); /// /// Dizionario conversione nome variabiliin memoria REDIS /// public Dictionary memCopyList = new Dictionary(); /// /// Porta comunicazione standard oggetto /// public int port; /// /// Stringa di configurazione globale /// public string connConfig; /// /// Flag per indicare se l'obj stia ancora girando /// private bool myRunning = false; /// /// Indicatore public di oggetto running running. /// public bool Running { get { return myRunning; } } /// /// la parte di "mark & sweep" (segnala ed invia) è iniziata e stiamo tracciando le conditions. /// bool myBegun = false; /// /// Elenco di TUTTI i NODI ITEMS gestiti dal gateway (item/variabile)... /// public Dictionary itemNodes = new Dictionary(); /// /// Elenco di TUTTI i NODI CONDITIONS gestiti dal gateway (allarme/condizione)... /// public Dictionary conditionNodes = new Dictionary(); /// /// Elenco ultimi allarmi riportati... /// protected Dictionary lastAlarmList; /// /// Bool di allarmi presenti /// public bool alarmPresent = false; #endregion #region gestione globale oggetto /// /// init classe come output su LOGFILE /// public Gateway() { lg = LogManager.GetCurrentClassLogger(); port = 0; connConfig = ""; protocollo = gwProtocol.LOGFILE; lastAlarmList = new Dictionary(); } /// /// Inizia la raccolta dati per confronto modifica da precedente... /// public virtual void beginDataCollect() { myBegun = true; foreach (object di in itemNodes) { // inizializza ogni oggetto (in particolare di tipo alarm/conditions x check variazione) //di.Begin(); } } /// /// Fa la verifica di cosa sia cambiato ed invia /// public virtual void sendChanged() { if (myBegun) { foreach (object di in itemNodes) { //di.Prepare(); } } #if false // Separate out the data items into those that are on one line and those // need separate lines. List together = new List(); List separate = new List(); foreach (object di in trackItems) { List list = di.ItemList(); if (di.NewLine) separate.AddRange(list); else together.AddRange(list); } // Compone all the same line data items onto one line. string line; if (timestamp == null) { DateTime now = DateTime.UtcNow; timestamp = now.ToString("yyyy-MM-dd\\THH:mm:ss.fffffffK"); } if (together.Count > 0) { line = timestamp; foreach (SimpleDataItem di in together) line += "|" + di.ToString(); line += "\n"; SendToAll(line); } // Now write out all the separate lines if (separate.Count > 0) { foreach (SimpleDataItem di in separate) { line = timestamp; line += "|" + di.ToString() + "\n"; SendToAll(line); } } // Flush the output FlushAll(); #endif // Cleanup foreach (object di in itemNodes) { // pulizia oggetti //di.Cleanup(); } myBegun = false; } /// /// Imposta TUTTI i data items null/unavailable /// public virtual void setAllUnavailable() { foreach (object di in itemNodes) { //// imposta a unavailable //di.Unavailable(); } } /// /// AVVIA processo lettura CNC e invio dati a client /// public virtual void start() { #if false if (!mRunning) { mListener = new TcpListener(IPAddress.Any, mPort); mListener.Start(); mListenThread = new Thread(new ThreadStart(ListenForClients)); mListenThread.Start(); } #endif } /// /// FERMA processo lettura CNC e invio dati a client /// public virtual void stop() { #if false if (mRunning) { mRunning = false; mListener.Stop(); foreach (Object obj in mClients) { Stream client = (Stream)obj; client.Close(); } mClients.Clear(); // Wait 5 seconds for the thread to exit. mListenThread.Join(2000); // Wait for all client threads to exit. mActiveClients.Wait(2000); } #endif } #endregion #region gestione controllo stato catena applicativi e messaggi da piattaforma /// /// Effettua chiamata URL e restituisce risultato /// /// /// public string callUrl(string URL) { string answ = ""; try { var client = new WebClient(); client.Headers.Add("user-agent", utils.CRS("appName")); answ = client.DownloadString(URL); } catch (Exception exc) { lg.Error(exc, "Errore in chiamata URL {0}{1}{2}", URL, Environment.NewLine, exc); } // restituisco valore! return answ; } /// /// Verifica lo stato di un servizio indicato per via numerica (differente x MTC / OPC-UA-REDIS) /// /// /// public virtual bool checkStatus(int numServ) { bool answ = false; return answ; } #endregion #region gestione nodi (dataItems / conditions) /// /// Aggiunge un generico item all'elenco di quelli tracciati INDICANDO IL TYPE /// /// /// public virtual void addItemNodeByType(string key, itemType tipo) { if (!itemNodes.ContainsKey(key)) { itemNode currItem = new itemNode(tipo, availStatus.NULL.ToString().ToLower()); itemNodes.Add(key, currItem); } } /// /// Aggiunge un generico item all'elenco di quelli tracciati INDICANDO IL TYPE ed il valore iniziale /// /// /// public virtual void addItemNodeAndSet(string key, itemType tipo, object value) { addItemNodeByType(key, tipo); // SOLO se richiesto FULL RESET... if (baseUtils.CRB("fullReset")) { updateItemNodeValue(key, value); } } /// /// Aggiunge un generico item all'elenco di quelli tracciati (NON STRONGLY TYPED!!! occhio!!!) /// /// /// public virtual void addItemNode(string key, object value) { // se non c'è già elemento lo aggiungo... if (!itemNodes.ContainsKey(key)) { // default: event... itemNode currItem = new itemNode(itemType.Event, value); itemNodes.Add(key, currItem); } } public virtual void addItemNode(object value) { if (!itemNodes.ContainsKey(value.ToString())) { // default: event... itemNode currItem = new itemNode(itemType.Event, value); itemNodes.Add(value.ToString(), currItem); } } /// /// Rimuove TUTTI i data items tracciati /// public virtual void removeAllItemNodes() { itemNodes.Clear(); } /// /// Rimuove un item dall'elenco di quelli tracciati /// /// public virtual void removeItemNode(string key) { itemNodes.Remove(key); } /// /// RESTITUISCE un item da KEY /// /// public virtual object getItemNode(string key) { return itemNodes[key].cObject; } /// /// AGGIORNA un generico item all'elenco di quelli tracciati (NON STRONGLY TYPED!!! occhio!!!) /// /// /// public virtual void updateItemNodeValue(string key, object value) { itemNodes[key].cObject = value; } /// /// AGGIORNA un generico item all'elenco di quelli tracciati (NON STRONGLY TYPED!!! occhio!!!) /// /// /// /// public virtual void updateItemNodeCodeValue(string key, string code, object value) { itemNodes[key].cObject = value; } /// /// Aggiunge un generico item all'elenco di quelli tracciati (NON STRONGLY TYPED!!! occhio!!!) /// /// /// public virtual void addConditionNode(string key, object value) { // se non c'è già elemento lo aggiungo... if (!conditionNodes.ContainsKey(key)) { // default: event... itemNode currItem = new itemNode(itemType.Condition, value); conditionNodes.Add(key, currItem); } } /// /// Rimuove TUTTI i CONDITION NODES tracciati /// public virtual void removeAllConditionNodes() { conditionNodes.Clear(); } /// /// Rimuove un CONDITION NODE dall'elenco di quelli tracciati /// /// chaive public virtual void removeConditionNode(string key) { conditionNodes.Remove(key); } #endregion #region gestione allarmi /// /// processo il vettore LOCALE degli allarmi /// public virtual void processAlarm() { // continuo controllo if (AlarmFlags != null && AlarmFlags.Length > 0) { // variabili helper StFlag32 AlarmBlock = 0; allarme currAllarm; Dictionary grpAlarmList = new Dictionary(); string alarmChannel = ""; // controllo TUTTI i bit della variabile COMPLETA degli status allarmi: se ce ne sono di alzati DEVO processare... for (int i = 0; i < AlarmFlags.Length / 4; i++) { // leggo 32bit alla volta... AlarmBlock = (StFlag32)BitConverter.ToUInt32(AlarmFlags, i * 4); if (AlarmBlock != StFlag32.NONE) { alarmPresent = true; for (int j = 0; j < 32; j++) { // converto! e aggiungo allarmi sollevati al corretto controller allarmi... if (AlarmBlock.HasFlag((StFlag32)Math.Pow(2, j))) { // recupero allarme da oggetto in memoria... currAllarm = elencoAllarmi[i * 32 + j]; addAlarm(currAllarm); // ora in base al TIPO di gateway determino il canale allarme... alarmChannel = currAllarm.gruppo; // se sono in SOURS if (protocollo == gwProtocol.SOURS) { // sostituisco gruppo con nome esatto... switch (alarmChannel.ToUpper()) { case "CNC": alarmChannel = nCncConditions; break; case "HMI": alarmChannel = nHmiConditions; break; case "PLC": alarmChannel = nPlcConditions; break; } // sostituisco livello allarme // aggiorno stringa... if (!grpAlarmList.ContainsKey(alarmChannel)) { grpAlarmList.Add(alarmChannel, ""); } grpAlarmList[alarmChannel] += string.Format("{0}|{1},", currAllarm.codNum, AdapterRed.condLevel(currAllarm.livello)); } } } } } // se in modalità SOUR riporto variabile alarmState if (protocollo == gwProtocol.SOURS) { foreach (var item in grpAlarmList) { // tolgo ultima stringa string elenco = item.Value.Substring(0, item.Value.Length - 1); // salvo... updateItemNodeValue(item.Key, elenco); } // se ho ALMENO un allarme attivo, altrimenti procedo che resetta da solo if (grpAlarmList.Count > 0) { // salvo i LAST alarm... lastAlarmList = grpAlarmList; } } } if (!alarmPresent) { // devo resettare gli allarmi notificati... foreach (var item in lastAlarmList) { // tolgo ultima stringa string elenco = "0|000"; // salvo... updateItemNodeValue(item.Key, elenco); } // resetto lastAlarmList = new Dictionary(); } // se in modalità SOUR riporto variabile alarmState if (protocollo == gwProtocol.SOURS && alarmPresent) { updateItemNodeValue("Alarm", AlarmFlags != null); } } /// /// Nome del channel allarmi CNC /// public string nCncConditions { get { string answ = "CNC"; if (protocollo == gwProtocol.SOURS) { answ = "Cnc:Condition"; } return answ; } } /// /// Nome del channel allarmi HMI /// public string nHmiConditions { get { string answ = "HMI"; if (protocollo == gwProtocol.SOURS) { answ = "Hmi:Condition"; } return answ; } } /// /// Nome del channel allarmi PLC /// public string nPlcConditions { get { string answ = "PLC"; if (protocollo == gwProtocol.SOURS) { answ = "Plc:Condition"; } return answ; } } /// /// SETUP oggetti gestione allarmi /// public virtual void addAlarmNodes() { // minimo sempre PLC/CNC addConditionNode(nCncConditions, "CNC"); addConditionNode(nPlcConditions, "PLC"); } /// /// INIT allarmi a normal /// public virtual void initAlarms() { } /// /// INIT di un SINGOLO NODO di allarme a normal /// /// public virtual void initAlarm(itemNode alarmNode) { } /// /// Aggiunta SINGOLO allarme su GENERICO (global) nodo /// /// Allarme da riportare al nodo public virtual void addAlarm(allarme currAllarm) { #if false // in base al tipo di allarme decodifico condizione... Condition.Level livello = Condition.Level.NORMAL; switch (currAllarm.livello) { case "WARNING": livello = Condition.Level.WARNING; break; case "FAULT": default: livello = Condition.Level.FAULT; break; } // in base al gruppo decido dove assegnare come CONDITION... switch (currAllarm.gruppo) { case "PLC": mAlarmPLC.Add(livello, currAllarm.descrizione, currAllarm.codNum, "", ""); break; case "CNC": default: mAlarmCNC.Add(livello, currAllarm.descrizione, currAllarm.codNum, "", ""); break; } #endif } /// /// Aggiunta SINGOLO allarme su SPECIFICO nodo /// /// Generico nodo di tipo condition /// Allarme da riportare al nodo public virtual void addAlarm(itemNode alarmNode, allarme currAllarm) { #if false // in base al tipo di allarme decodifico condizione... Condition.Level livello = Condition.Level.NORMAL; switch (currAllarm.livello) { case "WARNING": livello = Condition.Level.WARNING; break; case "FAULT": default: livello = Condition.Level.FAULT; break; } // in base al gruppo decido dove assegnare come CONDITION... switch (currAllarm.gruppo) { case "PLC": mAlarmPLC.Add(livello, currAllarm.descrizione, currAllarm.codNum, "", ""); break; case "CNC": default: mAlarmCNC.Add(livello, currAllarm.descrizione, currAllarm.codNum, "", ""); break; } #endif } /// /// RESTITUISCE un nodo CONDITION da KEY /// /// public virtual object getAlarmNode(string key) { return conditionNodes[key]; } #endregion } /// /// Tipologia protocolli di comunicazione ammessi /// public enum gwProtocol { /// /// NESSUN protocollo reale --> FILE DUMP sul log... /// LOGFILE, /// /// Protocollo di comunicazione MTConnect /// MTC, /// /// Protocollo di comunicazione SCM.OPC.UA.REDIS.SERVER /// SOURS } }