From cac0fb5727ba027e2dec097e25d86a5bc07f5997 Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Fri, 13 May 2022 09:12:57 +0200 Subject: [PATCH] Update x verifica log errori invio + cambio logica invio (reiduco chiamate) --- IOB-UT-NEXT/baseUtils.cs | 6 +- IOB-WIN-NEXT/IobOpcUa.cs | 1907 ++++++++++++++-------------- IOB-WIN-NEXT/IobOpcUaOmronIcoel.cs | 795 ++++++------ 3 files changed, 1386 insertions(+), 1322 deletions(-) diff --git a/IOB-UT-NEXT/baseUtils.cs b/IOB-UT-NEXT/baseUtils.cs index 25565fc4..7f074349 100644 --- a/IOB-UT-NEXT/baseUtils.cs +++ b/IOB-UT-NEXT/baseUtils.cs @@ -251,15 +251,15 @@ namespace IOB_UT_NEXT { // problema certificati locali... // https://living-sun.com/it/c/226351-webexception-could-not-establish-trust-relationship-for-the-ssl-tls-secure-channel-c-aspnet-web-services-ssl-webexception.html - // hack x certificati locali in 10.74.82.* - if (URL.Contains("10.74.82.")) + // hack x certificati locali in 10.74.* + if (URL.Contains("10.74.")) { System.Net.ServicePointManager.ServerCertificateValidationCallback = (senderX, certificate, chain, sslPolicyErrors) => { return true; }; } answ = clientPayload.UploadString(URL, payload); if (answ != "OK") { - lg.Error($"Invio dati fallito:{Environment.NewLine}- URL{Environment.NewLine}{URL}{Environment.NewLine}- payload{Environment.NewLine}{payload}"); + lg.Error($"Invio dati fallito, ricevuto messaggio [{answ}]:{Environment.NewLine}- URL{Environment.NewLine}{URL}{Environment.NewLine}- payload{Environment.NewLine}{payload}"); } } catch (Exception exc) diff --git a/IOB-WIN-NEXT/IobOpcUa.cs b/IOB-WIN-NEXT/IobOpcUa.cs index c92a8a14..3e21cc06 100644 --- a/IOB-WIN-NEXT/IobOpcUa.cs +++ b/IOB-WIN-NEXT/IobOpcUa.cs @@ -1,78 +1,26 @@ using IOB_UT_NEXT; using MapoSDK; -using MTConnect.Clients; using Newtonsoft.Json; -using System; -using System.Threading.Tasks; using Opc.Ua; using Opc.Ua.Configuration; +using System; using System.Collections.Generic; using System.IO; -using System.Net.NetworkInformation; -using System.Threading; -using System.Windows.Forms; using System.Linq; +using System.Net.NetworkInformation; using System.Text; -using System.Runtime.Serialization.Formatters.Binary; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; namespace IOB_WIN_NEXT { public class IobOpcUa : IobGeneric { - #region Private Fields - - /// - /// Struttura dove vengono memorizzati i dataitem ed i rispettivi valori x processing - /// - protected Dictionary dataItemMem = new Dictionary(); - - /// - /// Elenco degli items da monitorare come risultato del browse iniziale - /// - private Dictionary selectedItemList = new Dictionary(); - - #endregion Private Fields - - #region Protected Fields - - /// - /// 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; - - #endregion Protected Fields - #region Public Constructors /// - /// Estende l'init della classe base, impiegando il pacchetto Nuget OPC-UA foundation - /// https://github.com/OPCFoundation/UA-.NETStandard + /// Estende l'init della classe base, impiegando il pacchetto Nuget OPC-UA foundation https://github.com/OPCFoundation/UA-.NETStandard /// /// /// @@ -106,889 +54,6 @@ namespace IOB_WIN_NEXT #endregion Public Constructors - #region Protected Properties - - /// - /// 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 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 sia in stato Ssetup - /// - protected bool isSetup - { - get - { - return checkMultiCondition(opcUaParams.condSetup); - } - } - - /// - /// 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); - } - } - - /// - /// 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 Private Methods - - /// - /// Verifica ed invia variazioni - /// - /// - /// - /// - internal void checkAndSend(Opc.Ua.Client.MonitoredItem MonIt, string NotifyValue, bool forceSend) - { - 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 - bool changed = checkSaveValue(MonIt, NotifyValue); - // 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"); - } - } - - /// - /// Verifica ed invia variazioni DAL FORMATO RAW data (byte[]) - /// - /// - /// - /// - internal virtual void checkAndSendRaw(Opc.Ua.Client.MonitoredItem MonIt, byte[] NotifyValue, bool forceSend) - { - 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 - bool changed = checkSaveValue(MonIt, currVal); - // 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"); - } - } - - /// - /// Indica se si debba leggere un area di tipo "encoded" (byte raw --> obj) - /// - protected bool doByteRead { get; set; } = false; - /// - /// Area dati raw per lettura encoded (SE presente) - /// - protected byte[] byteRawData { get; set; } = new byte[1]; - - /// - /// 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) - { - 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); - checkAndSendRaw(item, byteRawData, true); - } - } - else - { - currVal = UA_ref.ReadNode(item.StartNodeId); - 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; - } - - 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; - } - - /// - /// 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); - } - catch(Exception exc) - { - lgError($"Eccezione in sendDataItemsList{Environment.NewLine} - url: {urlSaveDataItems}{Environment.NewLine}- payload:{rawData}{Environment.NewLine}Eccezione:{Environment.NewLine}{exc}"); - } - } - - /// - /// Periodo massimo (in sec) per letture dati RAW in mancanza di eventi - /// - protected int lastCurrentMaxElapsed { get; set; } = 120; - - /// - /// 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; - } - - protected int WatchDog = 0; - - #endregion Private Methods - - #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 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); - // 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 = $"{dataItem.StartNodeId}" - } - ).ToList(); - - // aspetta un tempo random da 10-100 ms... - Random rnd = new Random(); - Task.Delay(rnd.Next(10, 100)); - // invio il dataItem serializzato... - sendDataItemsList(elencoDataItems); - } - 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}"); - } - } - - /// - /// 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 Public Methods /// @@ -1099,8 +164,8 @@ namespace IOB_WIN_NEXT } /// - /// Effettua lettura semafori principale - /// Parametri da aggiornare x display in form + /// Effettua lettura semafori principale Parametri da + /// aggiornare x display in form /// public override void readSemafori(ref newDisplayData currDispData) { @@ -1159,7 +224,6 @@ namespace IOB_WIN_NEXT // altrimenti pausa forzata Thread.Sleep(300); } - } /// @@ -1291,5 +355,960 @@ namespace IOB_WIN_NEXT } #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); + // 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(); + + // 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); + } + catch (Exception exc) + { + lgError($"Eccezione in sendDataItemsList{Environment.NewLine} - url: {urlSaveDataItems}{Environment.NewLine}- payload:{rawData}{Environment.NewLine}Eccezione:{Environment.NewLine}{exc}"); + } + } + + #endregion Private Methods } } \ No newline at end of file diff --git a/IOB-WIN-NEXT/IobOpcUaOmronIcoel.cs b/IOB-WIN-NEXT/IobOpcUaOmronIcoel.cs index 3afd1935..49039eb3 100644 --- a/IOB-WIN-NEXT/IobOpcUaOmronIcoel.cs +++ b/IOB-WIN-NEXT/IobOpcUaOmronIcoel.cs @@ -1,12 +1,9 @@ using MapoSDK; -using Newtonsoft.Json; using Opc.Ua; using System; using System.Collections.Generic; using System.Linq; using System.Net.NetworkInformation; -using System.Text; -using System.Threading.Tasks; namespace IOB_WIN_NEXT { @@ -17,127 +14,10 @@ namespace IOB_WIN_NEXT Manuale = 2, Automatico = 3 } - public class MesItemStatus - { - public IcoelStatus Stato { get; set; } = 0; - public UInt16 Velocita { get; set; } = 0; - public bool Termico { get; set; } = false; - public bool MagnetoTermico { get; set; } = false; - public bool AvariaInverter { get; set; } = false; - - public MesItemStatus(byte[] rawData) - { - Stato = (IcoelStatus)BitConverter.ToUInt16(rawData, 0); - Velocita = BitConverter.ToUInt16(rawData, 2); - Termico = !rawData.Skip(4).Take(1).FirstOrDefault().Equals(0); - MagnetoTermico = !rawData.Skip(5).Take(1).FirstOrDefault().Equals(0); - AvariaInverter = !rawData.Skip(6).Take(1).FirstOrDefault().Equals(0); - } - - /// - /// Converte un singolo item in un array di byte per scrittura su PLC S7 - /// - /// - public byte[] serialize() - { - byte[] answ = new byte[7]; - Buffer.BlockCopy(BitConverter.GetBytes((short)Stato), 0, answ, 0, 2); - Buffer.BlockCopy(BitConverter.GetBytes(Velocita), 0, answ, 2, 2); - Buffer.BlockCopy(BitConverter.GetBytes(Termico), 0, answ, 4, 1); - Buffer.BlockCopy(BitConverter.GetBytes(MagnetoTermico), 0, answ, 5, 1); - Buffer.BlockCopy(BitConverter.GetBytes(AvariaInverter), 0, answ, 6, 1); - return answ; - } - - - public override bool Equals(object obj) - { - // Object is not a GaugeModel instance - if (!(obj is MesItemStatus item)) - return false; - - if (Stato != item.Stato) - return false; - if (Velocita != item.Velocita) - return false; - if (Termico != item.Termico) - return false; - if (MagnetoTermico != item.MagnetoTermico) - return false; - if (AvariaInverter != item.AvariaInverter) - return false; - - return true; - } - - public override int GetHashCode() - { - return base.GetHashCode(); - } - - } - - public class PlantStatus - { - public bool InMarcia { get; set; } = false; - public bool InEmergenza { get; set; } = false; - public bool InStop { get; set; } = false; - public PlantStatus(byte[] rawData) - { - InMarcia = !rawData.Skip(0).Take(1).FirstOrDefault().Equals(0); - InEmergenza = !rawData.Skip(1).Take(1).FirstOrDefault().Equals(0); - InStop = !rawData.Skip(2).Take(1).FirstOrDefault().Equals(0); - - //var valore = BitConverter.ToUInt16(rawData, 0); - - - //byte b = rawData.Skip(0).Take(1).FirstOrDefault(); - //InMarcia = (b & (1 << (1 - 1))) != 0; - //InEmergenza = (b & (1 << (2 - 1))) != 0; - //InStop = (b & (1 << (3 - 1))) != 0; ; - } - - public override bool Equals(object obj) - { - // Object is not a GaugeModel instance - if (!(obj is PlantStatus item)) - return false; - - if (InMarcia != item.InMarcia) - return false; - if (InEmergenza != item.InEmergenza) - return false; - if (InStop != item.InStop) - return false; - - return true; - } - - public override int GetHashCode() - { - return base.GetHashCode(); - } - } public class DatiMesIcoel { - public MesItemStatus Calibratrice_L1 { get; set; } - public MesItemStatus Calibratrice_L2 { get; set; } - public MesItemStatus Dewatering_L1 { get; set; } - public MesItemStatus Dewatering_L2 { get; set; } - public MesItemStatus Precalibro_L1 { get; set; } - public MesItemStatus Precalibro_L2 { get; set; } - public MesItemStatus Taglierina_L1 { get; set; } - public MesItemStatus Taglierina_L2 { get; set; } - public MesItemStatus NastroTaglierina_L1 { get; set; } - public MesItemStatus NastroTaglierina_L2 { get; set; } - public MesItemStatus Elevatore_L1 { get; set; } - public MesItemStatus Elevatore_L2 { get; set; } - public MesItemStatus ImmergitoreBins_L1 { get; set; } - public MesItemStatus ImmergitoreBins_L2 { get; set; } - public MesItemStatus ImmergitoreCasse_L1 { get; set; } - public MesItemStatus ImmergitoreCasse_L2 { get; set; } - public PlantStatus Varie { get; set; } + #region Public Constructors /// /// Inizializzazione classe con dati RAW da byte[] @@ -169,6 +49,31 @@ namespace IOB_WIN_NEXT } } + #endregion Public Constructors + + #region Public Properties + + public MesItemStatus Calibratrice_L1 { get; set; } + public MesItemStatus Calibratrice_L2 { get; set; } + public MesItemStatus Dewatering_L1 { get; set; } + public MesItemStatus Dewatering_L2 { get; set; } + public MesItemStatus Elevatore_L1 { get; set; } + public MesItemStatus Elevatore_L2 { get; set; } + public MesItemStatus ImmergitoreBins_L1 { get; set; } + public MesItemStatus ImmergitoreBins_L2 { get; set; } + public MesItemStatus ImmergitoreCasse_L1 { get; set; } + public MesItemStatus ImmergitoreCasse_L2 { get; set; } + public MesItemStatus NastroTaglierina_L1 { get; set; } + public MesItemStatus NastroTaglierina_L2 { get; set; } + public MesItemStatus Precalibro_L1 { get; set; } + public MesItemStatus Precalibro_L2 { get; set; } + public MesItemStatus Taglierina_L1 { get; set; } + public MesItemStatus Taglierina_L2 { get; set; } + public PlantStatus Varie { get; set; } + + #endregion Public Properties + + #region Public Methods public override bool Equals(object obj) { @@ -218,17 +123,193 @@ namespace IOB_WIN_NEXT { return base.GetHashCode(); } + + #endregion Public Methods } public class IobOpcUaOmronIcoel : IobOpcUaOmron { - #region Protected Fields - - - #endregion Protected Fields - #region Public Constructors + /// + /// Estende l'init della classe base, impiegando il pacchetto Nuget OPC-UA foundation con la + /// gestione specifica per Omron (es ICOEL) https://github.com/OPCFoundation/UA-.NETStandard + /// + /// + /// + public IobOpcUaOmronIcoel(AdapterForm caller, IobConfiguration IOBConf) : base(caller, IOBConf) + { + // inizializzo classe base... + if (!string.IsNullOrEmpty(getOptPar("CHANGE_ODL_MODE"))) + { + CHANGE_ODL_MODE = getOptPar("CHANGE_ODL_MODE"); + } + sendKeyRichiesta = true; + doByteRead = true; + lgInfo($"Avviato IobOpcUaOmronIcoel | encodeReadData: {doByteRead}"); + } + + #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); + } + + /// + /// Effettua vero processing contapezzi + /// + public override void processContapezzi() + { +#if false + if (utils.CRB("enableContapezzi")) + { + // check condizione validazione + if (checkMultiCondition(opcUaParams.condCountEnabled) || opcUaParams.condCountEnabled.checkList.Count == 0) + { + // cerco parametro contapezzi... + string currPzCount = getDataItemValue(opcUaParams.keyPartCount); + + // se ho un contapezzi... processo... + if (!string.IsNullOrEmpty(currPzCount)) + { + int newVal = -1; + bool fatto = Int32.TryParse(currPzCount, out newVal); + + if (fatto) + { + // gestione decremento contapezzi: viene "messo via" solo SE c'è un + // effettivo decremento contapezzi... + if (newVal < contapezziPLC) + { + pzCountResetted = true; + // incremento contatore richiesta + countKeyRichiesta = countKeyRichiesta + 1; + // log + lgInfo("Contapezzi resettato (PLC) --> pzCountResetted = true"); + } + + // salvo nuovo valore contapezziPLC + contapezziPLC = newVal > -1 ? newVal : contapezziPLC; + } + else + { + lgError($"Errore in decodifica valore contapezzi, valore rilevato: {currPzCount}"); + } + } + else + { + lgError("Errore in decodifica valore contapezzi, valore vuoto!"); + } + } + + if (CHANGE_ODL_MODE == "PZCOUNT_RESET") + { + // controllo comunque, se è ZERO il contapezzi, e sul server è maggiore il + // valore x ODL e NON abilitato il trigger reset --> abilito trigger... + if (!pzCountResetted && contapezziPLC == 0 && contapezziIOB > 0) + { + pzCountResetted = true; + // incremento contatore richiesta + countKeyRichiesta = countKeyRichiesta + 1; + // log + lgInfo("Contapezzi resettato (PLC==0 e IOB>PLC) --> pzCountResetted = true"); + } + } + } +#endif + } + + /// + /// 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; + } + + #endregion Public Methods + + #region Internal Methods + + /// + /// Verifica ed invia variazioni DAL FORMATO RAW data (byte[]) --> esplode oggetti e li + /// testa 1:1 + /// + /// + /// + /// + internal override bool checkAndSendRaw(Opc.Ua.Client.MonitoredItem MonIt, byte[] NotifyValue, bool forceSend) + { + bool changed = false; + if (MonIt != null) + { + if (NotifyValue != null && NotifyValue.Length > 0) + { + // verifico variazione "globale" + if (!lastData.Equals(currData)) + { + // effettuo test/invio x ogni info + changed = changed || testSendDataBlock(MonIt.StartNodeId, "Calibratrice_L1", lastData.Calibratrice_L1, currData.Calibratrice_L1, forceSend); + changed = changed || testSendDataBlock(MonIt.StartNodeId, "Calibratrice_L2", lastData.Calibratrice_L2, currData.Calibratrice_L2, forceSend); + changed = changed || testSendDataBlock(MonIt.StartNodeId, "Dewatering_L1", lastData.Dewatering_L1, currData.Dewatering_L1, forceSend); + changed = changed || testSendDataBlock(MonIt.StartNodeId, "Dewatering_L2", lastData.Dewatering_L2, currData.Dewatering_L2, forceSend); + changed = changed || testSendDataBlock(MonIt.StartNodeId, "Elevatore_L1", lastData.Elevatore_L1, currData.Elevatore_L1, forceSend); + changed = changed || testSendDataBlock(MonIt.StartNodeId, "Elevatore_L2", lastData.Elevatore_L2, currData.Elevatore_L2, forceSend); + changed = changed || testSendDataBlock(MonIt.StartNodeId, "ImmergitoreBins_L1", lastData.ImmergitoreBins_L1, currData.ImmergitoreBins_L1, forceSend); + changed = changed || testSendDataBlock(MonIt.StartNodeId, "ImmergitoreBins_L2", lastData.ImmergitoreBins_L2, currData.ImmergitoreBins_L2, forceSend); + changed = changed || testSendDataBlock(MonIt.StartNodeId, "ImmergitoreCasse_L1", lastData.ImmergitoreCasse_L1, currData.ImmergitoreCasse_L1, forceSend); + changed = changed || testSendDataBlock(MonIt.StartNodeId, "ImmergitoreCasse_L2", lastData.ImmergitoreCasse_L2, currData.ImmergitoreCasse_L2, forceSend); + changed = changed || testSendDataBlock(MonIt.StartNodeId, "NastroTaglierina_L1", lastData.NastroTaglierina_L1, currData.NastroTaglierina_L1, forceSend); + changed = changed || testSendDataBlock(MonIt.StartNodeId, "NastroTaglierina_L2", lastData.NastroTaglierina_L2, currData.NastroTaglierina_L2, forceSend); + changed = changed || testSendDataBlock(MonIt.StartNodeId, "Precalibro_L1", lastData.Precalibro_L1, currData.Precalibro_L1, forceSend); + changed = changed || testSendDataBlock(MonIt.StartNodeId, "Precalibro_L2", lastData.Precalibro_L2, currData.Precalibro_L2, forceSend); + changed = changed || testSendDataBlock(MonIt.StartNodeId, "Taglierina_L1", lastData.Taglierina_L1, currData.Taglierina_L1, forceSend); + changed = changed || testSendDataBlock(MonIt.StartNodeId, "Taglierina_L2", lastData.Taglierina_L2, currData.Taglierina_L2, forceSend); + // salvo lastData... + lastData = currData; + } + } + else + { + lgError($"checkAndSend ERROR | MonIt: {MonIt.DisplayName} | NotifyValue Null!!!"); + } + } + else + { + lgError("checkAndSend ERROR: MonIt null"); + } + return changed; + } + + internal override void UA_ref_eh_MonItChange(object sender, opcUaMonitItemChange e) + { + base.UA_ref_eh_MonItChange(sender, e); + } + + #endregion Internal Methods + + #region Protected Properties + /// /// Valore corrente dei dati ICOEL (traduzione JIT da byte[]) /// @@ -250,139 +331,49 @@ namespace IOB_WIN_NEXT } } + /// + /// Indica se abbia stato POWER ON (multicondizione) + /// + protected override bool hasPowerOn + { + get + { + // da rivedere + return true; + //return checkMultiCondition(opcUaParams.condPowerOn); + } + } + + /// + /// Indica se abbia stato MANUAL (condizioni varie, es stopped) + /// + protected override bool isManual + { + get + { + return false; + //return checkMultiCondition(opcUaParams.condManual); + } + } + + /// + /// Indica se abbia stato READY (condizioni varie, es ausiliari OK) + /// + protected override bool isReady + { + get + { + return false; + //return checkMultiCondition(opcUaParams.condReady); + } + } + /// /// Ultima versione validata delle info x confronto /// protected DatiMesIcoel lastData { get; set; } = new DatiMesIcoel(new byte[115]); - internal override void UA_ref_eh_MonItChange(object sender, opcUaMonitItemChange e) - { - base.UA_ref_eh_MonItChange(sender, e); - } - - /// - /// Estende l'init della classe base, impiegando il pacchetto Nuget OPC-UA foundation con la gestione specifica per Omron (es ICOEL) - /// https://github.com/OPCFoundation/UA-.NETStandard - /// - /// - /// - public IobOpcUaOmronIcoel(AdapterForm caller, IobConfiguration IOBConf) : base(caller, IOBConf) - { - // inizializzo classe base... - if (!string.IsNullOrEmpty(getOptPar("CHANGE_ODL_MODE"))) - { - CHANGE_ODL_MODE = getOptPar("CHANGE_ODL_MODE"); - } - sendKeyRichiesta = true; - doByteRead = true; - lgInfo($"Avviato IobOpcUaOmronIcoel | encodeReadData: {doByteRead}"); - } - /// - /// Verifico se salvare e inviare proprietà specificata - /// - /// - protected void testSendProperty(Opc.Ua.Client.MonitoredItem MonIt, string NotifyValue, bool forceSend) - { - DateTime locTStamp = DateTime.Now; - string sVal = ""; - string descr = ""; - descr = itemTranslation("OPC", MonIt.DisplayName); - sVal = $"Change: {locTStamp.ToString()} | descr: {descr} | Id: {MonIt.StartNodeId} | Val: {NotifyValue}"; - lgInfo($"TSP | {sVal}"); - - bool changed = checkSaveValue(MonIt, NotifyValue); - // cerco se non sia un dato filtrato in FLUXLOG... - bool isFiltered = opcUaParams.fluxLogVeto.Contains(MonIt.DisplayName); - if (isFiltered) - { - lgTrace($"TSP | NON ACCODATO sample per {MonIt.DisplayName} - trovato VETO in fluxLogVeto", false); - } - else - { - if (changed || forceSend) - { - accodaFLog(sVal, qEncodeFLog(descr, $"{NotifyValue}")); - } - else - { - lgTrace($"TSP | NON ACCODATO sample per {MonIt.DisplayName} - verifica variazione ha dato esito negativo", false); - } - } - } - - /// - /// effettua verifica del datablock icoel invianod eventualmente i dati variati - /// - /// - /// - /// - /// - /// - /// - protected void testSendDataBlock(NodeId startNodeId, string blockName, MesItemStatus currBlock, MesItemStatus newBlock, bool forceSend) - { - // verifica globale blocchi old/new... - if (!currBlock.Equals(newBlock)) - { - // creo un nuovo monitoredItem se non ci fosse x ogni variabile dell'oggetto... - testSendProperty(new Opc.Ua.Client.MonitoredItem() { DisplayName = $"{blockName}_Stato", NodeClass = NodeClass.Variable, StartNodeId = startNodeId }, $"{newBlock.Stato}", forceSend); - testSendProperty(new Opc.Ua.Client.MonitoredItem() { DisplayName = $"{blockName}_Velocita", NodeClass = NodeClass.Variable, StartNodeId = startNodeId }, $"{newBlock.Velocita}", forceSend); - testSendProperty(new Opc.Ua.Client.MonitoredItem() { DisplayName = $"{blockName}_Termico", NodeClass = NodeClass.Variable, StartNodeId = startNodeId }, $"{newBlock.Termico}", forceSend); - testSendProperty(new Opc.Ua.Client.MonitoredItem() { DisplayName = $"{blockName}_MagnetoTermico", NodeClass = NodeClass.Variable, StartNodeId = startNodeId }, $"{newBlock.MagnetoTermico}", forceSend); - testSendProperty(new Opc.Ua.Client.MonitoredItem() { DisplayName = $"{blockName}_AvariaInverter", NodeClass = NodeClass.Variable, StartNodeId = startNodeId }, $"{newBlock.AvariaInverter}", forceSend); - } - } - - - /// - /// Verifica ed invia variazioni DAL FORMATO RAW data (byte[]) --> esplode oggetti e li testa 1:1 - /// - /// - /// - /// - internal override void checkAndSendRaw(Opc.Ua.Client.MonitoredItem MonIt, byte[] NotifyValue, bool forceSend) - { - if (MonIt != null) - { - if (NotifyValue != null && NotifyValue.Length > 0) - { - - // verifico variazione "globale" - if (!lastData.Equals(currData)) - { - // effettuo test/invio x ogni info - testSendDataBlock(MonIt.StartNodeId, "Calibratrice_L1", lastData.Calibratrice_L1, currData.Calibratrice_L1, forceSend); - testSendDataBlock(MonIt.StartNodeId, "Calibratrice_L2", lastData.Calibratrice_L2, currData.Calibratrice_L2, forceSend); - testSendDataBlock(MonIt.StartNodeId, "Dewatering_L1", lastData.Dewatering_L1, currData.Dewatering_L1, forceSend); - testSendDataBlock(MonIt.StartNodeId, "Dewatering_L2", lastData.Dewatering_L2, currData.Dewatering_L2, forceSend); - testSendDataBlock(MonIt.StartNodeId, "Elevatore_L1", lastData.Elevatore_L1, currData.Elevatore_L1, forceSend); - testSendDataBlock(MonIt.StartNodeId, "Elevatore_L2", lastData.Elevatore_L2, currData.Elevatore_L2, forceSend); - testSendDataBlock(MonIt.StartNodeId, "ImmergitoreBins_L1", lastData.ImmergitoreBins_L1, currData.ImmergitoreBins_L1, forceSend); - testSendDataBlock(MonIt.StartNodeId, "ImmergitoreBins_L2", lastData.ImmergitoreBins_L2, currData.ImmergitoreBins_L2, forceSend); - testSendDataBlock(MonIt.StartNodeId, "ImmergitoreCasse_L1", lastData.ImmergitoreCasse_L1, currData.ImmergitoreCasse_L1, forceSend); - testSendDataBlock(MonIt.StartNodeId, "ImmergitoreCasse_L2", lastData.ImmergitoreCasse_L2, currData.ImmergitoreCasse_L2, forceSend); - testSendDataBlock(MonIt.StartNodeId, "NastroTaglierina_L1", lastData.NastroTaglierina_L1, currData.NastroTaglierina_L1, forceSend); - testSendDataBlock(MonIt.StartNodeId, "NastroTaglierina_L2", lastData.NastroTaglierina_L2, currData.NastroTaglierina_L2, forceSend); - testSendDataBlock(MonIt.StartNodeId, "Precalibro_L1", lastData.Precalibro_L1, currData.Precalibro_L1, forceSend); - testSendDataBlock(MonIt.StartNodeId, "Precalibro_L2", lastData.Precalibro_L2, currData.Precalibro_L2, forceSend); - testSendDataBlock(MonIt.StartNodeId, "Taglierina_L1", lastData.Taglierina_L1, currData.Taglierina_L1, forceSend); - testSendDataBlock(MonIt.StartNodeId, "Taglierina_L2", lastData.Taglierina_L2, currData.Taglierina_L2, forceSend); - // salvo lastData... - lastData = currData; - } - } - else - { - lgError($"checkAndSend ERROR | MonIt: {MonIt.DisplayName} | NotifyValue Null!!!"); - } - } - else - { - lgError("checkAndSend ERROR: MonIt null"); - } - } - - #endregion Public Constructors + #endregion Protected Properties #region Protected Methods @@ -460,150 +451,204 @@ namespace IOB_WIN_NEXT } } - - - /// - /// Indica se abbia stato POWER ON (multicondizione) - /// - protected override bool hasPowerOn - { - get - { - // da rivedere - return true; - //return checkMultiCondition(opcUaParams.condPowerOn); - } - } - - /// - /// Indica se abbia stato MANUAL (condizioni varie, es stopped) - /// - protected override bool isManual - { - get - { - return false; - //return checkMultiCondition(opcUaParams.condManual); - } - } - - /// - /// Indica se abbia stato READY (condizioni varie, es ausiliari OK) - /// - protected override bool isReady - { - get - { - return false; - //return checkMultiCondition(opcUaParams.condReady); - } - } - /// /// Effettua vera scrittura parametri /// /// protected override void plcWriteParams(ref List updatedPar) { + } + /// + /// effettua verifica del datablock icoel invianod eventualmente i dati variati + /// + /// + /// + /// + /// + /// + /// + protected bool testSendDataBlock(NodeId startNodeId, string blockName, MesItemStatus currBlock, MesItemStatus newBlock, bool forceSend) + { + bool changed = false; + // verifica globale blocchi old/new... + if (!currBlock.Equals(newBlock)) + { + // creo un nuovo monitoredItem se non ci fosse x ogni variabile dell'oggetto... + changed = changed || testSendProperty(new Opc.Ua.Client.MonitoredItem() { DisplayName = $"{blockName}_Stato", NodeClass = NodeClass.Variable, StartNodeId = startNodeId }, $"{newBlock.Stato}", forceSend); + changed = changed || testSendProperty(new Opc.Ua.Client.MonitoredItem() { DisplayName = $"{blockName}_Velocita", NodeClass = NodeClass.Variable, StartNodeId = startNodeId }, $"{newBlock.Velocita}", forceSend); + changed = changed || testSendProperty(new Opc.Ua.Client.MonitoredItem() { DisplayName = $"{blockName}_Termico", NodeClass = NodeClass.Variable, StartNodeId = startNodeId }, $"{newBlock.Termico}", forceSend); + changed = changed || testSendProperty(new Opc.Ua.Client.MonitoredItem() { DisplayName = $"{blockName}_MagnetoTermico", NodeClass = NodeClass.Variable, StartNodeId = startNodeId }, $"{newBlock.MagnetoTermico}", forceSend); + changed = changed || testSendProperty(new Opc.Ua.Client.MonitoredItem() { DisplayName = $"{blockName}_AvariaInverter", NodeClass = NodeClass.Variable, StartNodeId = startNodeId }, $"{newBlock.AvariaInverter}", forceSend); + + // spostare sotto in checkAndSendRaw ??? FIXME todo + if (changed) + { + sendDataItemListToServer(startNodeId); + } + } + return changed; + } + + /// + /// Verifico se salvare e inviare proprietà specificata + /// + /// + protected bool testSendProperty(Opc.Ua.Client.MonitoredItem MonIt, string NotifyValue, bool forceSend) + { + bool changed = false; + DateTime locTStamp = DateTime.Now; + string sVal = ""; + string descr = ""; + descr = itemTranslation("OPC", MonIt.DisplayName); + sVal = $"Change: {locTStamp.ToString()} | descr: {descr} | Id: {MonIt.StartNodeId} | Val: {NotifyValue}"; + lgInfo($"TSP | {sVal}"); + + changed = checkSaveValue(MonIt, NotifyValue, false); + // cerco se non sia un dato filtrato in FLUXLOG... + bool isFiltered = opcUaParams.fluxLogVeto.Contains(MonIt.DisplayName); + if (isFiltered) + { + lgTrace($"TSP | NON ACCODATO sample per {MonIt.DisplayName} - trovato VETO in fluxLogVeto", false); + } + else + { + if (changed || forceSend) + { + accodaFLog(sVal, qEncodeFLog(descr, $"{NotifyValue}")); + } + else + { + lgTrace($"TSP | NON ACCODATO sample per {MonIt.DisplayName} - verifica variazione ha dato esito negativo", false); + } + } + return changed; } #endregion Protected Methods + } + + public class MesItemStatus + { + #region Public Constructors + + public MesItemStatus(byte[] rawData) + { + Stato = (IcoelStatus)BitConverter.ToUInt16(rawData, 0); + Velocita = BitConverter.ToUInt16(rawData, 2); + Termico = !rawData.Skip(4).Take(1).FirstOrDefault().Equals(0); + MagnetoTermico = !rawData.Skip(5).Take(1).FirstOrDefault().Equals(0); + AvariaInverter = !rawData.Skip(6).Take(1).FirstOrDefault().Equals(0); + } + + #endregion Public Constructors + + #region Public Properties + + public bool AvariaInverter { get; set; } = false; + public bool MagnetoTermico { get; set; } = false; + public IcoelStatus Stato { get; set; } = 0; + public bool Termico { get; set; } = false; + public UInt16 Velocita { get; set; } = 0; + + #endregion Public Properties #region Public Methods - /// - /// Processo i task richiesti e li elimino dalla coda 1:1 - /// - /// - public override Dictionary executeTasks(Dictionary task2exe) + public override bool Equals(object obj) { - // uso metodo base x ora - return base.executeTasks(task2exe); + // Object is not a GaugeModel instance + if (!(obj is MesItemStatus item)) + return false; + + if (Stato != item.Stato) + return false; + if (Velocita != item.Velocita) + return false; + if (Termico != item.Termico) + return false; + if (MagnetoTermico != item.MagnetoTermico) + return false; + if (AvariaInverter != item.AvariaInverter) + return false; + + return true; + } + + public override int GetHashCode() + { + return base.GetHashCode(); } /// - /// Effettua vero processing contapezzi - /// - public override void processContapezzi() - { -#if false - if (utils.CRB("enableContapezzi")) - { - // check condizione validazione - if (checkMultiCondition(opcUaParams.condCountEnabled) || opcUaParams.condCountEnabled.checkList.Count == 0) - { - // cerco parametro contapezzi... - string currPzCount = getDataItemValue(opcUaParams.keyPartCount); - - // se ho un contapezzi... processo... - if (!string.IsNullOrEmpty(currPzCount)) - { - int newVal = -1; - bool fatto = Int32.TryParse(currPzCount, out newVal); - - if (fatto) - { - // gestione decremento contapezzi: viene "messo via" solo SE c'è un effettivo decremento contapezzi... - if (newVal < contapezziPLC) - { - pzCountResetted = true; - // incremento contatore richiesta - countKeyRichiesta = countKeyRichiesta + 1; - // log - lgInfo("Contapezzi resettato (PLC) --> pzCountResetted = true"); - } - - // salvo nuovo valore contapezziPLC - contapezziPLC = newVal > -1 ? newVal : contapezziPLC; - } - else - { - lgError($"Errore in decodifica valore contapezzi, valore rilevato: {currPzCount}"); - } - } - else - { - lgError("Errore in decodifica valore contapezzi, valore vuoto!"); - } - } - - if (CHANGE_ODL_MODE == "PZCOUNT_RESET") - { - // controllo comunque, se è ZERO il contapezzi, e sul server è maggiore il valore x ODL e NON abilitato il trigger reset --> abilito trigger... - if (!pzCountResetted && contapezziPLC == 0 && contapezziIOB > 0) - { - pzCountResetted = true; - // incremento contatore richiesta - countKeyRichiesta = countKeyRichiesta + 1; - // log - lgInfo("Contapezzi resettato (PLC==0 e IOB>PLC) --> pzCountResetted = true"); - } - } - } -#endif - } - - /// - /// Effettua reset del contapezzi, NON POSSIBILE in questa versione + /// Converte un singolo item in un array di byte per scrittura su PLC S7 /// /// - public override bool resetcontapezziPLC() + public byte[] serialize() { - bool answ = false; - return answ; - } - - /// - /// Effettua IMPOSTAZIONE FORZATA del contapezzi, NON POSSIBILE in questa versione - /// - /// - public override bool setcontapezziPLC(int newPzCount) - { - bool answ = false; + byte[] answ = new byte[7]; + Buffer.BlockCopy(BitConverter.GetBytes((short)Stato), 0, answ, 0, 2); + Buffer.BlockCopy(BitConverter.GetBytes(Velocita), 0, answ, 2, 2); + Buffer.BlockCopy(BitConverter.GetBytes(Termico), 0, answ, 4, 1); + Buffer.BlockCopy(BitConverter.GetBytes(MagnetoTermico), 0, answ, 5, 1); + Buffer.BlockCopy(BitConverter.GetBytes(AvariaInverter), 0, answ, 6, 1); return answ; } #endregion Public Methods } + + public class PlantStatus + { + #region Public Constructors + + public PlantStatus(byte[] rawData) + { + InMarcia = !rawData.Skip(0).Take(1).FirstOrDefault().Equals(0); + InEmergenza = !rawData.Skip(1).Take(1).FirstOrDefault().Equals(0); + InStop = !rawData.Skip(2).Take(1).FirstOrDefault().Equals(0); + + //var valore = BitConverter.ToUInt16(rawData, 0); + + //byte b = rawData.Skip(0).Take(1).FirstOrDefault(); + //InMarcia = (b & (1 << (1 - 1))) != 0; + //InEmergenza = (b & (1 << (2 - 1))) != 0; + //InStop = (b & (1 << (3 - 1))) != 0; ; + } + + #endregion Public Constructors + + #region Public Properties + + public bool InEmergenza { get; set; } = false; + public bool InMarcia { get; set; } = false; + public bool InStop { get; set; } = false; + + #endregion Public Properties + + #region Public Methods + + public override bool Equals(object obj) + { + // Object is not a GaugeModel instance + if (!(obj is PlantStatus item)) + return false; + + if (InMarcia != item.InMarcia) + return false; + if (InEmergenza != item.InEmergenza) + return false; + if (InStop != item.InStop) + return false; + + return true; + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + #endregion Public Methods + } } \ No newline at end of file