using IOB_UT_NEXT; using MapoSDK; using Newtonsoft.Json; using Opc.Ua; using Opc.Ua.Configuration; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.NetworkInformation; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace IOB_WIN_NEXT { public class IobOpcUa : IobGeneric { #region Public Constructors /// /// Estende l'init della classe base, impiegando il pacchetto Nuget OPC-UA foundation https://github.com/OPCFoundation/UA-.NETStandard /// /// /// public IobOpcUa(AdapterForm caller, IobConfiguration IOBConf) : base(caller, IOBConf) { // gestione invio ritardato contapezzi pzCountDelay = utils.CRI("pzCountDelay"); // gestione data filtering... if (!string.IsNullOrEmpty(getOptPar("ENABLE_DATA_FILTER"))) { bool.TryParse(getOptPar("ENABLE_DATA_FILTER"), out enableDataFilter); } // gestione restart OpcUa client... if (!string.IsNullOrEmpty(getOptPar("ENABLE_CLI_RESTART"))) { bool.TryParse(getOptPar("ENABLE_CLI_RESTART"), out enableCliRestart); } // init datetime counters DateTime adesso = DateTime.Now; lastPzCountSend = adesso; lastWarnODL = adesso; lastCurrent = adesso; // ora leggo il file di conf specifico.... string jsonFileName = getOptPar("OPC_PARAM_CONF"); if (!string.IsNullOrEmpty(jsonFileName)) { // leggo il file... loadOpcUaConf(jsonFileName); } } #endregion Public Constructors #region Public Methods /// /// 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); } /// /// Recupera uno specifico dataItem /// /// /// public string getDataItemValue(string diKey) { string answ = ""; if (string.IsNullOrEmpty(diKey)) { lgError($"Attenzione: richiesta chiave vuota in getDataItemValue{Environment.NewLine}StackTrace: {Environment.StackTrace}"); } else { DateTime adesso = DateTime.Now; if (dataItemMem.Count == 0) { numErroriCheck++; if (vetoCheckStatus < adesso) { lgError($"Errore in getDataItemValue per {diKey} | dataItemMem NON contiene valori"); // imposto veto per vetoSeconds... vetoCheckStatus = adesso.AddSeconds(vetoSeconds * 2); } } else { if (!dataItemMem.ContainsKey(diKey)) { if (vetoCheckStatus < adesso) { lgError($"Errore in getDataItemValue per {diKey} | dataItemMem non contiene la chiave richiesta ma altri {dataItemMem.Count} valori"); // imposto veto per vetoSeconds... vetoCheckStatus = adesso.AddSeconds(vetoSeconds * 2); } numErroriCheck++; } else { try { var currDataItem = dataItemMem[diKey]; answ = currDataItem.value; } catch (Exception exc) { lgError($"Errore in getDataItemValue per {diKey} | dataItemMem contiene {dataItemMem.Count} valori {Environment.NewLine}{exc}"); if (dataItemMem != null) { lgError($"dataItemMem contiene {dataItemMem.Count} valori"); int maxNum = 5; foreach (var item in dataItemMem) { maxNum--; if (maxNum < 0) { break; } lgInfo($"{item.Key} --> {item.Value.DisplayName} = {item.Value.value}"); } } } } } // se supero soglia errori lettura --> disconnetto e resetto if (numErroriCheck > maxErroriCheck) { lgInfo($"numErroriCheck: {numErroriCheck} --> richiesta disconnessione adapter con tryDisconnect"); numErroriCheck = 0; tryDisconnect(); } } return answ; } /// /// Recupero dati dinamici... /// public override Dictionary getDynData() { Dictionary outVal = new Dictionary(); return outVal; } /// /// Effettua vero processing contapezzi /// public override void processContapezzi() { if (utils.CRB("enableContapezzi")) { // da ridefinire la gestione base del contapezzi OPC-UA... } } /// /// 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; // verifico SE sia necessario forzare la lettura RAW if (doByteRead) { if (DateTime.Now.Subtract(lastCurrent).TotalSeconds > lastCurrentMaxElapsed) { // FIXME TODO !!! foreach (var item in dataItemMem) { // restituisce i 115 byte da deserializzare... qui HACK! var rawVal = UA_ref.ReadNodeRaw(item.Value.StartNodeId); if (rawVal != null) { byteRawData = getByteRaw((DataValue)rawVal); lastCurrent = DateTime.Now; currReadErrors = 0; } else { currReadErrors++; } } } } // decodifica e gestione decodeToBaseBitmap(); reportRawInput(ref currDispData); } catch (Exception exc) { currDispData.semIn = Semaforo.SR; lgError($"Eccezione in readSemafori:{Environment.NewLine}{exc}"); } // se > max errori --> disconnetto if (currReadErrors > maxReadErrors) { lgError($"Superato limite errori Read ({currReadErrors}) --> tryDisconnect"); currReadErrors = 0; tryDisconnect(); } else { // altrimenti pausa forzata Thread.Sleep(300); } } /// /// Effettua reset del contapezzi, NON POSSIBILE in questa versione /// /// public override bool resetcontapezziPLC() { bool answ = false; return answ; } /// /// Effettua IMPOSTAZIONE FORZATA del contapezzi, NON POSSIBILE in questa versione /// /// public override bool setcontapezziPLC(int newPzCount) { bool answ = false; return answ; } /// /// 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("OpcUa: 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; var task = Task.Run(async () => { return await doConnect().ConfigureAwait(false); }); short esitoLink = task.Result; // use returned result from async method here lgInfo($"szStatusConnection OpcUa, esitoLink: {esitoLink}"); parentForm.commPlcActive = false; connectionOk = true; // refresh stato allarmi!!! if (connectionOk) { if (adpRunning) { lgInfo("Connessione OK"); } } else { lgError("Impossibile procedere, connessione mancante..."); } } catch (Exception exc) { lgFatal($"Errore nella connessione all'adapter OpcUa: {szStatusConnection}{Environment.NewLine}{exc}"); connectionOk = false; lgInfo($"Eccezione in TryConnect, Adapter OpcUa 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: OpcUa 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(); } } /// /// Override disconnessione /// public override void tryDisconnect() { if (connectionOk) { if (UA_ref != null) { string szStatusConnection = ""; try { UA_ref.Disconnect(); connectionOk = false; lgInfo(szStatusConnection); lgInfo("Effettuata disconnessione adapter OpcUa!"); } catch (Exception exc) { lgFatal(exc, "Errore nella disconnessione dall'adapter OpcUa"); } } else { lgDebug("IMPOSSIBILE effettuare disconnessione OpcUa: UA_ref non disponibile..."); } } else { lgError("IMPOSSIBILE effettuare disconnessione OpcUa: Connessione non disponibile..."); } } #endregion Public Methods #region Internal Methods /// /// Verifica ed invia variazioni /// /// /// /// internal bool checkAndSend(Opc.Ua.Client.MonitoredItem MonIt, string NotifyValue, bool forceSend) { bool changed = false; if (MonIt != null) { if (!string.IsNullOrEmpty(NotifyValue)) { string sVal = ""; string descr = ""; DateTime locTStamp = DateTime.Now; descr = itemTranslation("OPC", MonIt.DisplayName); sVal = $"Change: {locTStamp.ToString()} | descr: {descr} | Id: {MonIt.StartNodeId} | Val: {NotifyValue}"; lgInfo(sVal); // verifico se salvare changed = checkSaveValue(MonIt, NotifyValue, true); // cerco se non sia un dato filtrato in FLUXLOG... bool isFiltered = opcUaParams.fluxLogVeto.Contains(MonIt.DisplayName); if (isFiltered) { lgTrace($"NON ACCODATO sample per {MonIt.DisplayName} - trovato VETO in fluxLogVeto", false); } else { if (changed || forceSend) { accodaFLog(sVal, qEncodeFLog(descr, $"{NotifyValue}")); } else { lgTrace($"NON ACCODATO sample per {MonIt.DisplayName} - verifica variazione ha dato esito negativo", false); } } } else { lgError($"checkAndSend ERROR | MonIt: {MonIt.DisplayName} | NotifyValue Null!!!"); } } else { lgError("checkAndSend ERROR: MonIt null"); } return changed; } /// /// Verifica ed invia variazioni DAL FORMATO RAW data (byte[]) /// /// /// /// internal virtual bool checkAndSendRaw(Opc.Ua.Client.MonitoredItem MonIt, byte[] NotifyValue, bool forceSend) { bool changed = false; if (MonIt != null) { if (NotifyValue != null && NotifyValue.Length > 0) { // versione base: il valore è la stringa composta da TUTTI i valori in BYTE // espressi come comma-sep-string StringBuilder sb = new StringBuilder(); foreach (var bVal in NotifyValue) { sb.Append($"{bVal},"); } string currVal = sb.ToString(); string sVal = ""; string descr = ""; DateTime locTStamp = DateTime.Now; descr = itemTranslation("OPC", MonIt.DisplayName); sVal = $"Change: {locTStamp.ToString()} | descr: {descr} | Id: {MonIt.StartNodeId} | Val: {currVal}"; lgInfo(sVal); // verifico se salvare changed = checkSaveValue(MonIt, currVal, true) || forceSend; // cerco se non sia un dato filtrato in FLUXLOG... bool isFiltered = opcUaParams.fluxLogVeto.Contains(MonIt.DisplayName); if (isFiltered) { lgTrace($"NON ACCODATO sample per {MonIt.DisplayName} - trovato VETO in fluxLogVeto", false); } else { if (changed || forceSend) { accodaFLog(sVal, qEncodeFLog(descr, $"{NotifyValue}")); } else { lgTrace($"NON ACCODATO sample per {MonIt.DisplayName} - verifica variazione ha dato esito negativo", false); } } } else { lgError($"checkAndSend ERROR | MonIt: {MonIt.DisplayName} | NotifyValue Null!!!"); } } else { lgError("checkAndSend ERROR: MonIt null"); } return changed; } internal void sendDataItemListToServer(NodeId StartNodeId) { // converto gli attuali nell'elenco dataitem... List elencoDataItems = dataItemMem.Select(d => new machDataItem() { uuid = d.Key, Category = DataItemCategory.EVENT, Name = d.Value.DisplayName, Type = $"{d.Value.NodeClass}", SubType = $"{StartNodeId}" } ).ToList(); lgInfo($"Richiesta sendDataItemListToServer per {elencoDataItems.Count} dataItems"); // aspetta un tempo random da 10-100 ms... Random rnd = new Random(); Task.Delay(rnd.Next(10, 100)); // invio il dataItem serializzato... sendDataItemsList(elencoDataItems); } /// /// Evento rilevazione modifica valori --> chiamo checkSend /// /// /// internal virtual void UA_ref_eh_MonItChange(object sender, opcUaMonitItemChange e) { string currVal = ""; // da verificare decodifica valore byte... if (doByteRead) { var currNot = e.CurrNotify; if (currNot != null) { byteRawData = getByteRaw((DataValue)currNot.Value); checkAndSendRaw(e.CurrMonitoredItem, byteRawData, false); } } else { currVal = $"{e.CurrNotify.Value}"; checkAndSend(e.CurrMonitoredItem, currVal, false); } // aggiorno ultima lettura lastCurrent = DateTime.Now; } #endregion Internal Methods #region Protected Fields /// /// Struttura dove vengono memorizzati i dataitem ed i rispettivi valori x processing /// protected Dictionary dataItemMem = new Dictionary(); /// /// Abilitazione restart (da opt par...) /// protected bool enableCliRestart = false; /// /// Gestione filtraggio dati /// protected bool enableDataFilter = false; /// /// Determina se ha effettuata lettura items in memoria x confronto... /// protected bool hasReadItems = false; /// /// Ultimo current received x gestione update periodico... /// protected DateTime lastCurrent = DateTime.Now; /// /// Oggetto MAIN x connessione MTC /// protected UAClient UA_ref; /// /// Veto controllo status x log... /// protected DateTime vetoCheckStatus = DateTime.Now; protected int WatchDog = 0; #endregion Protected Fields #region Protected Properties /// /// Area dati raw per lettura encoded (SE presente) /// protected byte[] byteRawData { get; set; } = new byte[1]; /// /// Indica se si debba leggere un area di tipo "encoded" (byte raw --> obj) /// protected bool doByteRead { get; set; } = false; /// /// Verifico se abbia ALMENO un errore... /// protected bool hasError { get { return checkMultiCondition(opcUaParams.condError); } } /// /// Indica se abbia emergenza ARMATA (cond normale) /// protected bool hasEStopArmed { get { return checkMultiCondition(opcUaParams.condEStop); } } /// /// Indica se abbia stato POWER ON (multicondizione) /// protected virtual bool hasPowerOn { get { return checkMultiCondition(opcUaParams.condPowerOn); } } /// /// Indica se abbia stato MANUAL (condizioni varie, es stopped) /// protected virtual bool isManual { get { return checkMultiCondition(opcUaParams.condManual); } } /// /// Indica se abbia stato READY (condizioni varie, es ausiliari OK) /// protected virtual bool isReady { get { return checkMultiCondition(opcUaParams.condReady); } } /// /// Indica se sia in stato Ssetup /// protected bool isSetup { get { return checkMultiCondition(opcUaParams.condSetup); } } /// /// Indica se sia in stato WarmUp / CoolDown (riscaldamento/raffreddamento) /// protected bool isWarmUpCoolDown { get { return checkMultiCondition(opcUaParams.condWarmUpCoolDown); } } /// /// Indica se sia in stato Warning /// protected bool isWarning { get { return checkMultiCondition(opcUaParams.condWarning); } } /// /// Indica se abbia stato READY (condizioni varie, es ausiliari OK) /// protected bool isWorking { get { // cerco SE HO cond OPC Ua o classica... nel caso creo e salvo if (opcUaParams.condWorkOpc.checkList == null || opcUaParams.condWorkOpc.checkList.Count == 0) { opcUaParams.condWorkOpc = new diCheckCondSetup() { checkList = opcUaParams.condWork, checkMode = boolCheckMode.AND, negateValue = false }; } return checkMultiCondition(opcUaParams.condWorkOpc); } } /// /// Periodo massimo (in sec) per letture dati RAW in mancanza di eventi /// protected int lastCurrentMaxElapsed { get; set; } = 120; /// /// Parametri specifici MTC /// protected OpcUaParamConf opcUaParams { get; set; } /// /// URL x salvataggio elenco dataItems OpcUa /// protected string urlSaveDataItems { get { string answ = ""; try { string machineName = Environment.MachineName; answ = $@"{cIobConf.serverData.TRANSP}://{cIobConf.serverData.MPIP}{cIobConf.serverData.MPURL}{cIobConf.serverData.CMDALIVE}/saveDataItems/{cIobConf.codIOB}"; } catch (Exception exc) { lgError(exc, "Errore in composizione urlSaveDataItems"); } return answ; } } #endregion Protected Properties #region Protected Methods /// /// Verifica un DataItem e se il valore corrisponde a quello indicato come "true value" /// restituisce true /// /// /// /// protected bool checkDataItem(string itemName, string trueVal) { bool answ = false; OpcUaDataItemExt currValue = null; try { currValue = dataItemMem[itemName]; answ = (currValue.value.Equals(trueVal)); } catch { lgError($"Errore in decodifica valore per {itemName} rispetto a {trueVal} | recuperato {currValue} / {currValue.value}"); } return answ; } /// /// Verifica condizione "multipla" secondo setup json /// /// Set condizioni da validare /// protected bool checkMultiCondition(diCheckCondSetup reqCondition) { bool answ = false; int numCondOk = 0; int numCond = 0; if (reqCondition.checkList != null && reqCondition.checkList.Count > 0) { numCond = reqCondition.checkList.Count; // cerco nell'elenco delle condizioni che indicano lavora se sono ok faccio +1 conteggio...... foreach (var item in reqCondition.checkList) { if (string.IsNullOrEmpty(item.keyName)) { lgError($"Attenzione: item vuoto in checkMultiCondition{Environment.NewLine}StackTrace: {Environment.StackTrace}"); } else { if (getDataItemValue(item.keyName) == item.targetValue) { numCondOk++; } } } if (reqCondition.checkMode == boolCheckMode.AND) { answ = (numCond == numCondOk); } else if (reqCondition.checkMode == boolCheckMode.OR) { answ = numCondOk > 0; } } else { answ = true; } // verifico se devo negare il valore... answ = reqCondition.negateValue ? !answ : answ; // restituisco return answ; } /// /// Verifica / Salva valore e restitusice SE sia variato (e quindi da inviare...) /// /// /// /// /// protected bool checkSaveValue(Opc.Ua.Client.MonitoredItem dataItem, string NotifyValue, bool sendItemList) { bool answ = !enableDataFilter; double oldVal = 0; double newVal = 0; if (dataItem != null) { if (!string.IsNullOrEmpty(NotifyValue)) { lgTrace($"Richiesta checkSaveValue per {dataItem.DisplayName} | id: {dataItem.StartNodeId} | Valore: {NotifyValue}"); // verifico in memoria se ho l'oggetto condition ed il suo valore.. string uuid = $"{dataItem.DisplayName}"; DateTime adesso = DateTime.Now; if (dataItemMem.ContainsKey(uuid)) { OpcUaDataItemExt currDataItemMem = dataItemMem[uuid]; // controllo SE SIA scaduto il tempo massimo... if (Math.Abs(dataItemMem[uuid].valueTimestamp.Subtract(adesso).TotalSeconds) > currDataItemMem.samplePeriod) { answ = true; } else { // ALTRIMENTI controllo SE diverso if (dataItemMem[uuid].value != $"{NotifyValue}") { lgInfo($"Val uuid: {dataItemMem[uuid].value} | NotifyValue: {NotifyValue}"); // controllo SE ho DeadBand... if (dataItemMem[uuid].thresholdDeadBand > 0) { // recupero i valori e testo DeadBand... bool isNum01 = double.TryParse(dataItemMem[uuid].value.Replace(".", ","), out oldVal); bool isNum02 = double.TryParse($"{NotifyValue}".Replace(".", ","), out newVal); // test deadband! if (!(isNum01 && isNum02)) { answ = true; } else { if (Math.Abs(newVal - oldVal) > dataItemMem[uuid].thresholdDeadBand) { // indico da salvare.. answ = true; } } lgInfo($"Test deadband: oldVal: {oldVal} | newVal: {newVal}"); } } } if (answ) { // salvo! dataItemMem[uuid].value = $"{NotifyValue}"; dataItemMem[uuid].valueTimestamp = adesso; } } else { // registro non trovato da aggiungere... lgInfo($"DataItem non trovato in checkSaveValue: {dataItem.DisplayName}"); // provo a creare oggetto in memoria... try { int dSamplePeriod = 0; int threshDBand = 0; uuid = ""; var currDataItem = formatDataItem(ref dSamplePeriod, ref threshDBand, ref uuid, dataItem); // sistemo valore/periodo currDataItem.value = $"{NotifyValue}"; currDataItem.valueTimestamp = adesso; // aggiungo dataItemMem.Add(uuid, currDataItem); if (sendItemList) { sendDataItemListToServer(dataItem.StartNodeId); } } catch (Exception exc) { lgError($"Eccezione in checkSaveSample{Environment.NewLine}{exc}"); } } } else { lgError("Attenzione: checkSaveItem con Notify null!"); } } else { lgError("Attenzione: checkSaveItem con MonIt null!"); } return answ; } /// /// Effettua decodifica aree memoria alla bitmap usata x MAPO /// protected virtual void decodeToBaseBitmap() { DateTime adesso = DateTime.Now; // init a zero... B_input = 0; /* ----------------------------------------------------- * STATE MACHINE 60 STD / SIMULA *------------------------------------------------------ * bitmap MAPO * B0: POWER_ON * B1: RUN * B2: pzCount * B3: allarme * B4: manuale * B5: SlowTC (NON gestito qui) * B6: warm-up / cool-down * B7: emergenza ---------------------------------------------------- */ // se valido il check ping lo eseguo... altrimenti lo do x buono bool checkPing = !opcUaParams.pingAsPowerOn; string currRun = ""; if (!checkPing) { checkPing = (testPingMachine == IPStatus.Success); } // bit 0 (poweron) imposto a 1 SE pingo + PowerOn=="ON"... bool powerOnOk = checkPing && hasPowerOn; // controllo se sono poweroff e se non ho dati buoni da > lastCurrentMaxElapsed --> disconnetto if (!powerOnOk && adesso.Subtract(lastCurrent).TotalSeconds > lastCurrentMaxElapsed) { tryDisconnect(); } // solo se non ho veto check int vFactor = 2; if (vetoCheckStatus < adesso) { lgTrace($"Stato variabili checkPing: {testPingMachine}"); // imposto veto per vetoSeconds... vetoCheckStatus = adesso.AddSeconds(vetoSeconds * vFactor); } // se abilitato watchdog... if (opcUaParams.WatchDog.IsEnabled) { lgTrace("WatchDog 01"); if (adesso.Subtract(lastWatchDogPLC).TotalSeconds > 2) { lastWatchDogPLC = adesso; WatchDog++; WatchDog = WatchDog > opcUaParams.WatchDog.MaxVal ? 0 : WatchDog; lgTrace($"WatchDog val: {WatchDog}"); try { WriteValue commWriteVal = new WriteValue(); commWriteVal.NodeId = new NodeId(opcUaParams.WatchDog.MemConfWrite); commWriteVal.AttributeId = Attributes.Value; commWriteVal.Value = new DataValue(); commWriteVal.Value.Value = WatchDog; List nodes2Write = new List(); nodes2Write.Add(commWriteVal); UA_ref.WriteNodes(nodes2Write); lgTrace("Effettuata scrittura WatchDog"); } catch (Exception exc) { lgError($"Eccezione in gestione WatchDog, valore attuale {WatchDog}{Environment.NewLine}{exc}"); } } } else { lgTrace("WatchDog disabilitato"); } // log opzionale! if (verboseLog) { lgDebug($"Trasformazione checkPing: {checkPing} | hasPowerOn: {hasPowerOn} | B_input: {B_input} | currRun = {currRun}"); } } protected byte[] getByteRaw(DataValue obj) { if (obj == null) return null; byte[] rawByte = new byte[1]; if (obj != null) { try { var wrapVal = ((DataValue)obj).WrappedValue; //rawByte = ObjectToByteArray(rawVal); var bodyVal = ((Opc.Ua.ExtensionObject)wrapVal.Value).Body; rawByte = (byte[])bodyVal; } catch { } } return rawByte; } /// /// Effettua traduzione ITEM da LUT parametrica (key: tipo+id) del file di conf, se non /// trovo uso key /// /// /// /// protected string itemTranslation(string tipo, string id) { string answ = ""; string lemma = id; if (!string.IsNullOrEmpty(tipo)) { lemma = $"{tipo}_{id}"; } // cerco nel dizionario delle traduzioni SE esiste un valore e prendo quello, altrimenti // uso il lemma... if (opcUaParams.itemTranslation.ContainsKey(lemma)) { answ = opcUaParams.itemTranslation[lemma]; } else { answ = lemma; } return answ; } /// /// Effettua lettura file di conf specifico OPC-UA da oggetto serializzato json Nome file da cui leggere i parametri json /// protected void loadOpcUaConf(string fileName) { string jsonFullPath = $"{Application.StartupPath}/DATA/CONF/{fileName}"; lgInfo($"Apertura file {jsonFullPath}"); using (StreamReader reader = new StreamReader(jsonFullPath)) { string jsonData = reader.ReadToEnd().Replace("\n", "").Replace("\r", ""); if (!string.IsNullOrEmpty(jsonData)) { lgDebug($"File json composto da {jsonData.Length} caratteri"); try { opcUaParams = JsonConvert.DeserializeObject(jsonData); lgDebug($"Decodifica aree OpcUaParamConf: trovati {opcUaParams.paramsEndThresh.Count} valori paramsEndThresh"); // sistemo se ci sono dati memMap... memMap = new plcMemMap(); if (opcUaParams.mMapWrite != null) { memMap.mMapWrite = opcUaParams.mMapWrite; } if (opcUaParams.mMapRead != null) { memMap.mMapRead = opcUaParams.mMapRead; } setupMemMap(); } catch (Exception exc) { lgError($"Eccezione in decodifica conf json OPC-UA:{Environment.NewLine}{exc}"); } } else { lgError("Errore in loadOpcUaConf: file json vuoto!"); } } //reader.Dispose(); } #endregion Protected Methods #region Private Fields /// /// Elenco degli items da monitorare come risultato del browse iniziale /// private Dictionary selectedItemList = new Dictionary(); #endregion Private Fields #region Private Methods /// /// Vera connessione ad OpcUa /// /// private async Task doConnect() { short esitoLink = 0; // reset memoria dataItem.. dataItemMem = new Dictionary(); // predisposizione conf oggetto di comunicazione MTC int port = 4840; int.TryParse(cIobConf.cncPort, out port); // ora avvio try { lgInfo("Start init OpcUa Client"); // Define the UA Client application ApplicationInstance application = new ApplicationInstance(); application.ApplicationName = "Steamware IOB-WIN Client"; application.ApplicationType = ApplicationType.Client; // load the application configuration. string confPath = $"{Application.StartupPath}\\DATA\\CONF\\IobOpcUaClient.Config.xml"; await application.LoadApplicationConfiguration(confPath, silent: false).ConfigureAwait(false); // check the application certificate. await application.CheckApplicationInstanceCertificate(silent: false, minimumKeySize: 0).ConfigureAwait(false); lgInfo($"Chiamata UAClient con configurazione standard: {application.ApplicationConfiguration.ApplicationName}"); UA_ref = new UAClient(application.ApplicationConfiguration, cIobConf.codIOB, opcUaParams.Identity.UserName, opcUaParams.Identity.Passwd, isVerboseLog, ClientBase.ValidateResponse); lgInfo($"Chiamata apertura OpcUa Client: {cIobConf.cncIpAddr}:{port}"); UA_ref.ServerUrl = $"opc.tcp://{cIobConf.cncIpAddr}:{port}"; var task = Task.Run(async () => { return await UA_ref.ConnectAsync().ConfigureAwait(false); }); bool connected = task.Result; if (connected) { // faccio un primo browse dei dati... Dictionary nodeIdNameList = new Dictionary(); if (!string.IsNullOrEmpty(opcUaParams.BrowseFullVal)) { try { UA_ref.Browse(opcUaParams.BrowseFullVal, opcUaParams.filterItemsNodeId, ref nodeIdNameList); } catch { } } else { UA_ref.Browse(opcUaParams.BrowseNSIndex, opcUaParams.BrowseValue, opcUaParams.filterItemsNodeId, ref nodeIdNameList); } // loggo elenco degli item sottocrivibili... lgDebug("---------- AVAILABLE FOR SUBSCRIBE ----------"); foreach (var item in nodeIdNameList) { lgDebug(item.Key); } lgDebug("---------- END LIST ----------"); // se ho un insieme non vuoto degli item sottoscritti carico solo quelli if (opcUaParams.subscribedItems != null && opcUaParams.subscribedItems.Count > 0) { // cerco e aggiungo SOLO quelle indicati foreach (var currItem in opcUaParams.subscribedItems) { var foundItems = nodeIdNameList.Where(x => x.Key.Contains(currItem)).ToList(); if (foundItems != null && foundItems.Count > 0) { foreach (var fItem in foundItems) { // verifico di NON duplicare... if (!selectedItemList.ContainsKey(fItem.Key)) { selectedItemList.Add(fItem.Key, fItem.Value); } } } else { lgDebug($"subscribedItems non trovato: {currItem}"); } } lgDebug($"Aggiunti {selectedItemList.Count} items!"); } // altrimenti tutti! else { selectedItemList = nodeIdNameList; } // loggo elenco degli item sottocrivibili... lgInfo("---------- SUBSCRIBED NODES ----------"); foreach (var item in selectedItemList) { lgInfo(item.Key); } lgInfo("---------- END LIST ----------"); // sottoscrivo a rilevazione cambio dati solo l'incrocio degli insiemi List subscribedItems = UA_ref.SubscribeToDataChanges(selectedItemList); // aggiungo come DataItems int dSamplePeriod = 0; int threshDBand = 0; string uuid = ""; foreach (var item in subscribedItems) { bool changed = false; OpcUaDataItemExt newItem = formatDataItem(ref dSamplePeriod, ref threshDBand, ref uuid, item); // controllo non sia già stato aggiunto if (dataItemMem.ContainsKey(uuid)) { lgDebug($"Item ALREADY subscribed: {uuid} | NOT re-adding"); } else { dataItemMem.Add(uuid, newItem); lgDebug($"Item subscribed: {uuid}"); // verifico se ho i dati complessi by design string currVal = ""; if (doByteRead) { // restituisce i 115 byte da deserializzare... qui HACK! var rawVal = UA_ref.ReadNodeRaw(item.StartNodeId); if (rawVal != null) { byteRawData = getByteRaw((DataValue)rawVal); changed = checkAndSendRaw(item, byteRawData, true); } } else { currVal = UA_ref.ReadNode(item.StartNodeId); changed = checkAndSend(item, currVal, true); } } } // gestione eventi change UA_ref.eh_MonItChange += UA_ref_eh_MonItChange; lgInfo("eh_MonItChange event registered"); } esitoLink = 1; // fix tempi! DateTime adesso = DateTime.Now; lastPzCountSend = adesso; lastWarnODL = adesso; lastCurrent = adesso; } catch (Exception exc) { lgError($"Eccezione in doConnect{Environment.NewLine}{exc}"); } return esitoLink; } /// /// Formatta un dataitem x salvataggio in memoria locale /// /// /// /// /// /// private OpcUaDataItemExt formatDataItem(ref int dSamplePeriod, ref int threshDBand, ref string uuid, Opc.Ua.Client.MonitoredItem dataItem) { OpcUaDataItemExt currDataItem; // calcolo parametri uuid = $"{dataItem.DisplayName}"; // SOLO SE è abilitato il datafiltering... if (enableDataFilter) { threshDBand = 1; // controllo SE ho conf x deadband... if (opcUaParams.paramsEndThresh.Count > 0) { // ciclo su tutti i parametri indicati... foreach (var item in opcUaParams.paramsEndThresh) { if (uuid.EndsWith(item.Key)) { threshDBand = item.Value; } } } } else { threshDBand = 0; } dSamplePeriod = 60; // salvo oggetto x "uso interno" currDataItem = new OpcUaDataItemExt(dataItem) { uid = uuid, thresholdDeadBand = threshDBand, samplePeriod = dSamplePeriod }; return currDataItem; } /// /// Effettua invio a MP/IO dell'elenco serializzato dei dataItems /// /// private void sendDataItemsList(List dataItems) { string rawData = JsonConvert.SerializeObject(dataItems); try { utils.callUrlNow($"{urlSaveDataItems}", rawData); lgInfo($"Effettuata chiamata sendDataItemsList all'url {urlSaveDataItems}"); } catch (Exception exc) { lgError($"Eccezione in sendDataItemsList{Environment.NewLine} - url: {urlSaveDataItems}{Environment.NewLine}- payload:{rawData}{Environment.NewLine}Eccezione:{Environment.NewLine}{exc}"); } } #endregion Private Methods } }