diff --git a/IOB-WIN-NEXT/DATA/CONF/Tend_RAMA_100_47.json b/IOB-WIN-NEXT/DATA/CONF/Tend_RAMA_100_47.json index d8d36f8e..5494ae87 100644 --- a/IOB-WIN-NEXT/DATA/CONF/Tend_RAMA_100_47.json +++ b/IOB-WIN-NEXT/DATA/CONF/Tend_RAMA_100_47.json @@ -813,238 +813,238 @@ "OPC_SP_FabSpeed_ChainSpeed": { "name": "OPC_SP_FabSpeed_ChainSpeed", "description": "Chain Speed", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fabric Transport\"#GEN.Float.0", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fabric Transport\"#Chain Speed - m_min", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_FabSpeed_TOffOverFeed": { "name": "OPC_SP_FabSpeed_TOffOverFeed", "description": "TakeOff Overfeeding", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fabric Transport\"#GEN.Float.24", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fabric Transport\"#Take Off Overfeeding - %", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_Spind_WidthMast": { "name": "OPC_SP_Spind_WidthMast", "description": "Width Master", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Width\"#GEN.Float.0", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Width\"#Width Master - cm", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_Spind_WidthV1": { "name": "OPC_SP_Spind_WidthV1", "description": "Diff V1", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Width\"#GEN.Float.4", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Width\"#Differentiation V1 - cm", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_Spind_WidthV2": { "name": "OPC_SP_Spind_WidthV2", "description": "Diff V2", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Width\"#GEN.Float.8", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Width\"#Differentiation V2 - cm", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_Spind_WidthV3": { "name": "OPC_SP_Spind_WidthV3", "description": "Diff V3", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Width\"#GEN.Float.12", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Width\"#Differentiation V3 - cm", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_Spind_WidthV4": { "name": "OPC_SP_Spind_WidthV4", "description": "Diff V4", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Width\"#GEN.Float.16", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Width\"#Differentiation V4 - cm", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_Spind_WidthV5": { "name": "OPC_SP_Spind_WidthV5", "description": "Diff V5", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Width\"#GEN.Float.20", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Width\"#Differentiation V5 - cm", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_Spind_WidthV6": { "name": "OPC_SP_Spind_WidthV6", "description": "Diff V6", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Width\"#GEN.Float.24", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Width\"#Differentiation V6 - cm", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_Spind_WidthV7": { "name": "OPC_SP_Spind_WidthV7", "description": "Diff V7", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Width\"#GEN.Float.28", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Width\"#Differentiation V7 - cm", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_OvenSpeedCoolCell1": { "name": "OPC_SP_OvenSpeedCoolCell1", "description": "Cooling Fan", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#GEN.Float.0", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#Cooling Fan 1 - %", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_OvenSpeedExFan1": { "name": "OPC_SP_OvenSpeedExFan1", "description": "Exhaust Fan 1", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#GEN.Float.8", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#Exaust Fan 1 - %", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_OvenSpeedExFan2": { "name": "OPC_SP_OvenSpeedExFan2", "description": "Exhaust Fan 2", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#GEN.Float.12", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#Exaust Fan 2 - %", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_OvenSpeedFan101": { "name": "OPC_SP_OvenSpeedFan101", "description": "Fan V1", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#GEN.Float.20", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#Fan V1 - %", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_OvenSpeedFan101A": { "name": "OPC_SP_OvenSpeedFan101A", "description": "Fan V1A", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#GEN.Float.24", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#Fan V1A - %", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_OvenSpeedFan103": { "name": "OPC_SP_OvenSpeedFan103", "description": "Fan V2", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#GEN.Float.28", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#Fan V2 - %", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_OvenSpeedFan103A": { "name": "OPC_SP_OvenSpeedFan103A", "description": "Fan V2A", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#GEN.Float.32", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#Fan V2A - %", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_OvenSpeedFan105": { "name": "OPC_SP_OvenSpeedFan105", "description": "Fan V3", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#GEN.Float.36", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#Fan V3 - %", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_OvenSpeedFan105A": { "name": "OPC_SP_OvenSpeedFan105A", "description": "Fan V3A", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#GEN.Float.40", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#Fan V3A - %", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_OvenSpeedFan107": { "name": "OPC_SP_OvenSpeedFan107", "description": "Fan V4", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#GEN.Float.44", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#Fan V4 - %", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_OvenSpeedFan107A": { "name": "OPC_SP_OvenSpeedFan107A", "description": "Fan V4A", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#GEN.Float.48", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#Fan V4A - %", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_OvenSpeedFan109": { "name": "OPC_SP_OvenSpeedFan109", "description": "Fan V5", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#GEN.Float.52", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#Fan V5 - %", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_OvenSpeedFan109A": { "name": "OPC_SP_OvenSpeedFan109A", "description": "Fan V5A", - "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#GEN.Float.56", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Fans\"#Fan V5A - %", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_Temp_Burner1": { "name": "OPC_SP_Temp_Burner1", "description": "Burner 1", - "memAddr": "\"Recipe_Table_DB_RW\".\"Temperature\"#GEN.Float.0", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Temperature\"#Burner 1 Temperature - °C", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_Temp_Burner2": { "name": "OPC_SP_Temp_Burner2", "description": "Burner 2", - "memAddr": "\"Recipe_Table_DB_RW\".\"Temperature\"#GEN.Float.4", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Temperature\"#Burner 2 Temperature - °C", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_Temp_Burner3": { "name": "OPC_SP_Temp_Burner3", "description": "Burner 3", - "memAddr": "\"Recipe_Table_DB_RW\".\"Temperature\"#GEN.Float.8", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Temperature\"#Burner 3 Temperature - °C", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_Temp_Burner4": { "name": "OPC_SP_Temp_Burner4", "description": "Burner 4", - "memAddr": "\"Recipe_Table_DB_RW\".\"Temperature\"#GEN.Float.12", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Temperature\"#Burner 4 Temperature - °C", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_Temp_Burner5": { "name": "OPC_SP_Temp_Burner5", "description": "Burner 5", - "memAddr": "\"Recipe_Table_DB_RW\".\"Temperature\"#GEN.Float.16", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Temperature\"#Burner 5 Temperature - °C", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_Temp_Burner6": { "name": "OPC_SP_Temp_Burner6", "description": "Burner 6", - "memAddr": "\"Recipe_Table_DB_RW\".\"Temperature\"#GEN.Float.20", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Temperature\"#Burner 6 Temperature - °C", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_Temp_Burner7": { "name": "OPC_SP_Temp_Burner7", "description": "Burner 7", - "memAddr": "\"Recipe_Table_DB_RW\".\"Temperature\"#GEN.Float.24", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Temperature\"#Burner 7 Temperature - °C", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_Temp_Burner8": { "name": "OPC_SP_Temp_Burner8", "description": "Burner 8", - "memAddr": "\"Recipe_Table_DB_RW\".\"Temperature\"#GEN.Float.28", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Temperature\"#Burner 8 Temperature - °C", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_Temp_Burner9": { "name": "OPC_SP_Temp_Burner9", "description": "Burner 9", - "memAddr": "\"Recipe_Table_DB_RW\".\"Temperature\"#GEN.Float.32", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Temperature\"#Burner 9 Temperature - °C", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_Termoset_ThermoTime": { "name": "OPC_SP_Termoset_ThermoTime", "description": "Thermoset Time", - "memAddr": "\"Recipe_Table_DB_RW\".\"Thermoset\"#GEN.Float.0", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Thermoset\"#Thermoset Time", "tipoMem": "FloatABCD", "factor": 1 }, "OPC_SP_Termoset_ThermoTemp": { "name": "OPC_SP_Termoset_ThermoTemp", "description": "Thermoset Temperature", - "memAddr": "\"Recipe_Table_DB_RW\".\"Thermoset\"#GEN.Float.4", + "memAddr": "ns=3;s=\"Recipe_Table_DB_RW\".\"Thermoset\"#Thermoset Temperature", "tipoMem": "FloatABCD", "factor": 1 } diff --git a/IOB-WIN-NEXT/DATA/IobOpcUaClient.Config.xml b/IOB-WIN-NEXT/DATA/IobOpcUaClient.Config.xml index e4d2b0b7..17669aa0 100644 --- a/IOB-WIN-NEXT/DATA/IobOpcUaClient.Config.xml +++ b/IOB-WIN-NEXT/DATA/IobOpcUaClient.Config.xml @@ -55,14 +55,14 @@ - 60000 + 600000 opc.tcp://{0}:4840 http://{0}:52601/UADiscovery http://{0}/UADiscovery/Default.svc - 10000 + 120000 diff --git a/IOB-WIN-NEXT/Iob/Generic.cs b/IOB-WIN-NEXT/Iob/Generic.cs index 4b31087f..630c5b69 100644 --- a/IOB-WIN-NEXT/Iob/Generic.cs +++ b/IOB-WIN-NEXT/Iob/Generic.cs @@ -635,6 +635,26 @@ namespace IOB_WIN_NEXT.Iob } } + /// + /// URL per recupero PODL NEXT = ATTIVABILI x macchina... + /// + public string urlGetActPODL + { + get + { + string answ = ""; + try + { + answ = $@"{cIobConf.serverData.TRANSP}://{cIobConf.serverData.MPIP}{cIobConf.serverData.MPURL}{cIobConf.serverData.CMDALIVE}/getPOdlAct/{cIobConf.codIOB}"; + } + catch (Exception exc) + { + lgError(exc, "Errore in composizione urlGetActPODL"); + } + return answ; + } + } + /// /// URL per recupero ARTICOLI correnti x macchina... /// @@ -696,8 +716,9 @@ namespace IOB_WIN_NEXT.Iob } /// - /// URL per recupero PODL correnti x macchina... + /// URL per recupero PODL ATTIVABILI (quindi correnti in senso di pronti) x macchina... /// + [Obsolete("Metodo obsoleto (dubbia interpretazione): sostituire con urlGetNextPODL se si vogliono PROSSIMI POdl attivabili (significato originale) o con urlGetActPODL x il POdl attualmente in produzione (per casi come Rama OPC-UA Siemens)")] public string urlGetCurrPODL { get @@ -735,6 +756,26 @@ namespace IOB_WIN_NEXT.Iob } } + /// + /// URL per recupero PODL NEXT = ATTIVABILI x macchina... + /// + public string urlGetNextPODL + { + get + { + string answ = ""; + try + { + answ = $@"{cIobConf.serverData.TRANSP}://{cIobConf.serverData.MPIP}{cIobConf.serverData.MPURL}{cIobConf.serverData.CMDALIVE}/getPOdlNext/{cIobConf.codIOB}"; + } + catch (Exception exc) + { + lgError(exc, "Errore in composizione urlGetNextPODL"); + } + return answ; + } + } + /// /// URL per recupero traduzione CodArt in numerico... /// @@ -4113,8 +4154,11 @@ namespace IOB_WIN_NEXT.Iob currProdData.Add(chiave, valore); done = true; } - // salvo in redis... - redisMan.redSaveHashDict(rKeyCurrProdData, currProdData); + if (done) + { + // salvo in redis... + redisMan.redSaveHashDict(rKeyCurrProdData, currProdData); + } return done; } diff --git a/IOB-WIN-NEXT/IobOpc/OpcUa.cs b/IOB-WIN-NEXT/IobOpc/OpcUa.cs index 0db84258..6a839703 100644 --- a/IOB-WIN-NEXT/IobOpc/OpcUa.cs +++ b/IOB-WIN-NEXT/IobOpc/OpcUa.cs @@ -3,6 +3,7 @@ using MapoSDK; using Newtonsoft.Json; using NLog.Time; using Opc.Ua; +using Opc.Ua.Client; using Opc.Ua.Configuration; using S7.Net.Types; using System; @@ -14,6 +15,7 @@ using System.Security.AccessControl; using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Web.Util; using System.Windows.Forms; using static IOB_UT_NEXT.ByteDataConverter; @@ -172,7 +174,7 @@ namespace IOB_WIN_NEXT.IobOpc string varDef = opcUaParams.keyPartCount.Substring(iPos + 1); // recupero byte byte[] bArray = getDataItemValueBytes(kRef); - answ = ConvBArr(bArray, varDef, opcUaParams.memDelim); + answ = ConvFromBArr(bArray, varDef, opcUaParams.memDelim); } else { @@ -483,7 +485,7 @@ namespace IOB_WIN_NEXT.IobOpc string varDef = opcUaParams.keyPartCount.Substring(iPos + 1); // recupero byte byte[] bArray = getDataItemValueBytes(kRef); - currPzCount = ConvBArr(bArray, varDef, opcUaParams.memDelim); + currPzCount = ConvFromBArr(bArray, varDef, opcUaParams.memDelim); } else { @@ -912,11 +914,11 @@ namespace IOB_WIN_NEXT.IobOpc var rByte = dataItemMem[uuid].rawByte; int iPos = item.Value.memAddr.IndexOf(opcUaParams.kvDelim); string varDef = item.Value.memAddr.Substring(iPos + 1); - string convVal = ConvBArr(rByte, varDef, opcUaParams.memDelim); + string convVal = ConvFromBArr(rByte, varDef, opcUaParams.memDelim); // verifica SE ci sia deadBand da validare... if (valueOverDBand(item.Key, convVal)) { - // metto in array currData... + // metto in array memData... if (upsertKey(item.Key, convVal)) { // accodo se variato! @@ -946,11 +948,11 @@ namespace IOB_WIN_NEXT.IobOpc var rByte = dataItemMem[uuid].rawByte; int iPos = item.Value.memAddr.IndexOf(opcUaParams.kvDelim); string varDef = item.Value.memAddr.Substring(iPos + 1); - string convVal = ConvBArr(rByte, varDef, opcUaParams.memDelim); + string convVal = ConvFromBArr(rByte, varDef, opcUaParams.memDelim); // verifica SE ci sia deadBand da validare... if (valueOverDBand(item.Key, convVal)) { - // metto in array currData... + // metto in array memData... if (upsertKey(item.Key, convVal)) { // accodo se variato! @@ -1735,7 +1737,7 @@ namespace IOB_WIN_NEXT.IobOpc try { var wrapVal = ((DataValue)obj).WrappedValue; - //rawByte = ObjectToByteArray(rawVal); + //rawByte = ObjectToByteArray(sVal); var bodyVal = ((Opc.Ua.ExtensionObject)wrapVal.Value).Body; rawByte = (byte[])bodyVal; } @@ -1744,6 +1746,31 @@ namespace IOB_WIN_NEXT.IobOpc } return rawByte; } +#if false + protected DataValue setByteRaw(DataValue obj, byte[] newData, int startPos) + { + if (obj != null) + { + byte[] rawByte = new byte[1]; + if (obj != null) + { + try + { + var wrapVal = ((DataValue)obj).WrappedValue; + var bodyVal = ((Opc.Ua.ExtensionObject)wrapVal.Value).Body; + rawByte = (byte[])bodyVal; + // sostituisco + Array.Copy(newData, 0, rawByte, startPos, newData.Length); + // aggiorno oggetto originale... + ((Opc.Ua.ExtensionObject)wrapVal.Value).Body = rawByte; + } + catch + { } + } + } + return obj; + } +#endif /// /// Effettua traduzione ITEM da LUT parametrica (key: tipo+id) del file di conf, se non @@ -1871,7 +1898,7 @@ namespace IOB_WIN_NEXT.IobOpc { try { - currVal = UA_ref.ReadNode(item.StartNodeId); + currVal = UA_ref.ReadNodeString(item.StartNodeId); changed = checkAndSend(item, currVal, true); if (changed) { @@ -1895,7 +1922,7 @@ namespace IOB_WIN_NEXT.IobOpc // controllo read error ed eventuale disconnect if (readErrorCurr > readErrorMax) { - lgError($"Effettuo disconnessione x superamento errori lettura UA_ref.ReadNode, sleetp 15 sec"); + lgError($"Effettuo disconnessione x superamento errori lettura UA_ref.ReadNodeString, sleetp 15 sec"); tryDisconnect(); Thread.Sleep(readErrorSleepTime); readErrorCurr = 0; @@ -1984,7 +2011,7 @@ namespace IOB_WIN_NEXT.IobOpc /// Stringa definizione variabile /// Separatore nella definizione /// - private string ConvBArr(byte[] bArray, string varDef, char pSep) + private string ConvFromBArr(byte[] bArray, string varDef, char pSep) { string answ = ""; // converto prendendo solo parte richiesta... @@ -2093,6 +2120,123 @@ namespace IOB_WIN_NEXT.IobOpc return answ; } + /// + /// Conversione da valore string a corrispettivo byteArray nel tipo obj richiesto + /// + /// Valore da inviare come string + /// Stringa definizione variabile + /// Separatore nella definizione + /// + private byte[] ConvToBArr(string sVal, string varDef, char pSep) + { + byte[] answ = new byte[1]; + // converto prendendo solo parte richiesta... + if (!string.IsNullOrEmpty(sVal)) + { + // splitto in primis la definizione + MemStruct currMem = new MemStruct(varDef, pSep); + + // S7.Double.B0 ora eseguo riconoscimento (da portare in base? FixMe ToDo...) + if (currMem.Product == "S7") + { +#if false + // converto! + plcDataType tVal = plcDataType.Int; + Enum.TryParse(currMem.Type, out tVal); + // ora gestisco seconda parte... + switch (tVal) + { + case plcDataType.Boolean: + var boolVal = S7.Net.Types.Boolean.GetValue(bArray[currMem.Idx], currMem.Num); + answ = $"{boolVal}"; + break; + + case plcDataType.Int: + var intVal = S7.Net.Types.Int.FromByteArray(bArray.Skip(currMem.Idx).Take(2).ToArray()); + answ = $"{intVal}"; + break; + //case plcDataType.IntLH: + // break; + case plcDataType.DInt: + var dIntVal = S7.Net.Types.Int.FromByteArray(bArray.Skip(currMem.Idx).Take(4).ToArray()); + answ = $"{dIntVal}"; + break; + //case plcDataType.DIntLH: + // break; + case plcDataType.Word: + var wordVal = S7.Net.Types.Word.FromByteArray((bArray.Skip(currMem.Idx).Take(2).ToArray())); + answ = $"{wordVal}"; + break; + + case plcDataType.DWord: + var dWordVal = S7.Net.Types.Word.FromByteArray((bArray.Skip(currMem.Idx).Take(4).ToArray())); + answ = $"{dWordVal}"; + break; + + case plcDataType.Real: + var dblVal = S7.Net.Types.Double.FromByteArray(bArray.Skip(currMem.Idx).Take(4).ToArray()); + answ = $"{dblVal:N3}"; + break; + //case plcDataType.RealHL: + // break; + //case plcDataType.RealLH: + // break; + case plcDataType.String: + answ = S7.Net.Types.String.FromByteArray(bArray.Skip(currMem.Idx).Take(currMem.Num).ToArray()); + break; + //case plcDataType.HLPInt: + // break; + //case plcDataType.FloatABCD: + // break; + //case plcDataType.FloatBADC: + // break; + //case plcDataType.FloatCDAB: + // break; + //case plcDataType.FloatDCBA: + // break; + //case plcDataType.BitMap: + // break; + //case plcDataType.Byte: + // break; + //case plcDataType.UInt: + // break; + //case plcDataType.UDInt: + // break; + default: + break; + } +#endif + } + else if (currMem.Product == "GEN") + { + // ora gestisco seconda parte... + switch (currMem.Type) + { + case "Byte": + answ = new byte[1]; + bool bVal = false; + bool.TryParse(sVal, out bVal); + answ[0] = bVal ? (byte)1 : (byte)0; + break; + + case "Float": + float fVal = 0; + float.TryParse(sVal, out fVal); + answ = BitConverter.GetBytes(fVal); + break; + + default: + break; + } + } + else + { + // gestire casi NON S7... + } + } + return answ; + } + /// /// Vera connessione ad OpcUa /// @@ -2286,7 +2430,7 @@ namespace IOB_WIN_NEXT.IobOpc { try { - currVal = UA_ref.ReadNode(item.StartNodeId); + currVal = UA_ref.ReadNodeString(item.StartNodeId); changed = checkAndSend(item, currVal, true); if (readErrorCurr > 0) { @@ -2295,7 +2439,7 @@ namespace IOB_WIN_NEXT.IobOpc } catch (Exception exc) { - lgError($"Eccezione durante UA_ref.ReadNode per {item.StartNodeId}{Environment.NewLine}{exc}"); + lgError($"Eccezione durante UA_ref.ReadNodeString per {item.StartNodeId}{Environment.NewLine}{exc}"); readErrorCurr++; } } @@ -2323,7 +2467,7 @@ namespace IOB_WIN_NEXT.IobOpc // controllo read error ed eventuale disconnect if (readErrorCurr > readErrorMax) { - lgError($"Effettuo disconnessione x superamento errori lettura UA_ref.ReadNode, sleep {readErrorSleepTime}msec"); + lgError($"Effettuo disconnessione x superamento errori lettura UA_ref.ReadNodeString, sleep {readErrorSleepTime}msec"); tryDisconnect(); Thread.Sleep(readErrorSleepTime); readErrorCurr = 0; @@ -2418,12 +2562,196 @@ namespace IOB_WIN_NEXT.IobOpc /// private void UA_ref_eh_nullExceed(object sender, int e) { - lgError($"Effettuo disconnessione x superamento letture UA_ref.ReadNode a NULL ({e}), sleep {2 * readErrorSleepTime}msec"); + lgError($"Effettuo disconnessione x superamento letture UA_ref.ReadNodeString a NULL ({e}), sleep {2 * readErrorSleepTime}msec"); tryDisconnect(); Thread.Sleep(2 * readErrorSleepTime); readErrorCurr = 0; } + + /// + /// Effettua vera scrittura parametri + /// + /// + protected override void plcWriteParams(ref List updatedPar) + { + // solo se connesso! + if (connectionOk) + { + dataConf currMem = null; + int byteSize = 0; + string memAddrWrite = ""; + string serObj = ""; + if (updatedPar != null) + { + // valori da scrivere in memoria + Dictionary nodes2Write = new Dictionary(); + Dictionary> nodesUdt2Write = new Dictionary>(); + Dictionary currUdsDict = new Dictionary(); + // controllo i parametri... ne gestisco 4... + foreach (var item in updatedPar) + { + if (connectionOk) + { + Opc.Ua.DataValue currNodeVal = new DataValue(); + try + { + memAddrWrite = ""; + int valInt = 0; + double valReal = 0; + // cerco in area memMapWrite... + if (memMap.mMapWrite.ContainsKey(item.uid)) + { + // recupero! + currMem = memMap.mMapWrite[item.uid]; + byteSize = currMem.size; + memAddrWrite = currMem.memAddr; + + // init obj scrittura + WriteValue commWriteVal = new WriteValue(); + commWriteVal.Value = new DataValue(); + commWriteVal.AttributeId = Attributes.Value; + + // faccio preliminarmente upsertKey... + upsertKey(memAddrWrite, currMem.value); + serObj = JsonConvert.SerializeObject(item); + lgInfo($"Inizio processing plcWriteParams per {currMem.name} | valore richiesto {currMem.value}"); + lgInfo($"{Environment.NewLine}---------------UPDATED PARAM:{Environment.NewLine}{serObj}{Environment.NewLine}---------------"); + serObj = JsonConvert.SerializeObject(currMem); + lgInfo($"{Environment.NewLine}---------------MEMORY CONTENT:{Environment.NewLine}{serObj}{Environment.NewLine}---------------"); + + // verifico se devo processare byte[]... + bool isUds = memAddrWrite.Contains(opcUaParams.kvDelim); + + if (isUds) + { + // ciclo e calcolo valore + int iPos = memAddrWrite.IndexOf(opcUaParams.kvDelim); + string nodeId = memAddrWrite.Substring(0, iPos); + string varName = memAddrWrite.Substring(iPos + 1); + + //cerco se ci sia già un record Uds... + if (nodesUdt2Write.ContainsKey(nodeId)) + { + currUdsDict = nodesUdt2Write[nodeId]; + // controllo valore... + if (currUdsDict.ContainsKey(varName)) + { + currUdsDict[varName] = item.reqValue; + } + else + { + currUdsDict.Add(varName, item.reqValue); + } + nodesUdt2Write[nodeId] = currUdsDict; + } + else + { + currUdsDict = new Dictionary(); + currUdsDict.Add(varName, item.reqValue); + nodesUdt2Write.Add(nodeId, currUdsDict); + } + } + else + { + // init generico x scrittura nell'oggetto OPC-UA direttamente + commWriteVal.NodeId = new NodeId(memAddrWrite); + commWriteVal.Value.Value = item.reqValue; + + // check tipo specifico + switch (currMem.tipoMem) + { + case plcDataType.Boolean: + break; + + case plcDataType.Int: + case plcDataType.DInt: + case plcDataType.Word: + case plcDataType.DWord: + int.TryParse(item.reqValue, out valInt); + commWriteVal.Value.Value = valInt; + break; + + case plcDataType.Real: + double.TryParse(item.reqValue, out valReal); + commWriteVal.Value.Value = valReal; + break; + + case plcDataType.String: + commWriteVal.Value.Value = item.reqValue; + break; + + default: + break; + } + } + lgInfo($"---------------{Environment.NewLine}OPC-UA data:{Environment.NewLine}NodeId: {commWriteVal.NodeId}{Environment.NewLine}Value: {commWriteVal.Value.Value}{Environment.NewLine}---------------"); + + // se supero soglia errori lettura --> disconnetto e resetto + if (numErroriCheck >= maxErroriCheck) + { + lgInfo($"numErroriCheck: {numErroriCheck} --> richiesta disconnessione adapter con tryDisconnect"); + + numErroriCheck = 0; + tryDisconnect(); + } + else + { + if (!string.IsNullOrEmpty(memAddrWrite) && !string.IsNullOrEmpty($"{commWriteVal.NodeId}")) + { + // cerco se ci fosse già --> aggiorno + if (nodes2Write.ContainsKey($"{commWriteVal.NodeId}")) + { + nodes2Write[$"{commWriteVal.NodeId}"] = commWriteVal; + } + else + { + nodes2Write.Add($"{commWriteVal.NodeId}", commWriteVal); + } + // 2023.09.19 resetto richiesta + item.reqValue = ""; + } + else + { + lgInfo($"Errore: memAddrWrite vuoto!"); + } + } + } + else + { + lgInfo($"Errore uid non trovato in area write memory: {item.uid}, ci sono {memMap.mMapWrite.Count} in area write"); + } + } + catch (Exception exc) + { + lgError($"Eccezione in fase di plcWriteParams per item {item.uid} con valore {item.value}{Environment.NewLine}{exc}"); + } + } + } + + if (connectionOk && (nodes2Write.Count + nodesUdt2Write.Count) > 0) + { + List listWriteReq = nodes2Write.Select(x => x.Value).ToList(); + // valori "diretti" + if (listWriteReq.Count > 0) + { + UA_ref.WriteNodes(listWriteReq); + } + // se valori Udt con modalità alternativa + if (nodesUdt2Write.Count > 0) + { + foreach (var item in nodesUdt2Write) + { + UA_ref.WriteNodesUdt(item.Key, item.Value); + } + } + + } + } + } + + } + #endregion Private Methods } } \ No newline at end of file diff --git a/IOB-WIN-NEXT/IobOpc/OpcUaKpwRama.cs b/IOB-WIN-NEXT/IobOpc/OpcUaKpwRama.cs index 52cc93f4..89f993a8 100644 --- a/IOB-WIN-NEXT/IobOpc/OpcUaKpwRama.cs +++ b/IOB-WIN-NEXT/IobOpc/OpcUaKpwRama.cs @@ -71,9 +71,9 @@ namespace IOB_WIN_NEXT.IobOpc * - recupero elenco DOSSIER last x macchina (legato ai PODL attivi) * - prendo SOLO PODL attivi, e prendo il + vecchio * - se trovo dossier x articolo del PODL + vecchio (e dovrebbe...) procedo - * - uso Dictionary RecipeKeyTranslate x tradurre l'elenco fluxLog in nuovi valori + * - uso Dictionary dei memWrite x tradurre l'elenco fluxLog in nuovi valori * - preparo il set di valori da scrivere con traduzione - * - invio scritture (sim test --> scrivo su file?) + * - invio scritture * - aggiungo info articolo (+ ODL...) * - aggiungo il valore di "scrittura effettuata" *----------------------------------------- */ @@ -87,7 +87,8 @@ namespace IOB_WIN_NEXT.IobOpc // 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); + // fixme todo verificare se usare urlGetActPODL + var rawListPODL = callUrl(urlGetNextPODL, false); if (!string.IsNullOrEmpty(rawListArt)) { try @@ -134,7 +135,7 @@ namespace IOB_WIN_NEXT.IobOpc } else { - lg.Error($"Errore: chiamata elenco PODL ({urlGetCurrPODL}) ha restituito valore vuoto"); + lg.Error($"Errore: chiamata elenco PODL ({urlGetNextPODL}) ha restituito valore vuoto"); } answ = okArt && okPodl && okDoss; @@ -187,129 +188,7 @@ namespace IOB_WIN_NEXT.IobOpc return answ; } - /// - /// Recupera da server set di dati specifici x IOB : qui per compilare ricette via OPC-UA - /// - /// - 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; - } - + #endregion Public Constructors } diff --git a/IOB-WIN-NEXT/IobOpc/OpcUaSiemens.cs b/IOB-WIN-NEXT/IobOpc/OpcUaSiemens.cs index f7964f60..c9be612c 100644 --- a/IOB-WIN-NEXT/IobOpc/OpcUaSiemens.cs +++ b/IOB-WIN-NEXT/IobOpc/OpcUaSiemens.cs @@ -291,6 +291,7 @@ namespace IOB_WIN_NEXT.IobOpc } } +#if false /// /// Effettua vera scrittura parametri /// @@ -303,6 +304,8 @@ namespace IOB_WIN_NEXT.IobOpc string serObj = ""; if (updatedPar != null) { + // cache dei valori da scrivere in memoria x i valori composti come byte[] + Dictionary memWriteCache = new Dictionary(); List nodes2Write = new List(); // controllo i parametri... ne gestisco 4... foreach (var item in updatedPar) @@ -320,6 +323,8 @@ namespace IOB_WIN_NEXT.IobOpc byteSize = currMem.size; memAddrWrite = currMem.memAddr; + // rifare: se è "Recipe_Table_DB_RW"."Thermoset"#GEN.Float.4 --> devo leggere un datablock di byte, aggiornare solo un pezzo... POI scrivere l'intero datablock... + WriteValue commWriteVal = new WriteValue(); commWriteVal.NodeId = new NodeId(memAddrWrite); commWriteVal.AttributeId = Attributes.Value; @@ -388,7 +393,8 @@ namespace IOB_WIN_NEXT.IobOpc UA_ref.WriteNodes(nodes2Write); } } - } + } +#endif #endregion Protected Methods } diff --git a/IOB-WIN-NEXT/IobOpc/OpcUaSiemensRama.cs b/IOB-WIN-NEXT/IobOpc/OpcUaSiemensRama.cs index e8975e43..6f8e3bc9 100644 --- a/IOB-WIN-NEXT/IobOpc/OpcUaSiemensRama.cs +++ b/IOB-WIN-NEXT/IobOpc/OpcUaSiemensRama.cs @@ -36,23 +36,6 @@ namespace IOB_WIN_NEXT.IobOpc * QUANDO FUNZIONA verificare se e come rendere parametrica la gestione oggetti/byte configurabili * *-------------------------------------------*/ -#if false - // inizializzo classe base... - if (!string.IsNullOrEmpty(getOptPar("CHANGE_ODL_MODE"))) - { - CHANGE_ODL_MODE = getOptPar("CHANGE_ODL_MODE"); - } - // 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); - } -#endif sendKeyRichiesta = true; } @@ -70,14 +53,15 @@ namespace IOB_WIN_NEXT.IobOpc } /// - /// Recupero dati da server x invio dati ricetta a KepWare (Ex RAMA x TFT) + /// Recupero dati da server x invio dati ricetta a OPC-UA Siemens (RAMA x Tenditalia) + /// NB: modificato rispetto a TFT: invia PODL correntemente in esecuzione (SE DISPONIBILE) oppure il PRIMO di quelli attivabili... /// /// private bool iobGetSendDossierKepware() { bool answ; - lg.Info("iobGetSendDossierKepware: Inizio scrittura dati a KepWare"); + lgInfo("iobGetSendDossierKepware: Inizio scrittura dati a KepWare"); /*----------------------------------------- * STEPS processo: * - recupero ARTICOLI (solo quelli dei PODL attivi) @@ -85,11 +69,11 @@ namespace IOB_WIN_NEXT.IobOpc * - recupero elenco DOSSIER last x macchina (legato ai PODL attivi) * - prendo SOLO PODL attivi, e prendo il + vecchio * - se trovo dossier x articolo del PODL + vecchio (e dovrebbe...) procedo - * - uso Dictionary RecipeKeyTranslate x tradurre l'elenco fluxLog in nuovi valori + * - uso Dictionary dei memWrite x tradurre l'elenco fluxLog in nuovi valori * - preparo il set di valori da scrivere con traduzione - * - invio scritture (sim test --> scrivo su file?) + * - invio scritture * - aggiungo info articolo (+ ODL...) - * - aggiungo il valore di "scrittura effettuata" + * - aggiungo i valori BIT di "reset contapezzi " + "nuova ricetta" *----------------------------------------- */ bool okArt = false; @@ -101,7 +85,7 @@ namespace IOB_WIN_NEXT.IobOpc // 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 rawListPODL = callUrl(urlGetActPODL, false); if (!string.IsNullOrEmpty(rawListArt)) { try @@ -148,7 +132,7 @@ namespace IOB_WIN_NEXT.IobOpc } else { - lg.Error($"Errore: chiamata elenco PODL ({urlGetCurrPODL}) ha restituito valore vuoto"); + lg.Error($"Errore: chiamata elenco PODL ({urlGetActPODL}) ha restituito valore vuoto"); } answ = okArt && okPodl && okDoss; @@ -184,6 +168,14 @@ namespace IOB_WIN_NEXT.IobOpc ListaCalcParams.Add(currData); lgInfo($"Add param: {currData.uid} | reqVal: {currData.reqValue}"); } + // aggiungo ultimo parametro di resetCounter... + objItem resetCnt = new objItem() + { + uid = "ResetCounter", + reqValue = "true", + name = "ResetCounter" + }; + ListaCalcParams.Add(resetCnt); // aggiungo ultimo parametro di newRecipe... objItem newRecipeData = new objItem() { @@ -201,129 +193,7 @@ namespace IOB_WIN_NEXT.IobOpc return answ; } - /// - /// Recupera da server set di dati specifici x IOB : qui per compilare ricette via OPC-UA - /// - /// - 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; - } - + #endregion Public Constructors } diff --git a/IOB-WIN-NEXT/UAClient.cs b/IOB-WIN-NEXT/UAClient.cs index 2c00176c..2f9cc00d 100644 --- a/IOB-WIN-NEXT/UAClient.cs +++ b/IOB-WIN-NEXT/UAClient.cs @@ -4,6 +4,8 @@ using Opc.Ua.Client; using System; using System.Collections; using System.Collections.Generic; +using System.Linq; +using System.Text; using System.Threading.Tasks; namespace IOB_WIN_NEXT @@ -334,7 +336,7 @@ namespace IOB_WIN_NEXT Session session = await Session.Create( m_configuration, endpoint, - false, + true, false, m_configuration.ApplicationName, 60 * 60 * 1000, @@ -342,6 +344,19 @@ namespace IOB_WIN_NEXT null ); +#if false + Session session = await Session.Create( + m_configuration, + endpoint, + false, + false, + m_configuration.ApplicationName, + 60 * 60 * 1000, + CurrUserIdentity, + null + ); +#endif + // Assign the created session if (session != null && session.Connected) { @@ -393,84 +408,82 @@ namespace IOB_WIN_NEXT } /// - /// Read a SINGLE of nodes value from Server + /// Reads a node by node Id /// - /// - /// - public string ReadNode(NodeId reqNodeId) + /// The node Id as string + /// The read node + /// Throws and forwards any exception with short error description. + public Node ReadNode(String nodeIdString) { - string answ = ""; - if (m_session == null || m_session.Connected == false) + //Create a nodeId using the identifier string + NodeId nodeId = new NodeId(nodeIdString); + //Create a node + Node node = new Node(); + //Read the dataValue + node = m_session.ReadNode(nodeId); + return node; + } + + /// Reads a node by node Id + /// The node Id as string + /// A dictionary containing the attribute values of the node using the attribute uint identifier as key; + /// Throws and forwards any exception with short error description. + public Dictionary ReadNodeAttributes(String nodeIdString) + { + //Create a nodeId using the identifier string + NodeId nodeId = new NodeId(nodeIdString); + return ReadNodeAttributes(nodeId); + } + + /// Reads a node by node Id + /// The node Id + /// A dictionary containing the attribute values of the node using the attribute uint identifier as key; + /// Throws and forwards any exception with short error description. + public Dictionary ReadNodeAttributes(NodeId nodeId) + { + Dictionary attributeDictionary = new Dictionary() { - lgError("Session not connected!"); - return answ; - } + {1, Attributes.NodeId }, + {2, Attributes.NodeClass }, + {3, Attributes.BrowseName }, + {4, Attributes.DisplayName }, + {5, Attributes.Value }, + {6, Attributes.DataType}, + {7, Attributes.ValueRank}, + {8, Attributes.ArrayDimensions}, + {9, Attributes.AccessLevel } + }; - try + //Create a read value id collection to store the nodes to read + ReadValueIdCollection nodesToRead = new ReadValueIdCollection(); + + //Create a StatusCodeCollection + DataValueCollection dataValueCollection = new DataValueCollection(); + + //Create a DiagnosticInfoCollection + DiagnosticInfoCollection diag = new DiagnosticInfoCollection(); + + //Go through the attribute dictionary and create the nodes accordingly + for (int i = 0; i < attributeDictionary.Count; i++) { - //// build a list of nodes to be read - //ReadValueIdCollection nodesToRead = new ReadValueIdCollection() - //{ - // // Value of ServerStatus - // new ReadValueId() { NodeId = Variables.Server_ServerStatus, AttributeId = Attributes.Value }, - // // BrowseName of ServerStatus_StartTime - // new ReadValueId() { NodeId = Variables.Server_ServerStatus_StartTime, AttributeId = Attributes.BrowseName }, - // // Value of ServerStatus_StartTime - // new ReadValueId() { NodeId = Variables.Server_ServerStatus_StartTime, AttributeId = Attributes.Value } - //}; - - //// Read the node attributes - //lgInfo("Reading nodes..."); - - //// Call Read Service - //m_session.Read( - // null, - // 0, - // TimestampsToReturn.Both, - // nodesToRead, - // out DataValueCollection resultsValues, - // out DiagnosticInfoCollection diagnosticInfos); - - //// Validate the results - //m_validateResponse(resultsValues, nodesToRead); - - //// Display the results. - //foreach (DataValue result in resultsValues) - //{ - // lgInfo("Read Value = {0} , StatusCode = {1}", result.Value, result.StatusCode); - //} - - try - { - DataValue resp = m_session.ReadValue(reqNodeId); - answ = $"{resp.Value}"; - // decremento errore... - if (numErrors > 0) - { - numErrors--; - } - } - catch (Exception exc) - { - // Log Error - numErrors++; - lgTrace($"ReadValue Error | reqNodeId: {reqNodeId} | numErrors: {numErrors}{Environment.NewLine}{exc}"); - } + ///Create a read value id with the nessessary attribute + ReadValueId nodeToRead = new ReadValueId(); + nodeToRead.NodeId = nodeId; + nodeToRead.AttributeId = attributeDictionary[i + 1]; + nodesToRead.Add(nodeToRead); } - catch (Exception ex) + //Read the read value id collection + m_session.Read(null, 0, TimestampsToReturn.Neither, nodesToRead, out dataValueCollection, out diag); + //Create a dictionary to store the attribute values + Dictionary attributeValues = new Dictionary(); + //Go through the data value collection to store every data value according to its attribute identifier + int counter = 1; + foreach (DataValue dataValue in dataValueCollection) { - // Log Error - lgError($"Read Nodes Error : {ex.Message}."); - numErrors++; + attributeValues.Add(attributeDictionary[counter], dataValue); + counter += 1; } - // se supero maxErrors --> invio eccezione e resetto... - if (numErrors >= maxErrors) - { - // reset + exception - numErrors = 0; - throw new Exception($"Errore lettura OPC-UA: {maxErrors} errori"); - } - return answ; + return attributeValues; } /// @@ -484,6 +497,7 @@ namespace IOB_WIN_NEXT if (m_session == null || m_session.Connected == false) { lgError("Session not connected!"); + numErrors++; return answ; } @@ -500,6 +514,14 @@ namespace IOB_WIN_NEXT { // Log Error lgError($"ReadNodeRaw Error 01: {Environment.NewLine}{exc}"); + numErrors++; + + // provare disconnect?!? + if (numErrors >= maxErrors) + { + numErrors = 0; + Disconnect(); + } } #endregion Read the Value attribute of a node by calling the Session.ReadValue method @@ -578,6 +600,101 @@ namespace IOB_WIN_NEXT } } + /// + /// Read a SINGLE of nodes value from Server + /// + /// + /// Value as String + public string ReadNodeString(NodeId reqNodeId) + { + string answ = ""; + if (m_session == null || m_session.Connected == false) + { + lgError("Session not connected!"); + return answ; + } + + try + { + try + { + DataValue resp = m_session.ReadValue(reqNodeId); + answ = $"{resp.Value}"; + // decremento errore... + if (numErrors > 0) + { + numErrors--; + } + } + catch (Exception exc) + { + // Log Error + numErrors++; + lgTrace($"ReadValue Error | reqNodeId: {reqNodeId} | numErrors: {numErrors}{Environment.NewLine}{exc}"); + } + } + catch (Exception ex) + { + // Log Error + lgError($"Read Nodes Error : {ex.Message}."); + numErrors++; + } + // se supero maxErrors --> invio eccezione e resetto... + if (numErrors >= maxErrors) + { + // reset + exception + numErrors = 0; + throw new Exception($"Errore lettura OPC-UA: {maxErrors} errori"); + } + return answ; + } + + /// Reads a struct or UDT by node Id + /// The node Id as strings + /// The read struct/UDT elements as a list of string[3]; string[0] = tag name, string[1] = value, string[2] = opc data type + /// Throws and forwards any exception with short error description. + public List ReadStructUdt(String nodeIdString) + { + //Define result list to return var name and var value + List resultStringList = new List(); + + //Read the node attributes + Dictionary nodeAttributes = ReadNodeAttributes(nodeIdString); + //Create a data value storing the value + DataValue data = nodeAttributes[Attributes.Value]; + + //Check if the entered node id is of type array and accordingly get the byte array of the data values + ExtensionObject dataValue = new ExtensionObject(); + List byteArrays = new List(); + if (data.Value is ExtensionObject[]) + { + foreach (ExtensionObject temp in (ExtensionObject[])data.Value) + { + byteArrays.Add((byte[])temp.Body); + } + } + else if (data.Value is ExtensionObject) + { + dataValue = (ExtensionObject)data.Value; + byteArrays.Add((byte[])dataValue.Body); + } + else + { + throw new Exception("The entered node id is not of tpye struct/udt."); + } + + //Get the type dictionary of the struct as a list of string[3]; string[0] = tag name, string[1] = data type, string [2] = array dimensions + List structureDictionary = new List(); + DataTypeNode dataTypeNode = (DataTypeNode)ReadNode(nodeAttributes[Attributes.DataType].ToString()); + GetTypeDictionary(dataTypeNode, m_session, structureDictionary); + + //Get the deserialized byte string as a list of string[4]; string[0]=array index; string[1]=tag name; string[2]=tag value; string[3]=tag data type + resultStringList = ParseDataToTagsFromDictionary(structureDictionary, byteArrays); + + //return result as List string[0]=array index; string[1]=tag name; string[2]=tag value; string[3]=tag data type + return resultStringList; + } + /// /// Create Subscription and MonitoredItems for DataChanges /// @@ -678,6 +795,67 @@ namespace IOB_WIN_NEXT { // Log Error lgError($"Write Nodes Error : {ex.Message}"); + numErrors++; + + // provare disconnect?!? + if (numErrors >= maxErrors) + { + numErrors = 0; + Disconnect(); + } + } + } + } + + /// + /// Metodo scrittura alternativa in formato UDT dei dati... + /// + /// + public void WriteNodesUdt(string nodeIdString, Dictionary data2write) + { + if (!string.IsNullOrEmpty(nodeIdString) && data2write != null && data2write.Count > 0) + { + if (m_session == null || m_session.Connected == false) + { + lgError("Session not connected!"); + return; + } + + try + { + var listUdtRows = new List(); + + listUdtRows = new List(); + // leggo elenco dati in primis + listUdtRows = ReadStructUdt(nodeIdString); + // va scartata ultima riga!!!! + listUdtRows.RemoveAt(listUdtRows.Count - 1); + + // sostituzione dei dati: per ogni valore corrente cerco nei valori data2write + foreach (var item in listUdtRows) + { + // se trovo valore da sostituire + if (data2write.ContainsKey(item[1])) + { + item[2] = data2write[item[1]]; + } + } + + // scrivo + WriteStructUdt(nodeIdString, listUdtRows); + } + catch (Exception ex) + { + // Log Error + lgError($"Error WriteNodesUdt for {nodeIdString}: {Environment.NewLine}{ex.Message}"); + numErrors++; + + // provare disconnect?!? + if (numErrors >= maxErrors) + { + numErrors = 0; + Disconnect(); + } } } } @@ -731,6 +909,132 @@ namespace IOB_WIN_NEXT } } + /// Writes data to a struct or UDT by node Id + /// The node Id as strings + /// The data to write as string[3]; string[0] = tag name, string[1] = value, string[2] = opc data type + /// Throws and forwards any exception with short error description. + public void WriteStructUdt(String nodeIdString, List dataToWrite) + { + //Create a NodeId from the NodeIdString + NodeId nodeId = new NodeId(nodeIdString); + + //Creat a WriteValueColelction + WriteValueCollection valuesToWrite = new WriteValueCollection(); + + //Create a WriteValue + WriteValue writevalue = new WriteValue(); + + //Read the node attributes + Dictionary nodeAttributes = ReadNodeAttributes(nodeIdString); + //Create a data value storing the value + DataValue data = nodeAttributes[Attributes.Value]; + + //Check if the entered node id is of type array and accordingly get the byte array(s) of the data values + ExtensionObject extensionObject = new ExtensionObject(); + List byteArrays = new List(); + if (data.Value is ExtensionObject[]) + { + foreach (ExtensionObject temp in (ExtensionObject[])data.Value) + { + byteArrays.Add((byte[])temp.Body); + } + } + else + { + extensionObject = (ExtensionObject)data.Value; + byteArrays.Add((byte[])extensionObject.Body); + } + + //Get the type dictionary of the struct as a list of string[3]; string[0] = tag name, string[1] = data type, string [2] = array dimensions + List structureDictionary = new List(); + DataTypeNode dataTypeNode = (DataTypeNode)ReadNode(nodeAttributes[Attributes.DataType].ToString()); + GetTypeDictionary(dataTypeNode, m_session, structureDictionary); + + //Delete empty list elements in the structure dictionary and in the list of the gui + for (int j = 0; j < structureDictionary.Count; j++) + { + if (structureDictionary[j][1] == "")//Check if the data type entry is empty as this is an indicator for a struct/udt in a struct/udt + { + structureDictionary.RemoveAt(j); + //dataToWrite.RemoveAt(j); + } + } + + //Create a byte array + List bytesToWrite; + + //Parse dataToWrite to the byte array + bytesToWrite = ParseDataToByteArray(dataToWrite, structureDictionary); + + //Create a StatusCodeCollection + StatusCodeCollection results = new StatusCodeCollection(); + + //Create a DiagnosticInfoCollection + DiagnosticInfoCollection diag = new DiagnosticInfoCollection(); + + //Get the structure definition + StructureDefinition structureDefinition = (StructureDefinition)dataTypeNode.DataTypeDefinition.Body; + + //Create an ExtensionObject from the Structure given to this function + ExtensionObject writeExtObj = new ExtensionObject(); + + //Copy the encoding node id to the extension object type id + writeExtObj.TypeId = structureDefinition.DefaultEncodingId; + + //Copy data to extension object body + DataValue dataValue = null; + + if (bytesToWrite.Count == 1) + { + //Copy data to extension object body + writeExtObj.Body = bytesToWrite[0]; + //Turn the created ExtensionObject into a DataValue + dataValue = new DataValue(writeExtObj); + } + else + { + ExtensionObject[] writeExteObjArr = new ExtensionObject[bytesToWrite.Count]; + for (int i = 0; i < bytesToWrite.Count; i++) + { + //Create an ExtensionObject from the Structure given to this function + writeExtObj = new ExtensionObject(); + //Copy data to extension object body + writeExtObj.TypeId = structureDefinition.DefaultEncodingId; + writeExtObj.Body = bytesToWrite[i]; + //Copy extension object to extension object array + writeExteObjArr[i] = writeExtObj; + } + //Turn the created ExtensionObject[] into a DataValue + dataValue = new DataValue(writeExteObjArr); + } + //Setup for the WriteValue + writevalue.NodeId = nodeId; + writevalue.Value = dataValue; + writevalue.AttributeId = Attributes.Value; + //Add the created value to the collection + valuesToWrite.Add(writevalue); + + try + { + m_session.Write(null, valuesToWrite, out results, out diag); + } + catch (Exception e) + { + //Handle Exception here + throw e; + } + + //Check result codes + foreach (StatusCode result in results) + { + if (result.ToString() != "Good") + { + Exception e = new Exception(result.ToString()); + throw e; + } + } + } + /// /// Write a list of nodes to the Server /// @@ -806,10 +1110,15 @@ namespace IOB_WIN_NEXT #region Protected Fields protected static bool isLogVerbose = false; + protected static Logger lg; + protected int currNullReceiv = 0; + protected int maxErrors = 20; + protected int maxNullAllowed = 100; + protected int numErrors = 0; #endregion Protected Fields @@ -861,14 +1170,494 @@ namespace IOB_WIN_NEXT #region Private Fields private readonly string currIob; + private readonly Action m_validateResponse; + private ApplicationConfiguration m_configuration; + private Session m_session; #endregion Private Fields #region Private Methods + /// + /// Browses for the node id of the parent data type of type base data type + /// + /// The data to write as string[4]; string[0] = index, string[1] = tag name, string[2] = value, string[3] = opc data type + /// The node id of the parent data type + /// Throws and forwards any exception with short error description. + private static NodeId GetParentDataType(NodeId nodeId, Session theSessionToBrowseIn) + { + ReferenceDescriptionCollection referenceDescriptionCollection; + byte[] continuationPoint; + theSessionToBrowseIn.Browse(null, null, nodeId, 1, BrowseDirection.Inverse, ReferenceTypeIds.HasSubtype, true, 0, out continuationPoint, out referenceDescriptionCollection); + NodeId nodeIdParentDataType = (NodeId)referenceDescriptionCollection[0].NodeId; + + if (nodeIdParentDataType.NamespaceIndex != 0) + { + nodeIdParentDataType = GetParentDataType(nodeIdParentDataType, theSessionToBrowseIn); + } + + return nodeIdParentDataType; + } + + /// + /// Browses for the desired type dictonary to parse for containing data types + /// + /// The data type node + /// The current session to browse in + /// The list of string arrays containing the structure information tag name, data type id and array dimension + /// The dictionary as List of string arrays + /// Throws and forwards any exception with short error description. + private static List GetTypeDictionary(DataTypeNode dataTypeNode, Session theSessionToBrowseIn, List structureDictionary) + { + //Create a temp list of string containing the structure dictionary + List tempStructureDictionary = new List(); + tempStructureDictionary = structureDictionary; + Session tempSession = theSessionToBrowseIn; + //Get Structure Definition + StructureFieldCollection structureFieldCollection = null; + try + { + StructureDefinition structureDefinition = (StructureDefinition)dataTypeNode.DataTypeDefinition.Body; + structureFieldCollection = (StructureFieldCollection)structureDefinition.Fields; + } + catch + { + structureDictionary[structureDictionary.Count - 1][1] = UAClient.GetParentDataType(dataTypeNode.NodeId, tempSession).ToString(); + return structureDictionary; + } + + //Check every structure field in the collection + foreach (StructureField structureField in structureFieldCollection) + { + if (structureField.DataType.NamespaceIndex != 0) //Check for structures in the structure + { + //Create a reference description collection + ReferenceDescriptionCollection referenceDescriptionCollection; + //Create a continuationPoint + byte[] continuationPoint; + //Browse for the references of the data type of the structure field + theSessionToBrowseIn.Browse( + null, + null, + structureField.DataType, + 0u, + BrowseDirection.Both, + ReferenceTypeIds.References, + true, + 0, + out continuationPoint, + out referenceDescriptionCollection); + if (((NodeId)referenceDescriptionCollection[0].NodeId).NamespaceIndex == 0)//Data type is sub type of a base data type, i.e. BYTE is a sub tpye of Byte + { + //Check if the structure field is an array and parse the dimension to string + string arrayDimensions = ""; + if (structureField.ValueRank == ValueRanks.OneDimension) + { + arrayDimensions = structureField.ArrayDimensions[0].ToString(); + } + //Add the structure field name, its data type node id and array dimension to the dictionary list + tempStructureDictionary.Add(new string[] { structureField.Name, ((NodeId)referenceDescriptionCollection[0].NodeId).ToString(), arrayDimensions }); + } + else//The structure/udt has at least one additional structure/udt inside + { + //Read the data type node id of the structure/udt in the parent structure/udt + DataTypeNode tempDataTypeNode = (DataTypeNode)theSessionToBrowseIn.ReadNode(structureField.DataType.ToString()); + //Enter an empty Line to seperate the inside structure from the parent + tempStructureDictionary.Add(new string[] { structureField.Name + " - " + "struct/udt of type: " + tempDataTypeNode.DisplayName.ToString(), "", "" }); + //Copy the temp dictionary to the real dictionary + structureDictionary = tempStructureDictionary; + //Recursively get the structure of the structure/udt in the parent structure/udt + GetTypeDictionary(tempDataTypeNode, tempSession, structureDictionary); + } + } + else //The structure field is a base data type + { + //Check if the structure field is an array and parse the dimension to string + string arrayDimensions = ""; + if (structureField.ValueRank == ValueRanks.OneDimension) + { + arrayDimensions = structureField.ArrayDimensions[0].ToString(); + } + //Add the structure field name, its data type node id and array dimension to the dictionary list + tempStructureDictionary.Add(new string[] { structureField.Name, structureField.DataType.ToString(), arrayDimensions }); + } + //Copy the temp dictionary to the real dictionary + structureDictionary = tempStructureDictionary; + } + //Add a row for marking the end of the struct/udt + + structureDictionary.Add(new string[] { "/" + dataTypeNode.DisplayName.ToString(), "", "" }); + return structureDictionary; + } + + /// + /// Parses data to write to a list of byte arrays + /// + /// The data to write as string[4]; string[0] = index, string[1] = tag name, string[2] = value, string[3] = opc data type + /// The structure of the udt/struct + /// The list of the parsed byte arrays + /// Throws and forwards any exception with short error description. + private static List ParseDataToByteArray(List dataToWrite, List structureDictionary) + { + //Define result byte list to return a byte array + List resultByteList = new List(); + + //Dictionary to get the right byte length depending on the data type + Dictionary byteLength = new Dictionary() + { + {BuiltInType.Boolean, 1}, + {BuiltInType.Int16, 2}, + {BuiltInType.Int32, 4}, + {BuiltInType.Int64, 8}, + {BuiltInType.UInt16, 2}, + {BuiltInType.UInt32, 4}, + {BuiltInType.UInt64, 8}, + {BuiltInType.Float, 4}, + {BuiltInType.Double, 8}, + {BuiltInType.Byte, 1}, + {BuiltInType.String, 4} + }; + + //Counter for the structure dictionary browsing + int tempCounter = 0; + + //Create byte array to store the data of one array + byte[] byteArray = new byte[0]; + + //Start decoding for opc data types + foreach (string[] element in dataToWrite) + { + //Reset the counter and the byteArray each time the counter reaches the size of the target Structure/UDT and the data to write include the target Structure/UDT several times + if (tempCounter == structureDictionary.Count) + { + //Add the byte array to the result byte array list + resultByteList.Add(byteArray); + + //Reset the counter and the array + byteArray = new byte[0]; + tempCounter = 0; + } + + //Create byte array to store the bytes of one value depending on its data type, hence it is reseted to 0 for each element + byte[] dataByteArray = new byte[0]; + + //Get the data type id and the target type by using the structure dictionary + NodeId datatypeId = new NodeId(structureDictionary[tempCounter][1]); + BuiltInType targetType = TypeInfo.GetBuiltInType(datatypeId); + + //Get the system type for getting the rigth parse method + Type systemType = TypeInfo.GetSystemType(datatypeId, null); + + //Parse array dimension string of the structure dictionary in int32 + Int32 arraySize = 0; + if (structureDictionary[tempCounter][2] != "") + { + arraySize = Int32.Parse(structureDictionary[tempCounter][2]); + } + + //Serialize the byte string depending on the target data type and the array size - special if queries are needed for byte and string + if (targetType == BuiltInType.String && !(arraySize > 0)) + { + //Resize the data byte array to its correct size depending on the byte length of target type by using the dictionary + dataByteArray = new byte[byteLength[targetType]]; + //Get the bytes of the string length and copy them to the beginning of the data byte array + Array.Copy(BitConverter.GetBytes(element[2].Length), 0, dataByteArray, 0, byteLength[targetType]); + //Convert the string to byte and concat them to the data byte array by converting every char of the string + foreach (Char c in element[2]) + { + byte[] tempArray = new byte[1]; + tempArray[0] = Convert.ToByte(c); + dataByteArray = dataByteArray.Concat(tempArray).ToArray(); + } + } + else if (targetType == BuiltInType.String && arraySize > 0) + { + //Create a temp string array with the array size + string[] tempStringArr = new string[(arraySize - 1)]; + //Resize the data byte array to its correct size depending on the byte length of target type by using the dictionary + dataByteArray = new byte[byteLength[BuiltInType.UInt32]]; + //Get the bytes of the string length and copy them to the beginning of the data byte array + Array.Copy(BitConverter.GetBytes(arraySize), 0, dataByteArray, 0, byteLength[targetType]); + + //Get the single array elements by splitting the string + tempStringArr = element[2].Split(';'); + //Go through every string element in the temp string array + for (int ii = 0; ii < arraySize; ii++) + { + //Copy the string length as byte to the data byte array + byte[] tempArray = new byte[byteLength[targetType]]; + Array.Copy(BitConverter.GetBytes(tempStringArr[ii].Length), 0, tempArray, 0, byteLength[targetType]); + dataByteArray = dataByteArray.Concat(tempArray).ToArray(); + //Convert the string to byte and concat them to the data byte array by converting every char of the string + foreach (Char c in tempStringArr[ii]) + { + byte[] tempArraySingle = new byte[1]; + tempArraySingle[0] = Convert.ToByte(c); + dataByteArray = dataByteArray.Concat(tempArraySingle).ToArray(); + } + } + } + else if (targetType == BuiltInType.Byte && !(arraySize > 0)) + { + //Convert the string of byte to byte + dataByteArray = new byte[byteLength[targetType]]; + dataByteArray[0] = Convert.ToByte(element[2]); + } + else if (targetType == BuiltInType.Byte && arraySize > 0) + { + //Create a temp string variable to store the array elements which are divided by ; + String tempString = ""; + //Resize the data byte array to its correct size depending on the byte length of target type by using the dictionary + dataByteArray = new byte[byteLength[BuiltInType.UInt32]]; + //Get the bytes of the string length and copy them to the beginning of the data byte array + Array.Copy(BitConverter.GetBytes(arraySize), 0, dataByteArray, 0, byteLength[targetType]); + + //Check every char in the string value of the element + foreach (Char c in element[2]) + { + if (c != ';') //Concat every char to a string value in elements except the ; + { + tempString = String.Concat(tempString, c); + } + else + { + //Create a temp byte array with its correct size depending on the byte length of the target data type + byte[] tempArray = new byte[byteLength[targetType]]; + tempArray[0] = Convert.ToByte(tempString); + //Copy the temp array to the data byte array + dataByteArray = dataByteArray.Concat(tempArray).ToArray(); + //Reset the temp string + tempString = ""; + } + } + } + else if (!(arraySize > 0)) + { + //Create the byte array with its correct size depending on the byte length of the target data type + dataByteArray = new byte[byteLength[targetType]]; + //Get the converter method depending on the system type + System.Reflection.MethodInfo methodInfo = typeof(Convert).GetMethod("To" + systemType.Name, new Type[] { typeof(string) }); + //Call the converter method + dynamic convertDataType = methodInfo.Invoke(null, new Object[] { element[2] }); + Array.Copy(BitConverter.GetBytes(convertDataType), 0, dataByteArray, 0, byteLength[targetType]); + } + else if (arraySize > 0) + { + //Create a temp string variable to store the array elements which are divided by ; + String tempString = ""; + //Create the byte array with the size of a UInt32, which is the data type for the array dimension + dataByteArray = new byte[byteLength[BuiltInType.UInt32]]; + //Get the bytes of the array dimension and copy them to the data byte array as they always are at the beginning of a byte array of an array + Array.Copy(BitConverter.GetBytes(arraySize), 0, dataByteArray, 0, byteLength[targetType]); + + //Check every char in the string value of the element + foreach (Char c in element[2]) + { + if (c != ';') //Concat every char to a string value in elements except the ; + { + tempString = String.Concat(tempString, c); + } + else + { + //Create a temp byte array with its correct size depending on the byte length of the target data type + byte[] tempArray = new byte[byteLength[targetType]]; + //Get the converter method depending on the system type + System.Reflection.MethodInfo methodInfo = typeof(Convert).GetMethod("To" + systemType.Name, new Type[] { typeof(string) }); + //Call the converter method + dynamic convertDataType = methodInfo.Invoke(null, new Object[] { element[2] }); + Array.Copy(BitConverter.GetBytes(convertDataType), 0, tempArray, 0, byteLength[targetType]); + //Concat the temp array with the data array + dataByteArray = dataByteArray.Concat(tempArray).ToArray(); + //Reset the temp string + tempString = ""; + } + } + } + else + { + Exception e = new Exception("Data type is too complex to be parsed." + System.Environment.NewLine); + throw e; + } + + //Add the data byte array to the byte array containing all data and count the counter + byteArray = byteArray.Concat(dataByteArray).ToArray(); + tempCounter++; + } + resultByteList.Add(byteArray); + return resultByteList; + } + + /// + /// Parses a byte array to objects containing tag names and tag data types + /// + /// List of object containing tag names and tag data types + /// A byte array to parse + /// A list of string[4]; string[0] = tag name, string[1] = tag name, string[2] = value, string[3] = opc data type + /// Throws and forwards any exception with short error description. + private static List ParseDataToTagsFromDictionary(List varList, List byteArrays) + { + //Define result list to return var name, var value and var data type + List resultStringList = new List(); + + //Dictionary for index counting + Dictionary byteLength = new Dictionary() + { + {BuiltInType.Boolean, 1}, + {BuiltInType.Int16, 2}, + {BuiltInType.Int32, 4}, + {BuiltInType.Int64, 8}, + {BuiltInType.UInt16, 2}, + {BuiltInType.UInt32, 4}, + {BuiltInType.UInt64, 8}, + {BuiltInType.Float, 4}, + {BuiltInType.Double, 8}, + {BuiltInType.Byte, 1}, + {BuiltInType.String, 4} + }; + + //Array index + int arrayIndex = 0; + foreach (byte[] byteResult in byteArrays) + { + //Byte decoding index + int index = 0; + + //Start decoding for opc data types + foreach (string[] val in varList) + { + string[] dataReferenceStringArray = new string[4]; + + //Check if the type dictionary includes a struct/udt in a struct/udt and enter an empty row + if (val[1] == "") + { + dataReferenceStringArray[0] = "[" + arrayIndex.ToString() + "]"; + dataReferenceStringArray[1] = val[0]; + dataReferenceStringArray[2] = ""; + dataReferenceStringArray[3] = ""; + //Add the data Reference string array to the result string list + resultStringList.Add(dataReferenceStringArray); + continue; + } + //Add array index + dataReferenceStringArray[0] = "[" + arrayIndex.ToString() + "]"; + //Copy tag name + dataReferenceStringArray[1] = val[0]; + //Get the target data type via the data type id + NodeId datatypeId = new NodeId(val[1]); + BuiltInType targetType = TypeInfo.GetBuiltInType(datatypeId); + //Get the system type + Type systemType = TypeInfo.GetSystemType(datatypeId, null); + //Parse array dimensions string in int32 + Int32 arrayDimensions = 0; + if (val[2] != "") + { + arrayDimensions = Int32.Parse(val[2]); + } + + //Deserialize the byte array depending on the target data type and the array dimension + if (targetType == BuiltInType.String && !(arrayDimensions > 0)) + { + //Get the string length + Int32 stringlength = BitConverter.ToInt32(byteResult, index); + index += byteLength[targetType]; + if (stringlength > 0) //Decode the bytes to its string value + { + dataReferenceStringArray[2] = Encoding.UTF8.GetString(byteResult, index, stringlength); + index += stringlength; + } + else + { + dataReferenceStringArray[2] = ""; + } + } + else if (targetType == BuiltInType.String && arrayDimensions > 0) + { + //Skip array information (UInt32) regarding its size + index += byteLength[BuiltInType.UInt32]; + //Check every element of the array + for (int i = 0; i < arrayDimensions; i++) + { + //Get the string length + Int32 stringlength = BitConverter.ToInt32(byteResult, index); + index += byteLength[targetType]; + if (stringlength > 0) //Decode the bytes to its string value + { + dataReferenceStringArray[2] = String.Concat(dataReferenceStringArray[2], Encoding.UTF8.GetString(byteResult, index, stringlength)); + //Add a ; to the string value to seperate the value from the other array values + dataReferenceStringArray[2] = String.Concat(dataReferenceStringArray[2], ";"); + index += stringlength; + } + else + { + dataReferenceStringArray[2] = dataReferenceStringArray[2] + ";"; + } + } + } + else if (targetType == BuiltInType.Byte && !(arrayDimensions > 0)) + { + //Copy the byte value as string to the data reference array + dataReferenceStringArray[2] = byteResult[index].ToString(); + index += byteLength[targetType]; + } + else if (targetType == BuiltInType.Byte && arrayDimensions > 0) + { + //Skip array information (UInt32) regarding its size + index += byteLength[BuiltInType.UInt32]; + Int32[] tempArray = new Int32[arrayDimensions]; + //Check every element of the array + for (int i = 0; i < arrayDimensions; i++) + { + tempArray[i] = byteResult[index]; + index += byteLength[targetType]; + } + //Add the values as one value seperated by ; to the data reference array + dataReferenceStringArray[2] = String.Join(";", tempArray); + dataReferenceStringArray[2] = String.Concat(dataReferenceStringArray[2], ";"); + } + else if (!(arrayDimensions > 0)) + { + //Get the converter method depending on the system type + System.Reflection.MethodInfo methodInfo = typeof(BitConverter).GetMethod("To" + systemType.Name); + //Call the method + dataReferenceStringArray[2] = methodInfo.Invoke(null, new Object[] { byteResult, index }).ToString(); + index += byteLength[targetType]; + } + else if (arrayDimensions > 0) + { + //Skip array information (UInt32) regarding its size + index += byteLength[BuiltInType.UInt32]; + //Get the converter method depending on the system type + System.Reflection.MethodInfo methodInfo = typeof(BitConverter).GetMethod("To" + systemType.Name); + dynamic[] tempArray = new dynamic[arrayDimensions]; + //Check every element of the array + for (int i = 0; i < arrayDimensions; i++) + { + tempArray[i] = methodInfo.Invoke(null, new Object[] { byteResult, index }).ToString(); + index += byteLength[targetType]; + } + //Add the values as one value seperated by ; to the data reference array + dataReferenceStringArray[2] = String.Join(";", tempArray); + dataReferenceStringArray[2] = String.Concat(dataReferenceStringArray[1], ";"); + } + else + { + Exception e = new Exception("Data type is too complex to be parsed." + System.Environment.NewLine); + throw e; + } + //Copy the target type as string to the data reference array + dataReferenceStringArray[3] = targetType.ToString(); + //Add the data Reference string array to the result string list + resultStringList.Add(dataReferenceStringArray); + } + //Count the array index + arrayIndex += 1; + } + return resultStringList; + } + /// /// Handles the certificate validation event. This event is triggered every time an /// untrusted certificate is received from the server.