private void WriteVTQ(PreparedStatement stmt, VTQ data, Timestamp timeDB) { stmt[0] = data.T.JavaTicks; stmt[1] = (timeDB - data.T).TotalMilliseconds / 1000L; stmt[2] = (int)data.Q; stmt[3] = data.V.JSON; }
private void WriteVTQ(PreparedStatement stmt, VTQ data, Timestamp timeDB) { stmt[0] = data.T.ToDateTime(); stmt[1] = (int)((timeDB - data.T).TotalMilliseconds / 1000L); stmt[2] = (short)(int)data.Q; stmt[3] = data.V.JSON; }
private static JObject FromVariableValue(VariableValue vv, MqttVarPub config) { var obj = new JObject(); obj["ID"] = vv.Variable.Object.LocalObjectID; VTQ vtq = vv.Value; if (config.TimeAsUnixMilliseconds) { obj["T"] = vtq.T.JavaTicks; } else { obj["T"] = vtq.T.ToString(); } if (config.QualityNumeric) { obj["Q"] = MapQualityToNumber(vtq.Q); } else { obj["Q"] = vtq.Q.ToString(); } obj["V"] = JToken.Parse(vtq.V.JSON); return(obj); }
public InputValue[] CurrentInputValues(Timestamp now) { int N = CalcConfig.Inputs.Count; InputValue[] res = new InputValue[N]; for (int n = 0; n < N; ++n) { Config.Input input = CalcConfig.Inputs[n]; InputValue inValue = new InputValue() { InputID = input.ID }; if (input.Constant.HasValue) { inValue.Value = VTQ.Make(input.Constant.Value, now, Quality.Good); } else if (mapInputValues.ContainsKey(input.ID)) { VariableValue vv = mapInputValues[input.ID]; inValue.Value = vv.Value; inValue.AttachedVariable = vv.Variable; } else { inValue.Value = VTQ.Make(input.GetDefaultValue(), now, Quality.Bad); } res[n] = inValue; } return(res); }
private static async Task OnReceivedVarWriteRequest(MqttVarReceive mqtt, Connection clientFAST, MqttApplicationMessageReceivedEventArgs arg) { await arg.AcknowledgeAsync(CancellationToken.None); var msg = arg.ApplicationMessage; string objID = GetObjIdFromTopicName(msg.Topic); Console.WriteLine($"On got write req for {objID}"); byte[] payloadBytes = msg.Payload; if (payloadBytes != null && payloadBytes.Length > 0) { string payload = Encoding.UTF8.GetString(payloadBytes); DataValue value = DataValue.FromJSON(payload); var variable = VariableRef.Make(mqtt.ModuleID, objID, "Value"); VTQ vtq = VTQ.Make(value, Timestamp.Now, Quality.Good); try { await clientFAST.WriteVariable(variable, vtq); } catch (Exception exp) { Exception e = exp.GetBaseException() ?? exp; Console.Error.WriteLine($"Failed to write variable {variable}: {e.Message}"); } } }
private async Task OnAlarmOrEvent(AlarmOrEvent alarmOrEvent) { if (!running) { initBuffer.Add(alarmOrEvent); return; } AggregatedEvent?aggEvent = null; for (int i = aggregatedWarningsAndAlarms.Count - 1; i >= 0; i--) { var e = aggregatedWarningsAndAlarms[i]; if (e.CanAggregateWith(alarmOrEvent)) { aggEvent = e; break; } } if (aggEvent != null) { aggEvent.AggregateWith(alarmOrEvent); if (aggEvent.ReturnedToNormal && aggEvent.State == EventState.Ack) { aggregatedWarningsAndAlarms.Remove(aggEvent); } DataValue data = DataValue.FromObject(aggEvent); VTQ vtq = new VTQ(aggEvent.TimeFirst, Quality.Good, data); await connection.HistorianModify(GetVar(), ModifyMode.Update, vtq); } else if (!alarmOrEvent.ReturnToNormal) { aggEvent = AggregatedEvent.FromEvent(alarmOrEvent); if (aggEvent.TimeFirst <= latestUsedTimestamp) { Timestamp t = latestUsedTimestamp.AddMillis(1); aggEvent.TimeFirst = t; aggEvent.TimeLast = t; } if (aggEvent.IsWarningOrAlarm()) { aggregatedWarningsAndAlarms.Add(aggEvent); } latestUsedTimestamp = aggEvent.TimeFirst; DataValue data = DataValue.FromObject(aggEvent); VTQ vtq = new VTQ(aggEvent.TimeFirst, Quality.Good, data); await connection.HistorianModify(GetVar(), ModifyMode.Insert, vtq); if (alarmOrEvent.Severity == Severity.Warning || alarmOrEvent.Severity == Severity.Alarm) { NotifyAlarm(alarmOrEvent); } } }
public override async Task <ReqResult> OnUiRequestAsync(string command, DataValue parameters) { switch (command) { case "ReadModuleVariables": if (activeModuleID != "") { await Connection.DisableChangeEvents(); } var pars = parameters.Object <ReadModuleVariables_Params>(); string moduleID = string.IsNullOrEmpty(pars.ModuleID) ? modules[0].ID : pars.ModuleID; ObjectInfo rootObjInfo = await Connection.GetRootObject(moduleID); ObjectRef rootObj = rootObjInfo.ID; ObjectInfo[] objects = await Connection.GetAllObjects(moduleID); SetObjectNameMap(objects); VariableValue[] values = await Connection.ReadAllVariablesOfObjectTree(rootObj); await Connection.EnableVariableValueChangedEvents(SubOptions.AllUpdates(sendValueWithEvent: true), rootObj); VarEntry[] entries = values.Select(MapVarValue).ToArray(); activeModuleID = moduleID; var result = new ReadModuleVariables_Result() { Modules = modules, ModuleID = moduleID, ModuleName = modules.FirstOrDefault(m => m.ID == moduleID).Name, Variables = entries }; mapIdx.Clear(); for (int n = 0; n < values.Length; ++n) { mapIdx[values[n].Variable] = n; } return(ReqResult.OK(result)); case "WriteVariable": var write = parameters.Object <WriteVariable_Params>(); VTQ vtq = new VTQ(Timestamp.Now, Quality.Good, DataValue.FromJSON(write.V)); await Connection.WriteVariable(ObjectRef.FromEncodedString(write.ObjID), write.Var, vtq); return(ReqResult.OK()); default: return(ReqResult.Bad("Unknown command: " + command)); } }
public ItemState(string id, string name, AdapterState adapter, VTQ value, bool write, int?fractionalDigits, DataType type) { ID = id; Name = name; Adapter = adapter; LastReadValue = value; Writeable = write; FractionalDigits = fractionalDigits; Type = type; }
static void AppendRegular(Timestamp t, int n, List <VTQ> list) { for (int i = 0; i < n; ++i) { var vtq = VTQ.Make( 17.54321, t + Duration.FromSeconds(i * 5), Quality.Good); list.Add(vtq); } }
static void AppendAllSame(Timestamp t, int n, List <VTQ> list) { for (int i = 0; i < n; ++i) { var vtq = VTQ.Make( -17.1321, t + Duration.FromSeconds(10), Quality.Bad); list.Add(vtq); } }
static void AppendSpecial(Timestamp t, List <VTQ> list) { list.Add(VTQ.Make("", t + Duration.FromSeconds(10), Quality.Good)); list.Add(VTQ.Make("A", t + Duration.FromSeconds(11), Quality.Good)); list.Add(VTQ.Make("Ä", t + Duration.FromSeconds(18), Quality.Good)); list.Add(VTQ.Make("AB", t + Duration.FromSeconds(19), Quality.Good)); list.Add(VTQ.Make("ÖÜÄ", t + Duration.FromSeconds(30), Quality.Good)); list.Add(VTQ.Make(new string('P', 255), t + Duration.FromSeconds(34), Quality.Good)); list.Add(VTQ.Make(new string('a', 500), t + Duration.FromSeconds(38), Quality.Good)); list.Add(VTQ.Make(new string('ä', 9000), t + Duration.FromMilliseconds(55005), Quality.Good)); list.Add(VTQ.Make(DataValue.Empty, t + Duration.FromSeconds(30), Quality.Good)); }
public override async Task<ReqResult> OnUiRequestAsync(string command, DataValue parameters) { switch (command) { case "Read": Console.Out.WriteLine("Parameters: " + parameters.ToString()); VTQ vtq = await Connection.ReadVariable(Var); return ReqResult.OK(vtq); default: return ReqResult.Bad("Unknown command: " + command); } }
static void AppendRandom(Timestamp t, int n, List <VTQ> list) { Random rand = new Random(2808); for (int i = 0; i < n; ++i) { var vtq = VTQ.Make( rand.NextDouble(), t + Duration.FromMilliseconds(i * rand.Next(0, 5000000)), Quality.Uncertain); list.Add(vtq); } }
public async Task <ReqResult> UiReq_ReadVar() { if (configuration.Variable.HasValue) { VTQ vtq = await Connection.ReadVariable(configuration.Variable.Value); return(ReqResult.OK(vtq.V.JSON)); } else { return(ReqResult.OK("")); } }
private async Task TestHistorianModify() { var varA = GetVarRef("Variable_0"); await ExpectCount(varA, 0); VTQ vtq1 = new VTQ(Timestamp.Now, Quality.Good, DataValue.FromDouble(3.14)); await con.HistorianModify(varA, ModifyMode.Insert, vtq1); await ExpectCount(varA, 1); await TestHistoryRaw(varA, Timestamp.Empty, Timestamp.Max, 10, vtq1); await ExpectException(() => { return(con.HistorianModify(varA, ModifyMode.Insert, vtq1)); }); VTQ vtq2 = new VTQ(vtq1.T, Quality.Good, DataValue.FromDouble(100)); await con.HistorianModify(varA, ModifyMode.Update, vtq2); await ExpectCount(varA, 1); await TestHistoryRaw(varA, Timestamp.Empty, Timestamp.Max, 10, vtq2); VTQ vtq3 = new VTQ(vtq1.T, Quality.Uncertain, DataValue.FromDouble(1000)); await con.HistorianModify(varA, ModifyMode.Upsert, vtq3); await TestHistoryRaw(varA, Timestamp.Empty, Timestamp.Max, 10, vtq3); VTQ vtq4 = new VTQ(vtq1.T.AddMillis(1), Quality.Good, DataValue.FromDouble(1001)); await con.HistorianModify(varA, ModifyMode.Upsert, vtq4); await ExpectCount(varA, 2); await TestHistoryRaw(varA, Timestamp.Empty, Timestamp.Max, 10, vtq3, vtq4); VTQ vtq5 = new VTQ(vtq1.T.AddMillis(2), Quality.Good, DataValue.FromDouble(777)); await ExpectException(() => { return(con.HistorianModify(varA, ModifyMode.Insert, vtq5, vtq3)); }); await TestHistoryRaw(varA, Timestamp.Empty, Timestamp.Max, 10, vtq3, vtq4); await con.HistorianModify(varA, ModifyMode.Delete, vtq3); await TestHistoryRaw(varA, Timestamp.Empty, Timestamp.Max, 10, vtq4); await con.HistorianModify(varA, ModifyMode.Delete, vtq4); await ExpectCount(varA, 0); await con.HistorianModify(varA, ModifyMode.Delete, vtq4); // no exception when not found }
public override async Task <VTQ[]> ReadDataItems(string group, IList <ReadRequest> items, Duration?timeout) { if (!await TryConnect() || connection == null) { return(GetBadVTQs(items)); } var readHelper = new ReadManager <VariableRef, VariableValue>(items, readRequest => mapId2Info[readRequest.ID].VarRef); List <VariableRef> dataItemsToRead = readHelper.GetRefsList(); try { List <VariableValue> readResponse = await connection.ReadVariablesSyncIgnoreMissing(dataItemsToRead); if (readResponse.Count == dataItemsToRead.Count) { readHelper.SetAllResults(readResponse, (vv, request) => vv.Value); return(readHelper.values); } else { var badDataItems = new List <DataItem>(); for (int i = 0; i < dataItemsToRead.Count; ++i) { VariableRef v = dataItemsToRead[i]; try { VariableValue value = readResponse.First(rr => rr.Variable == v); readHelper.SetSingleResult(i, value.Value); } catch (Exception) { // not found ReadRequest req = readHelper.GetReadRequest(i); readHelper.SetSingleResult(i, VTQ.Make(req.LastValue.V, Timestamp.Now, Quality.Bad)); DataItem dataItem = mapId2Info[req.ID].Item; badDataItems.Add(dataItem); } } string[] dataItemNamesWithAddresss = badDataItems.Select(di => di.Name + ": " + di.Address).ToArray(); string details = string.Join("; ", dataItemNamesWithAddresss); string msg = badDataItems.Count == 1 ? $"Invalid address for data item '{badDataItems[0].Name}': {badDataItems[0].Address}" : $"Invalid address for {badDataItems.Count} data items"; LogError("Invalid_Addr", msg, badDataItems.Select(di => di.ID).ToArray(), details); return(readHelper.values); } } catch (Exception exp) { Exception e = exp.GetBaseException() ?? exp; LogWarn("ReadExcept", $"Read exception: {e.Message}", details: e.ToString()); Task ignored = CloseConnection(); return(GetBadVTQs(items)); } }
private async Task <VTQ> ReadDataItemFromDB(string itemID, string query, VTQ lastValue) { var rows = new List <JObject>(); using (DbCommand cmd = CreateCommand(dbConnection !, query)) { using (var reader = await cmd.ExecuteReaderAsync()) { while (reader.Read()) { int n = reader.FieldCount; JObject objRow = new JObject(); for (int i = 0; i < n; ++i) { string name = reader.GetName(i); object value = reader.GetValue(i); objRow[name] = JToken.FromObject(value); } rows.Add(objRow); } } } DataValue dataValue = DataValue.FromObject(rows, indented: true); if (lastValue.V == dataValue) { return(lastValue); } VTQ vtq = VTQ.Make(dataValue, Timestamp.Now.TruncateMilliseconds(), Quality.Good); int N = rows.Count; string firstRow = N == 0 ? "" : StdJson.ObjectToString(rows[0], indented: false); if (N == 0) { PrintLine($"Read 0 rows for {itemID}"); } else if (N == 1) { PrintLine($"Read 1 row for {itemID}: {firstRow}"); } else { PrintLine($"Read {N} rows for {itemID}. First row: {firstRow}"); } return(vtq); }
public override async Task <VTQ[]> ReadDataItems(string group, IList <ReadRequest> items, Duration?timeout) { int N = items.Count; VTQ[] res = new VTQ[N]; for (int i = 0; i < N; ++i) { res[i] = items[i].LastValue; } bool connected = await TryOpenDatabase(); if (!connected) { return(res); } Dictionary <string, string> mapId2Address = config.GetAllDataItems().ToDictionary( item => /* key */ item.ID, item => /* val */ item.Address); bool anyError = false; for (int i = 0; i < N; ++i) { ReadRequest req = items[i]; string itemID = req.ID; VTQ lastVal = req.LastValue; string query = mapId2Address[itemID]; try { res[i] = await ReadDataItemFromDB(itemID, query, lastVal); ReturnToNormal("ReadDataItems", $"Successful read of DataItem {itemID}", itemID); } catch (Exception exp) { Exception e = exp.GetBaseException() ?? exp; LogWarning("ReadDataItems", $"ReadDataItemFromDB failed for {itemID}: {e.Message}", itemID); anyError = true; } } if (anyError) { CloseDB(); } return(res); }
public override async Task <VTQ[]> ReadDataItems(string group, IList <ReadRequest> items, Duration?timeout) { int N = items.Count; if (!await TryConnect() || networkStream == null || config == null) { return(GetBadVTQs(items)); } VTQ[] vtqs = new VTQ[N]; byte[] writeBuffer = new byte[7 + 5]; // 7: Header, 5: PDU for (int i = 0; i < N; ++i) { ReadRequest request = items[i]; if (mapId2Info.ContainsKey(request.ID)) { ItemInfo item = mapId2Info[request.ID]; ModbusAddress address = item.Address; WriteUShort(writeBuffer, 0, (ushort)i); // Transaction-ID WriteUShort(writeBuffer, 2, 0); // Protocol-ID WriteUShort(writeBuffer, 4, 6); // Length writeBuffer[6] = GetModbusHeaderAddress(config, item.Item); writeBuffer[7] = GetModbusFunctionCode(config, item.Item); WriteUShort(writeBuffer, 8, (ushort)(address.Start - 1)); WriteUShort(writeBuffer, 10, address.Count); try { await networkStream.WriteAsync(writeBuffer); ushort[] words = await ReadResponse(networkStream, address.Count); vtqs[i] = ParseModbusResponse(item.Item, words, Timestamp.Now); } catch (Exception exp) { Exception e = exp.GetBaseException() ?? exp; LogWarn("ReadExcept", $"Failed to read item {item.Item.Name}: {e.Message}"); vtqs[i] = VTQ.Make(request.LastValue.V, Timestamp.Now, Quality.Bad); CloseConnection(); } } else { vtqs[i] = VTQ.Make(request.LastValue.V, Timestamp.Now, Quality.Bad); } } return(vtqs); }
private static VTQ[] GetBadVTQs(IList <ReadRequest> items) { int N = items.Count; var t = Timestamp.Now; VTQ[] res = new VTQ[N]; for (int i = 0; i < N; ++i) { VTQ vtq = items[i].LastValue; vtq.Q = Quality.Bad; vtq.T = t; res[i] = vtq; } return(res); }
private VTQ MakeVTQ(Workstation.ServiceModel.Ua.DataValue readRes, VTQ lastValue, string dataItemID) { var t = Timestamp.FromDateTime(readRes.SourceTimestamp); try { var q = MapQuality(readRes.StatusCode); var v = MakeDataValue(readRes.Variant, lastValue.V); return(new VTQ(t, q, v)); } catch (Exception exp) { string name = mapId2Info.ContainsKey(dataItemID) ? mapId2Info[dataItemID].Name : dataItemID; LogWarn("ReadConvertFailed", $"Converting read result of '{name}' failed.", dataItemID, exp.Message); return(new VTQ(t, Quality.Bad, lastValue.V)); } }
public static VTQ Average(double defaultValue, params VTQ[] vtqs) { if (vtqs.Length == 0) { throw new ArgumentException("Average requires at least one parameter"); } if (vtqs.Length == 1) { return(vtqs[0]); } double sum = 0; double count = 0; Quality q = Quality.Good; Timestamp t = Timestamp.Empty; foreach (VTQ vtq in vtqs) { double?v = vtq.V.AsDouble(); if (vtq.Q != Quality.Bad && v.HasValue) { sum += v.Value; count += 1; if (vtq.Q == Quality.Uncertain) { q = Quality.Uncertain; } if (vtq.T > t) { t = vtq.T; } } } if (count == 0) { Timestamp tMax = vtqs.Select(v => v.T).Max(); return(VTQ.Make(defaultValue, tMax, Quality.Bad)); } else { double avg = sum / count; return(VTQ.Make(avg, t, q)); } }
public async void OnAlarmOrEvent(AlarmOrEvent alarmOrEvent) { if (!running) { initBuffer.Add(alarmOrEvent); return; } AggregatedEvent aggEvent = null; for (int i = aggregatedWarningsAndAlarms.Count - 1; i >= 0; i--) { var e = aggregatedWarningsAndAlarms[i]; if (e.CanAggregateWith(alarmOrEvent)) { aggEvent = e; break; } } if (aggEvent != null) { aggEvent.AggreagteWith(alarmOrEvent); DataValue data = DataValue.FromObject(aggEvent); VTQ vtq = new VTQ(aggEvent.TimeFirst, Quality.Good, data); await connection.HistorianModify(GetVar(), ModifyMode.Update, vtq); } else { aggEvent = AggregatedEvent.FromEvent(alarmOrEvent); if (aggEvent.TimeFirst <= latestUsedTimestamp) { Timestamp t = latestUsedTimestamp.AddMillis(1); aggEvent.TimeFirst = t; aggEvent.TimeLast = t; } if (aggEvent.IsWarningOrAlarm()) { aggregatedWarningsAndAlarms.Add(aggEvent); } latestUsedTimestamp = aggEvent.TimeFirst; DataValue data = DataValue.FromObject(aggEvent); VTQ vtq = new VTQ(aggEvent.TimeFirst, Quality.Good, data); await connection.HistorianModify(GetVar(), ModifyMode.Insert, vtq); } }
private VTQ RoundFloat(ItemState istate, VTQ vtq) { bool isFloat = istate.Type.IsFloat(); try { if (isFloat && istate.FractionalDigits.HasValue && !vtq.V.IsArray) { int digits = istate.FractionalDigits.Value; vtq.V = DataValue.FromDouble(Math.Round(vtq.V.GetDouble(), digits)); } } catch (Exception exp) { Log_Warn_Details("RoundingFailed", $"Rounding of data item {istate.Name} failed. Value: " + vtq.V.ToString(), exp.Message); } return(vtq); }
public static List <VariableValue> MakeTestData(int n) { List <VTQ> vtqs = Test_VTQ.MakeTestData(n); List <VariableValue> res = new List <VariableValue>(vtqs.Count); List <VariableRef> varRefs = MakeVarRefs(); for (int i = 0; i < vtqs.Count; ++i) { VTQ vtq = vtqs[i]; VariableRef varRef = varRefs[i % varRefs.Count]; res.Add(VariableValue.Make(varRef, vtq)); } return(res); }
public override Task <StepResult> Step(Timestamp t, InputValue[] inputValues) { foreach (InputValue v in inputValues) { InputBase input = inputs.FirstOrDefault(inn => inn.ID == v.InputID); if (input != null) { input.VTQ = v.Value; } } foreach (var output in outputs) { output.VTQ = VTQ.Make(DataValue.Empty, t, Quality.Good); output.ValueHasBeenAssigned = false; } stepAction(t, dt); StateValue[] resStates = states.Select(kv => new StateValue() { StateID = kv.ID, Value = kv.GetValue() }).ToArray(); var outputValues = new List <OutputValue>(outputs.Length); foreach (OutputBase output in outputs) { if (output.ValueHasBeenAssigned) { outputValues.Add(new OutputValue() { OutputID = output.ID, Value = output.VTQ }); } } var stepRes = new StepResult() { Output = outputValues.ToArray(), State = resStates, }; return(Task.FromResult(stepRes)); }
public void SetInitialStateValues(Dictionary <VariableRef, VTQ> mapVarValues) { lastStateValues.Clear(); foreach (Config.State state in CalcConfig.States) { VariableRef v = GetStateVarRef(state.ID); if (mapVarValues.ContainsKey(v)) { VTQ value = mapVarValues[v]; lastStateValues.Add(new StateValue() { StateID = state.ID, Value = value.V }); } } }
public void SetInitialOutputValues(Dictionary <VariableRef, VTQ> mapVarValues) { lastOutputValues.Clear(); foreach (Config.Output output in CalcConfig.Outputs) { VariableRef v = GetOutputVarRef(output.ID); if (mapVarValues.ContainsKey(v)) { VTQ value = mapVarValues[v]; lastOutputValues.Add(new OutputValue() { OutputID = output.ID, Value = value }); } } }
public override async Task <VTQ[]> ReadVariables(Origin origin, VariableRef[] variables, Duration?timeout) { VTQ[] result = new VTQ[variables.Length]; var adapter2Items = new Dictionary <AdapterState, List <ReadRequest> >(); for (int i = 0; i < variables.Length; ++i) { VariableRef vr = variables[i]; string id = vr.Object.LocalObjectID; if (dataItemsState.ContainsKey(id) && vr.Name == VariableName) { ItemState itemState = dataItemsState[id]; AdapterState adapter = itemState.Adapter; if (!adapter2Items.ContainsKey(adapter)) { adapter2Items[adapter] = new List <ReadRequest>(); } VTQ value = itemState.LastReadValue; adapter2Items[adapter].Add(new ReadRequest(id, value)); } else { result[i] = new VTQ(Timestamp.Empty, Quality.Bad, DataValue.Empty); } } var allReadTasks = new List <Task <VTQ[]> >(adapter2Items.Count); foreach (var adapterItems in adapter2Items) { AdapterState adapter = adapterItems.Key; List <ReadRequest> requests = adapterItems.Value; Task <VTQ[]> task = AdapterReadTask(adapter, requests, timeout); allReadTasks.Add(task); } VTQ[][] resArr = await Task.WhenAll(allReadTasks); VTQ[] res = resArr.SelectMany(x => x).ToArray(); return(res); }
protected override async Task OnConfigModelChanged(bool init) { await base.OnConfigModelChanged(init); var t = Timestamp.Now.TruncateMilliseconds(); var newVarVals = new Dictionary <ObjectRef, VariableValue[]>(); foreach (var entry in mapObjectInfos) { ObjectInfo obj = entry.Value; ObjectRef id = obj.ID; Variable[] variables = obj.Variables; VariableValue[] existingValues = null; if (varVals.ContainsKey(id)) { existingValues = varVals[id]; } int VarLen = variables == null ? 0 : variables.Length; if (existingValues != null || VarLen > 0) { VariableValue[] newValues = new VariableValue[VarLen]; for (int i = 0; i < VarLen; ++i) { Variable variable = variables[i]; VTQ vtq = new VTQ(t, Quality.Bad, variable.DefaultValue); if (existingValues != null) { foreach (VariableValue vv in existingValues) { if (vv.Variable.Name == variable.Name) { vtq = vv.Value; break; } } } newValues[i] = VariableValue.Make(id, variable.Name, vtq); } newVarVals[id] = newValues; } } this.varVals = newVarVals; }