using MapoSDK; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Net.NetworkInformation; using static IOB_UT_NEXT.CustomObj; namespace IOB_WIN_NEXT.IobOpc { public class OpcUaMBHCimolai : OpcUaMBH { #region Public Constructors /// /// Estende l'init della classe base, impiegando il pacchetto Nuget OPC-UA foundation con la /// gestione specifica per MBH (es Cimolai, Baglietto) /// https://github.com/OPCFoundation/UA-.NETStandard /// /// /// public OpcUaMBHCimolai(AdapterForm caller, IobConfiguration IOBConf) : base(caller, IOBConf) { lgInfo("Init OpcUa MBH versione Cimolai (Baglietto)"); // inizializzo classe base... if (!string.IsNullOrEmpty(getOptPar("CHANGE_ODL_MODE"))) { CHANGE_ODL_MODE = getOptPar("CHANGE_ODL_MODE"); lgInfo($"CHANGE_ODL_MODE: {CHANGE_ODL_MODE}"); } if (!string.IsNullOrEmpty(getOptPar("VETO_SEND_SNAPSHOT"))) { string strVETO_SEND_SNAPSHOT = getOptPar("VETO_SEND_SNAPSHOT"); int.TryParse(strVETO_SEND_SNAPSHOT, out VETO_SEND_SNAPSHOT); lgInfo($"VETO_SEND_SNAPSHOT: {VETO_SEND_SNAPSHOT}"); } // gestione restart OpcUa client... if (!string.IsNullOrEmpty(getOptPar("ENABLE_CLI_RESTART"))) { bool.TryParse(getOptPar("ENABLE_CLI_RESTART"), out enableCliRestart); } // init lastCurrentMaxElapsed if (!string.IsNullOrEmpty(getOptPar("MAX_ELAPSED_TIME_SEC"))) { int.TryParse(getOptPar("MAX_ELAPSED_TIME_SEC"), out lastCurrentMaxElapsed); } sendKeyRichiesta = true; // controllo se abilitare Processing automatico ODL if (!string.IsNullOrEmpty(getOptPar("AUTO_CHANGE_ODL"))) { bool.TryParse(getOptPar("AUTO_CHANGE_ODL"), out doProcOdl); lgInfo($"AUTO_CHANGE_ODL: {doProcOdl}"); } // controllo se disabilitare check exe mode if (!string.IsNullOrEmpty(getOptPar("EXEMODE_CHECK_BYPASS"))) { bool.TryParse(getOptPar("EXEMODE_CHECK_BYPASS"), out EXEMODE_CHECK_BYPASS); lgInfo($"EXEMODE_CHECK_BYPASS: {EXEMODE_CHECK_BYPASS}"); } } #endregion Public Constructors #region Public Methods /// /// Processo i task richiesti e li elimino dalla coda 2:2 /// /// public override Dictionary executeTasks(Dictionary task2exe) { // uso metodo base x salvare esito scrittura var writeResult = base.executeTasks(task2exe); // aggiungo comportamento custom: se ho impostato nome ricetta (programma) --> imposto // richiesta caricamento se ho richiesto reset o fine lavoro --> imposto azzeramento // esco restituendo risultato scrittura iniziali if (task2exe != null) { // controllo se memMap != null... if (memMap != null) { bool taskOk = false; string taskVal = ""; // cerco task specifici x OMP foreach (var item in task2exe) { taskOk = false; taskVal = ""; // converto richiesta in enum... taskType tName = taskType.nihil; Enum.TryParse(item.Key, out tName); // controllo sulla KEY... switch (tName) { case taskType.setProg: // recupero dati da memMap... if (memMap != null && memMap.mMapWrite != null) { if (memMap.mMapWrite.ContainsKey(item.Key)) { dataConf currMem = memMap.mMapWrite[item.Key]; string addr = currMem.memAddr; taskVal = $"SET task: {item.Key} --> {item.Value} | mem: {currMem.memAddr} - {currMem.size} byte"; // salvo il nuovo valore nella memoria... così prox invio lo trasmetterà memMap.mMapWrite[item.Key].value = item.Value; } else { taskVal = $"NO DATA MEM, SET task: {item.Key} --> {item.Value}"; } } else { taskVal = $"NO MemMap found, SET task: {item.Key} --> {item.Value}"; } // salvo in currProd.. upsertKey(item.Key, item.Value); break; case taskType.startSetup: setFineLotto(); break; case taskType.stopSetup: setInizioProd(); break; case taskType.syncDbData: processDataSync(); break; default: taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | SKIPPED | NO EXEC"; lgInfo($"Chiamata senza processing: taskOk: {taskOk} | taskVal: {taskVal}"); break; } } } else { lgError($"Attenzione! memMap è nullo, non posso eseguire task2exe!"); } } return writeResult; } /// /// Effettua vero processing contapezzi /// public override void processContapezzi() { // non fa nulal (non ha contapezzi) } public override bool resetContapezziPLC() { bool answ = false; #if false try { List nodes2Write = new List(); foreach (var item in opcUaParams.actResetCounter) { WriteValue commWriteVal = new WriteValue(); commWriteVal.NodeId = new NodeId(item.Key); commWriteVal.AttributeId = Attributes.Value; commWriteVal.Value = new DataValue(); commWriteVal.Value.Value = item.Value; nodes2Write.Add(commWriteVal); } // vera scrittura UA_ref.WriteNodes(nodes2Write); answ = true; } catch { } #endif return answ; } #endregion Public Methods #region Protected Fields protected bool EXEMODE_CHECK_BYPASS = false; protected int lastAct = 0; protected bool lastIsInCorso = false; /// /// Ultimo rum mode rilevato x decidere SE inviare variazione... /// protected int lastRunMode = -999; protected int VETO_SEND_SNAPSHOT = 10; protected DateTime vetoSnapshot = DateTime.Now; #endregion Protected Fields #region Protected Enums /// /// Definizione stati travel /// protected enum travelState { Idle = 0, StartOdl, UpLift, Move, DownLift, WaitClose } #endregion Protected Enums #region Protected Properties /// /// Attività corrente (INT) da keyRunMode valore di PLC/DB231/Attivita /// protected int currRunMode { get { int answ = 0; if (!string.IsNullOrEmpty(opcUaParams.keyRunMode)) { string currRun = getDataItemValue(opcUaParams.keyRunMode); if (!string.IsNullOrEmpty(currRun)) { int.TryParse(currRun, out answ); } } return answ; } } protected travelState currTravelState { get; set; } = travelState.Idle; protected DateTime currTravelStateStart { get; set; } = DateTime.Today.AddYears(-1); /// /// Lavorazione in CORSO = da keyExeMode valore di PLC/DB231/InCorso /// protected bool isInCorso { get { bool answ = false; if (!string.IsNullOrEmpty(opcUaParams.keyExeMode)) { string currExe = getDataItemValue(opcUaParams.keyExeMode); if (!string.IsNullOrEmpty(currExe)) { bool.TryParse(currExe, out answ); } } return answ; } } /// /// Indica se abbia stato MANUAL x CIMOLAI /// protected bool isManualCimolai { get { bool answ = false; if (lastAct > 0 && lastAct < 10) { answ = (lastAct % 2 == 1); } return answ; } } /// /// Indica se abbia stato WORKING x CIMOLAI /// protected bool isWorkingCimolai { get { bool answ = false; if (lastAct > 0 && lastAct < 10) { answ = (lastAct % 2 == 0); } return answ; } } #endregion Protected Properties #region Protected Methods /// /// Effettua decodifica aree memoria alla bitmap usata x MAPO /// protected override 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 / setup * B7: emergenza ARMATA (1=ok, 0 = premuta) ---------------------------------------------------- */ // se valido il check ping lo eseguo... altrimenti lo do x buono bool checkPing = !opcUaParams.pingAsPowerOn; string currRun = "N.A."; if (!checkPing) { checkPing = (testPingMachine == IPStatus.Success); } // bit 0 (poweron) imposto a 1 SE pingo + PowerOn=="ON"... bool powerOnOk = checkPing && hasPowerOn; // procedo SOLO SE mi da ping OK... if (checkPing) { B_input = powerOnOk ? 1 : 0; procRunMode(ref currRun); /* ----------------------------------------------------- * CIMOLAI CUSTOM *------------------------------------------------------ * AUX = 2 --> emergenza armata * AUX <> 2 --> emergenza premuta * Aux = 2 + InCorso = 0 --> pronto * Aux = 2 + LastAct in (2,4,6,8) --> LAVORA * * * PLC/DB231/Attivita * 0: Emergenza * 1: Avvio registrazione ricetta * 2: Inizio comando traslazione * 3: Termine comando traslazione * 4: Inizio comando di sterzatura * 5: Termine comando di sterzatura * 6: Inizio comando movimento carrelli * 7: Termine comando movimento carrelli * 8: Inizio comando sollevamento * 9: Termine comando sollevamento * 10: richiesta snapshot parametri * *------------------------------------------------------ * SEMPLIFICAZIONE POST CERTIFICAZIONE *------------------------------------------------------ * * Visto che la rete potrebbe saltare ad intermittenza, conviene gestire in modo semplificato il lavora * torno a condizione work base: AUX = 2, marcia = in corso * * ---------------------------------------------------- */ // controllo emergenza... se zero --> emergenza! if (hasEStopArmed) { B_input += (1 << 7); } else { // resetto last act... lastAct = 0; } // verifico se aggiornare stato LAST ACTION if (lastAct != currRunMode && currRunMode != 0) { // registro solo azioni > 1 e < 10 if (currRunMode > 1 && currRunMode < 10) { // escludo le azioni 4 e 5 (che sono anche in concomitanza con 2-3) if (currRunMode < 4 || currRunMode > 5) { lastAct = currRunMode; } } } // Gestione ODL automatica: se abilitata --> qui con start/stop da impianto... if (doProcOdl) { lgTrace($"inizio process verifica presa in carico PODL | isInCorso: {isInCorso} | lastIsInCorso: {lastIsInCorso} | currProgName {currProgName}"); // se rilevo variazione exe (curr/last) // --> registro richiesta attreazzaggio PODL da info commessa if (lastIsInCorso != isInCorso) { // se 0 --> 1 --> registro if (isInCorso) { lgInfo("--------------------------------------------"); // prendo senza stringa PODL string sPODL = currProgName.Replace("PODL", ""); int idxPODL = 0; int.TryParse(sPODL, out idxPODL); // chiamo richiesta setup PODL... lgInfo($"Inizio trySetupPODL per {idxPODL}"); trySetupPODL(idxPODL); lgInfo("--------------------------------------------"); } else { lgInfo("--------------------------------------------"); lgInfo("Inizio tryCloseODL"); // registro chiusura ODL.. tryCloseCurrODL(); lgInfo("--------------------------------------------"); } // registro exe mode lastIsInCorso = isInCorso; } } // salvo running come = working... isRunning = isWorkingCimolai; // se ho setup if (isWarmUpCoolDown) { B_input += (1 << 6); } // se ho almeno 1 allarme E NON SONO IN AUTO --> ALARM! if (hasError) { B_input += (1 << 3); } if (isWorkingCimolai || isWorking || isInCorso) { // RUN = LAVORA! B_input += (1 << 1); } else { if (!isReady || isManualCimolai || isManual) { // se NON ready --> manual B_input += (1 << 4); } } } // controllo se non ho dati buoni da > lastCurrentMaxElapsed sec --> disconnetto if (adesso.Subtract(lastCurrent).TotalSeconds > lastCurrentMaxElapsed) { lgInfo($"Timeout per mancata comunicazione da oltre {lastCurrentMaxElapsed} sec --> disconnessione adapter OpcUa!"); tryDisconnect(); } // solo se non ho veto check int vFactor = 2; if (vetoCheckStatus < adesso) { lgDebug($"Stato variabili checkPing: {testPingMachine}"); // imposto veto per vetoSeconds... vetoCheckStatus = adesso.AddSeconds(vetoSeconds * vFactor); } // log opzionale! if (verboseLog) { lgDebug($"Trasformazione checkPing: {checkPing} | hasPowerOn: {hasPowerOn} | B_input: {B_input} | currRun = {currRun}"); } } /// /// Recupera da server set di dati specifici x IOB : qui per compilare files CSV /// /// protected override bool iobGetDataFromServer() { bool answ = false; bool okArt = false; bool okDoss = false; bool okPodl = false; bool okLVFasi = false; List listaPODL = new List(); List listaDoss = new List(); List listaArt = new List(); List anagLVFasi = new List(); Dictionary dictAF = new Dictionary(); // recupera dati da server tramite chiamate REST a MP/IO/IOB... var rawListArt = callUrl(urlGetCurrArt, false); var rawListDOSS = callUrl(urlGetCurrDOSS, false); var rawListPODL = callUrl(urlGetCurrPODL, false); var rawLVFasi = callUrl(urlGetListValFasiPodl, false); if (!string.IsNullOrEmpty(rawListArt)) { try { listaArt = JsonConvert.DeserializeObject>(rawListArt); okArt = listaArt.Count > 0; } catch (Exception exc) { lg.Error($"Errore: chiamata elenco ART ha restituito errore{Environment.NewLine}{exc}"); } } else { lg.Error($"Errore: chiamata elenco ART ({urlGetCurrArt}) ha restituito valore vuoto"); } if (!string.IsNullOrEmpty(rawListDOSS)) { try { listaDoss = JsonConvert.DeserializeObject>(rawListDOSS); okDoss = listaDoss.Count > 0; } catch (Exception exc) { lg.Error($"Errore: chiamata elenco DOSSIER ha restituito errore{Environment.NewLine}{exc}"); } } else { lg.Error($"Errore: chiamata elenco DOSSIER ({urlGetCurrDOSS}) ha restituito valore vuoto"); } if (!string.IsNullOrEmpty(rawListPODL)) { try { listaPODL = JsonConvert.DeserializeObject>(rawListPODL); okPodl = listaPODL.Count > 0; } catch (Exception exc) { lg.Error($"Errore: chiamata elenco PODL ha restituito errore{Environment.NewLine}{exc}"); } } else { lg.Error($"Errore: chiamata elenco PODL ({urlGetCurrPODL}) ha restituito valore vuoto"); } if (!string.IsNullOrEmpty(rawLVFasi)) { try { anagLVFasi = JsonConvert.DeserializeObject>(rawLVFasi); dictAF = anagLVFasi.ToDictionary(x => x.value, x => x.label); okLVFasi = listaArt.Count > 0; } catch (Exception exc) { lg.Error($"Errore: chiamata elenco ListVal ha restituito errore{Environment.NewLine}{exc}"); } } else { lg.Error($"Errore: chiamata elenco ListVal ({urlGetListValFasiPodl}) ha restituito valore vuoto"); } answ = okPodl && okDoss && okArt && okLVFasi; if (answ) { // predispongo dati PODL ListaJobs = listaPODL .Select(x => new JobRow() { Matricola = x.CodArticolo, Commessa = $"PODL{x.IdxPromessa:00000000}", Articolo = x.CodArticolo, Descrizione = x.CodArticolo, DataIns = $"{x.InsertDate:dd/MM/yyyy}", OraIns = $"{x.InsertDate:HH:mm}", Lavorazione = $"{getLV(dictAF, x.KeyRichiesta)} {x.Note}".Trim() }) .ToList(); // predispongo dati articoli ListaArticoli = listaArt .Select(a => new ArtRow() { Matricola = a.CodArticolo, Articolo = a.Disegno, Descrizione = a.DescArticolo, LimiteVel = 100 }) .Distinct() .ToList(); // completo con dati DOSSIER foreach (var item in ListaArticoli) { string codArt = item.Matricola; var currDoss = listaDoss.Where(x => x.CodArticolo == codArt).FirstOrDefault(); if (currDoss != null) { DossierFluxLogDTO resultSet = JsonConvert.DeserializeObject(currDoss.Valore); // traduco AD MENTULAM... item.Peso_01 = getFluxValInt(resultSet, "OPC_PLC/DB231/peso1"); item.Peso_02 = getFluxValInt(resultSet, "OPC_PLC/DB231/peso2"); item.Peso_03 = getFluxValInt(resultSet, "OPC_PLC/DB231/peso3"); item.Peso_04 = getFluxValInt(resultSet, "OPC_PLC/DB231/peso4"); item.PosizCarrello_01 = getFluxValInt(resultSet, "OPC_PLC/DB231/PosCarr1"); item.PosizCarrello_02 = getFluxValInt(resultSet, "OPC_PLC/DB231/PosCarr2"); item.PosizCarrello_03 = getFluxValInt(resultSet, "OPC_PLC/DB231/PosCarr3"); item.PosizCarrello_04 = getFluxValInt(resultSet, "OPC_PLC/DB231/PosCarr4"); } } } return answ; } /// /// Effettua sync dati /// protected override void processDataSync() { lgInfo("--------------------------------------------"); lgInfo($"executeTasks --> syncDbData --> processDataSync"); lgInfo("--------------------------------------------"); // effettua sync fixme todo: test scrittura file csv... var taskGet = iobGetDataFromServer(); var taskWrite = iobWriteLocalCSV(); var taskSend = iobSendFTP(""); } protected override void procRunMode(ref string currRun) { // variabili RUN... se richiesto invio runMode if (opcUaParams.runModeSend) { currRun = $"{currRunMode}"; // effettuo processing SPECIFICO currRunMode x gestione snapshot... if (currRunMode == 10) { if (DateTime.Now > vetoSnapshot) { lgInfo("--------------------------------------------"); callUrl(urlTakeSnapshot, false); // blocco snapshot x VETO_SEND_SNAPSHOT sec... vetoSnapshot = DateTime.Now.AddSeconds(VETO_SEND_SNAPSHOT); lgInfo($"Effettuata richiesta salvataggio snapshot, impostato veto nuova chiamata a {vetoSnapshot}"); lgInfo("--------------------------------------------"); } else { lgInfo($"NON effettuo salvataggio snapshot perché veto attivo fino a {vetoSnapshot}"); } } else { // aggiorno veto x snapshot vetoSnapshot = DateTime.Now; } // invio del valore RUN MODE come FLog, SE variato... if (!string.IsNullOrEmpty(currRun)) { if (currReadErrors != lastRunMode) { // aggiorno last... lastRunMode = currRunMode; // se ho valore --> invio string sVal = ""; string descr = $"RunModeVal"; DateTime locTStamp = DateTime.Now; sVal = $"Change 04: {locTStamp.ToString()} | descr: {descr} | Id: {opcUaParams.keyExeMode} | Val: {currRun}"; // cerco se sia un dato/valore in veto bool hasVetoLK = false; bool hasVetoLKV = false; hasVetoLK = opcUaParams.fluxLogKeyValVeto.ContainsKey(descr); if (hasVetoLK) { // se c'è controllo il valore... var valList = opcUaParams.fluxLogKeyValVeto[descr]; hasVetoLKV = valList.Contains(currRun); } if (hasVetoLKV) { lgTrace($"NON ACCODATO sample: veto trovato per {descr}/{currRun} in fluxLogKeyValVeto ", false); } else { accodaFLog(sVal, qEncodeFLog(descr, currRun)); // se richiesto provo a tradurre if (opcUaParams.runModeTrad) { string RunModeDescr = itemTranslation("RunMode", currRun); descr = $"RunMode"; accodaFLog(sVal, qEncodeFLog(descr, RunModeDescr)); } } } } } //verifica preliminare durata minima stato DateTime adesso = DateTime.Now; bool canChange = adesso.Subtract(currTravelStateStart).TotalSeconds > opcUaParams.minSecStatusDuration; // gestione dei macro-stati del travel... SOLO se in esecuzione da travel... BYPASS x ora!!! if (isInCorso || EXEMODE_CHECK_BYPASS) { double pesoTot = 0; // può cambiare stato se per ALMENO minSecStatus è rimasto nello status corrente... if (canChange) { lgTrace($"start checkExe | TRAVEL STATE: {currTravelState}"); // STEP 1: verifico se sia iniziato caricamento... if (currTravelState == travelState.Idle) { if (currIdxODL > 0) { currTravelState = travelState.StartOdl; currTravelStateStart = adesso; lgInfo("--------------------------------------------"); lgInfo("TRAVEL STATE: StartOdl"); lgInfo("--------------------------------------------"); } } // sono nell'esecuzione ODL, controllo il resto else { // nb: vedere se usare isWorkingCimolai / isManualCimolai // verifico il valore della variabile calcolata del PESO totale... if (dataItemMem.ContainsKey("PesoTot")) { pesoTot = getDataItemValueDouble("PesoTot"); } // ...oppuresommo direttamente else { pesoTot += getDataItemValueDouble("PLC/DB231/peso1"); pesoTot += getDataItemValueDouble("PLC/DB231/peso2"); pesoTot += getDataItemValueDouble("PLC/DB231/peso3"); pesoTot += getDataItemValueDouble("PLC/DB231/peso4"); } lgTrace($"TState: Test 03 | pesoTot: {pesoTot}"); // STEP 2: verifico se sia iniziato caricamento... if (currTravelState == travelState.StartOdl) { if (pesoTot > 0) { currTravelState = travelState.UpLift; currTravelStateStart = adesso; lgInfo("--------------------------------------------"); lgInfo("TRAVEL STATE: UpLift"); lgInfo("--------------------------------------------"); } } // STEP 3: verifico se sia terminato caricamento... else if (currTravelState == travelState.UpLift) { if (pesoTot == 0) { currTravelState = travelState.Move; currTravelStateStart = adesso; lgInfo("--------------------------------------------"); lgInfo("TRAVEL STATE: Move"); lgInfo("--------------------------------------------"); } } // STEP 4: verifico se sia terminato movimento... else if (currTravelState == travelState.Move) { if (pesoTot > 0) { currTravelState = travelState.DownLift; currTravelStateStart = adesso; lgInfo("--------------------------------------------"); lgInfo("TRAVEL STATE: DownLift"); lgInfo("--------------------------------------------"); } } // STEP 5: verifico se sia terminato scaricamento... else if (currTravelState == travelState.DownLift) { if (pesoTot == 0) { currTravelState = travelState.WaitClose; currTravelStateStart = adesso; lgInfo("--------------------------------------------"); lgInfo("TRAVEL STATE: WaitClose"); lgInfo("--------------------------------------------"); } } } } else { if (canChange) { lgTrace("TRAVEL STATE | Nessuna gestione status travel: inCorso = 0"); } } // ora verifico solo condizione finale x eventuale chiusura... bool canSendAskClose = adesso.Subtract(currTravelStateStart).TotalSeconds > opcUaParams.minSecFinalWait; if (canSendAskClose && currTravelState == travelState.WaitClose) { // STEP 6: verifico se sia tolto ODL e quindi ha già chiuso... if (currIdxODL == 0) { currTravelState = travelState.Idle; currTravelStateStart = adesso; lgInfo("--------------------------------------------"); lgInfo("TRAVEL STATE: Idle"); lgInfo("--------------------------------------------"); } // altrimenti manda richiesta chiusura else { // chiama richiesta chiusura x utente tryAskCloseCurrODL(); // resetta contatore x nuova richeista finale eventuale... currTravelStateStart = adesso; } } } else { if (canChange) { // altrimenti forzo a "Idle" state currTravelState = travelState.Idle; currTravelStateStart = adesso; // trace opzionale lgTrace($"TRAVEL STATE: {currTravelState}"); } } } #endregion Protected Methods #region Private Fields /// /// abilitata gestione ODL da impianto /// private bool doProcOdl = false; #endregion Private Fields #region Private Methods /// /// Azioni specifiche x indicare fine lotto di produzione /// /// private bool setFineLotto() { bool answ = false; #if false try { List nodes2Write = new List(); foreach (var item in opcUaParams.actStopProd) { WriteValue commWriteVal = new WriteValue(); commWriteVal.NodeId = new NodeId(item.Key); commWriteVal.AttributeId = Attributes.Value; commWriteVal.Value = new DataValue();\ commWriteVal.Value.Value = item.Value; nodes2Write.Add(commWriteVal); } // vera scrittura UA_ref.WriteNodes(nodes2Write); answ = true; } catch { } #endif return answ; } /// /// Azioni specifiche x iniziare produzione (impostazione ricetta) /// /// private bool setInizioProd() { bool answ = false; #if false try { List nodes2Write = new List(); foreach (var item in opcUaParams.actSetRecipe) { WriteValue commWriteVal = new WriteValue(); commWriteVal.NodeId = new NodeId(item.Key); commWriteVal.AttributeId = Attributes.Value; commWriteVal.Value = new DataValue(); commWriteVal.Value.Value = item.Value; nodes2Write.Add(commWriteVal); } // vera scrittura UA_ref.WriteNodes(nodes2Write); answ = true; } catch { } #endif return answ; } #endregion Private Methods } }