Added hydrocalm3 driver.

pull/353/head
Fredrik Öhrström 2021-10-02 11:36:33 +02:00
rodzic 7445282e1f
commit d6c83f65b2
12 zmienionych plików z 208 dodań i 63 usunięć

Wyświetl plik

@ -1,4 +1,5 @@
Added support for the Hydrocal-M3 heating/cooling meter.
Added support for the Apator uniSMART gas meter.
You can now decode a telegram just by supplying hex on the command line:

Wyświetl plik

@ -172,6 +172,7 @@ METER_OBJS:=\
$(BUILD)/meter_multical21.o \
$(BUILD)/meter_multical302.o \
$(BUILD)/meter_multical403.o \
$(BUILD)/meter_multical602.o \
$(BUILD)/meter_multical603.o \
$(BUILD)/meter_multical803.o \
$(BUILD)/meter_omnipower.o \

Wyświetl plik

@ -364,6 +364,7 @@ Heat and Cooling meter Kamstrup Multical 603 (multical603) (in C1 mode)
Heat and Cooling meter Kamstrup Multical 803 (multical803) (in C1 mode)
Heat meter Apator Elf (elf)
Heat meter Diehl Sharky 775 (sharky)
Heat and Cooling meter BMeters Hydrocal-M3 (hydrocalm3)
Supported room sensors:
Bmeters RFM-AMB Thermometer/Hygrometer (rfmamb)

Wyświetl plik

@ -275,3 +275,8 @@ telegram=76442104710007612507727100076121042507B5006005E2E95A3C2A1279A5415E67326
telegram=|6044B8059430040001037A1D005085E2B670BCF1A5C87E0C1A51DA18924EF984613DA2A9CD39D8F4C7208326C76D42DBEADF80D574192B71BD7C4F56A7F1513151768A9DB804883B28CB085CA2D0F7438C361CB9E2734712ED9BFBB2A14EF55208|
{"media":"gas","meter":"unismart","name":"GasMeter","id":"00043094","fabrication_no":"3162296","total_date_time":"2021-09-15 13:18","total_m3":917,"target_date_time":"2021-09-01 06:00","target_m3":911.32,"version":"UGG4","device_date_time":"2021-09-15 13:18","suppler_info":"00","status":"F00C","parameter_set":"02","other_int":20,"timestamp":"1111-11-11T11:11:11Z"}
|GasMeter;00043094;917.000000;911.320000;1111-11-11 11:11.11
# Test Hydrocal M3 heat/cooling meter
telegram=|8E44B409747372710B0D7A798080052F2F|0C0E59600100046D1D36B9290C13679947000C0E000000000C13590000000C13000000000C13000000000A5A18020A5E11020F823D06003D06003D06003D0600140600620500480400E402001601000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002F2F|
{"media":"heat/cooling load","meter":"hydrocalm3","name":"HeatCool","id":"71727374","total_heating_kwh":4460.833333,"total_cooling_kwh":0,"device_date_time":"2021-09-25 22:29","total_heating_m3":0.059,"total_cooling_m3":0,"total_cooling_m3":0,"supply_volume_m3":0,"return_volume_m3":0,"supply_temperature_c":21.8,"return_temperature_c":21.8,"timestamp":"1111-11-11T11:11:11Z"}
|HeatCool;71727374;4460.833333;0.000000;1111-11-11 11:11.11

Wyświetl plik

@ -314,6 +314,12 @@ bool hasKey(std::map<std::string,std::pair<int,DVEntry>> *values, std::string ke
bool findKey(MeasurementType mit, ValueInformation vif, int storagenr, int tariffnr,
std::string *key, std::map<std::string,std::pair<int,DVEntry>> *values)
{
return findKeyWithNr(mit, vif, storagenr, tariffnr, 1, key, values);
}
bool findKeyWithNr(MeasurementType mit, ValueInformation vif, int storagenr, int tariffnr, int nr,
std::string *key, std::map<std::string,std::pair<int,DVEntry>> *values)
{
int low, hi;
valueInfoRange(vif, &low, &hi);
@ -338,10 +344,11 @@ bool findKey(MeasurementType mit, ValueInformation vif, int storagenr, int tarif
&& (tariffnr == ANY_TARIFFNR || tariffnr == tn))
{
*key = v.first;
nr--;
if (nr <= 0) return true;
/*debug("(dvparser) found key %s for type=%s vif=%02x (%s) storagenr=%d\n",
v.first.c_str(), measurementTypeName(ty).c_str(),
vi, toString(toValueInformation(vi)), storagenr);*/
return true;
}
}
return false;

Wyświetl plik

@ -71,8 +71,11 @@ bool parseDV(Telegram *t,
// in combination with the storagenr. (Later I will add tariff/subunit)
bool findKey(MeasurementType mt, ValueInformation vi, int storagenr, int tariffnr,
std::string *key, std::map<std::string,std::pair<int,DVEntry>> *values);
bool findKeyVife(MeasurementType mt, ValueInformation vi, int storagenr, int tariffnr,
std::string *key, std::map<std::string,std::pair<int,DVEntry>> *values);
// Some meters have multiple identical DIF/VIF values! Meh, they are not using storage nrs or tariff nrs.
// So here we can pick for example nr 2 of an identical set if DIF/VIF values.
// Nr 1 means the first found value.
bool findKeyWithNr(MeasurementType mt, ValueInformation vi, int storagenr, int tariffnr, int nr,
std::string *key, std::map<std::string,std::pair<int,DVEntry>> *values);
#define ANY_STORAGENR -1
#define ANY_TARIFFNR -1

Wyświetl plik

@ -30,6 +30,7 @@
X(APATOR08, 0x8614/*APT?*/, 0x03, 0x03) \
X(APATOR162, MANUFACTURER_APA, 0x06, 0x05) \
X(APATOR162, MANUFACTURER_APA, 0x07, 0x05) \
X(CCx01, MANUFACTURER_GSS, 0x02, 0x01) \
X(CMA12W, MANUFACTURER_ELV, 0x1b, 0x20) \
X(COMPACT5, MANUFACTURER_TCH, 0x04, 0x45) \
X(COMPACT5, MANUFACTURER_TCH, 0xc3, 0x45) \
@ -77,6 +78,7 @@
X(LANSENPU, MANUFACTURER_LAS, 0x00, 0x0b) \
X(LSE_07_17, MANUFACTURER_LSE, 0x07, 0x16) \
X(LSE_07_17, MANUFACTURER_LSE, 0x07, 0x17) \
X(LSE_08, MANUFACTURER_LSE, 0x08, 0x01) \
X(MKRADIO3, MANUFACTURER_TCH, 0x62, 0x74) \
X(MKRADIO3, MANUFACTURER_TCH, 0x72, 0x74) \
X(MKRADIO4, MANUFACTURER_TCH, 0x62, 0x95) \
@ -91,11 +93,13 @@
X(MULTICAL403,MANUFACTURER_KAM, 0x0b, 0x34) \
X(MULTICAL403,MANUFACTURER_KAM, 0x0c, 0x34) \
X(MULTICAL403,MANUFACTURER_KAM, 0x0d, 0x34) \
X(MULTICAL602,MANUFACTURER_KAM, 0x04, 0x1c) \
X(MULTICAL603,MANUFACTURER_KAM, 0x04, 0x35) \
X(MULTICAL803,MANUFACTURER_KAM, 0x04, 0x39) \
X(OMNIPOWER, MANUFACTURER_KAM, 0x02, 0x30) \
X(RFMAMB, MANUFACTURER_BMT, 0x1b, 0x10) \
X(RFMTX1, MANUFACTURER_BMT, 0x07, 0x05) \
X(SENSOSTAR, MANUFACTURER_EFE, 0x04, 0x00) \
X(TSD2, MANUFACTURER_TCH, 0xf0, 0x76) \
X(Q400, MANUFACTURER_AXI, 0x07, 0x10) \
X(QCALORIC, MANUFACTURER_QDS, 0x08, 0x35) \
@ -107,6 +111,7 @@
X(TOPASESKR, MANUFACTURER_AMT, 0x06, 0xf1) \
X(TOPASESKR, MANUFACTURER_AMT, 0x07, 0xf1) \
X(ULTRIMIS, MANUFACTURER_APA, 0x16, 0x01) \
X(UNISMART, MANUFACTURER_AMX, 0x03, 0x01) \
X(VARIO451, MANUFACTURER_TCH, 0x04, 0x27) \
X(VARIO451, MANUFACTURER_TCH, 0xc3, 0x27) \
X(WATERSTARM, MANUFACTURER_DWZ, 0x06, 0x02) \
@ -114,11 +119,7 @@
X(WATERSTARM, MANUFACTURER_EFE, 0x07, 0x03) \
X(WHE46X, MANUFACTURER_LSE, 0x08, 0x18) \
X(WHE5X, MANUFACTURER_LSE, 0x08, 0x34) \
X(SENSOSTAR, MANUFACTURER_EFE, 0x04, 0x00) \
X(CCx01, MANUFACTURER_GSS, 0x02, 0x01) \
X(LSE_08, MANUFACTURER_LSE, 0x08, 0x01) \
X(WEH_07, MANUFACTURER_WEH, 0x07, 0xfe) \
X(UNISMART, MANUFACTURER_AMX, 0x03, 0x01) \
// End of list

Wyświetl plik

@ -1,6 +1,5 @@
/*
Copyright (C) 2018-2020 Fredrik Öhrström
2020 Eric Bus
Copyright (C) 2018-2021 Fredrik Öhrström
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -35,11 +34,6 @@
struct MeterHydrocalM3 : public virtual HeatMeter, public virtual MeterCommonImplementation {
MeterHydrocalM3(MeterInfo &mi);
double totalHeatingEnergyConsumption(Unit u);
double totalHeatingVolume(Unit u);
double totalCoolingEnergyConsumption(Unit u);
double totalCoolingVolume(Unit u);
private:
void processContent(Telegram *t);
@ -47,6 +41,10 @@ private:
double total_heating_volume_m3_ {};
double total_cooling_energy_kwh_ {};
double total_cooling_volume_m3_ {};
double t1_temperature_c_ { 127 };
double t2_temperature_c_ { 127 };
double c1_volume_m3_ {};
double c2_volume_m3_ {};
string device_date_time_ {};
};
@ -57,9 +55,14 @@ MeterHydrocalM3::MeterHydrocalM3(MeterInfo &mi) :
addLinkMode(LinkMode::T1);
addPrint("total_energy_consumption", Quantity::Energy,
[&](Unit u){ return totalEnergyConsumption(u); },
"The total energy consumption recorded by this meter.",
addPrint("total_heating", Quantity::Energy,
[&](Unit u){ assertQuantity(u, Quantity::Energy); return convert(total_heating_energy_kwh_, Unit::KWH, u); },
"The total heating energy consumption recorded by this meter.",
true, true);
addPrint("total_cooling", Quantity::Energy,
[&](Unit u){ assertQuantity(u, Quantity::Energy); return convert(total_cooling_energy_kwh_, Unit::KWH, u); },
"The total cooling energy consumption recorded by this meter.",
true, true);
addPrint("device_date_time", Quantity::Text,
@ -67,69 +70,119 @@ MeterHydrocalM3::MeterHydrocalM3(MeterInfo &mi) :
"Date when total energy consumption was recorded.",
false, true);
addPrint("total_volume", Quantity::Volume,
[&](Unit u){ return totalVolume(u); },
"Total volume of media.",
true, true);
addPrint("total_heating", Quantity::Volume,
[&](Unit u){ assertQuantity(u, Quantity::Volume); return convert(total_heating_volume_m3_, Unit::M3, u); },
"Total heating volume of media.",
false, true);
addPrint("total_cooling", Quantity::Volume,
[&](Unit u){ assertQuantity(u, Quantity::Volume); return convert(total_cooling_volume_m3_, Unit::M3, u); },
"Total cooling volume of media.",
false, true);
addPrint("total_cooling", Quantity::Volume,
[&](Unit u){ assertQuantity(u, Quantity::Volume); return convert(total_cooling_volume_m3_, Unit::M3, u); },
"Total cooling volume of media.",
false, true);
addPrint("supply_volume", Quantity::Volume,
[&](Unit u){ assertQuantity(u, Quantity::Volume); return convert(c1_volume_m3_, Unit::M3, u); },
"Supply c1 volume.",
false, true);
addPrint("return_volume", Quantity::Volume,
[&](Unit u){ assertQuantity(u, Quantity::Volume); return convert(c2_volume_m3_, Unit::M3, u); },
"Return c2 volume.",
false, true);
addPrint("supply_temperature", Quantity::Temperature,
[&](Unit u){ return convert(t1_temperature_c_, Unit::C, u); },
"The supply t1 pipe temperature.",
false, true);
addPrint("return_temperature", Quantity::Temperature,
[&](Unit u){ return convert(t1_temperature_c_, Unit::C, u); },
"The return t2 pipe temperature.",
false, true);
}
shared_ptr<HeatMeter> createHydrocalM3(MeterInfo &mi) {
return shared_ptr<HeatMeter>(new MeterHydrocalM3(mi));
}
double MeterHydrocalM3::totalHeatingEnergyConsumption(Unit u)
{
assertQuantity(u, Quantity::Energy);
return convert(total_heating_energy_kwh_, Unit::KWH, u);
}
double MeterHydrocalM3::totalHeatingVolume(Unit u)
{
assertQuantity(u, Quantity::Volume);
return convert(total_heating_volume_m3_, Unit::M3, u);
}
double MeterHydrocalM3::totalCoolingEnergyConsumption(Unit u)
{
assertQuantity(u, Quantity::Energy);
return convert(total_cooling_energy_kwh_, Unit::KWH, u);
}
double MeterHydrocalM3::totalCoolingVolume(Unit u)
{
assertQuantity(u, Quantity::Volume);
return convert(total_cooling_volume_m3_, Unit::M3, u);
}
void MeterHydrocalM3::processContent(Telegram *t)
{
int offset;
string key;
// The meter either sends the total energy consumed as kWh or as MJ.
// First look for kwh
if(findKey(MeasurementType::Instantaneous, ValueInformation::EnergyWh, 0, 0, &key, &t->values)) {
extractDVdouble(&t->values, key, &offset, &total_heating_energy_kwh_);
t->addMoreExplanation(offset, " total heating energy consumption (%f kWh)", total_heating_energy_kwh_);
}
// Then look for mj.
if(findKey(MeasurementType::Instantaneous, ValueInformation::EnergyMJ, 0, 0, &key, &t->values)) {
double mj;
extractDVdouble(&t->values, key, &offset, &mj);
total_heating_energy_kwh_ = convert(mj, Unit::MJ, Unit::KWH);
t->addMoreExplanation(offset, " total heating_energy consumption (%f MJ = %f kWh)", mj, total_heating_energy_kwh_);
}
if (findKey(MeasurementType::Instantaneous, ValueInformation::DateTime, 0, 0, &key, &t->values)) {
if (findKey(MeasurementType::Instantaneous, ValueInformation::DateTime, 0, 0, &key, &t->values))
{
struct tm datetime;
extractDVdate(&t->values, key, &offset, &datetime);
device_date_time_ = strdatetime(&datetime);
t->addMoreExplanation(offset, " device date time (%s)", device_date_time_.c_str());
}
if(findKey(MeasurementType::Instantaneous, ValueInformation::Volume, 0, 0, &key, &t->values)) {
// The meter either sends the total energy consumed as kWh or as MJ.
// First look for kwh
if (findKeyWithNr(MeasurementType::Instantaneous, ValueInformation::EnergyWh, 0, 0, 1, &key, &t->values))
{
extractDVdouble(&t->values, key, &offset, &total_heating_energy_kwh_);
t->addMoreExplanation(offset, " total heating energy consumption (%f kWh)", total_heating_energy_kwh_);
}
// Then look for mj.
if (findKeyWithNr(MeasurementType::Instantaneous, ValueInformation::EnergyMJ, 0, 0, 1, &key, &t->values))
{
double mj;
extractDVdouble(&t->values, key, &offset, &mj);
total_heating_energy_kwh_ = convert(mj, Unit::MJ, Unit::KWH);
t->addMoreExplanation(offset, " total heating energy consumption (%f MJ = %f kWh)", mj, total_heating_energy_kwh_);
}
if (findKeyWithNr(MeasurementType::Instantaneous, ValueInformation::Volume, 0, 0, 1, &key, &t->values))
{
extractDVdouble(&t->values, key, &offset, &total_heating_volume_m3_);
t->addMoreExplanation(offset, " total heating_volume (%f m3)", total_heating_volume_m3_);
t->addMoreExplanation(offset, " total heating volume (%f m3)", total_heating_volume_m3_);
}
// Now look for cooling energy, which uses the same DIFVIF but follows the first set of data.
if (findKeyWithNr(MeasurementType::Instantaneous, ValueInformation::EnergyWh, 0, 0, 2, &key, &t->values))
{
extractDVdouble(&t->values, key, &offset, &total_cooling_energy_kwh_);
t->addMoreExplanation(offset, " total cooling energy consumption (%f kWh)", total_cooling_energy_kwh_);
}
// Then look for mj.
if (findKeyWithNr(MeasurementType::Instantaneous, ValueInformation::EnergyMJ, 0, 0, 2, &key, &t->values))
{
double mj;
extractDVdouble(&t->values, key, &offset, &mj);
total_cooling_energy_kwh_ = convert(mj, Unit::MJ, Unit::KWH);
t->addMoreExplanation(offset, " total cooling energy consumption (%f MJ = %f kWh)", mj, total_cooling_energy_kwh_);
}
if (findKeyWithNr(MeasurementType::Instantaneous, ValueInformation::Volume, 0, 0, 2, &key, &t->values))
{
extractDVdouble(&t->values, key, &offset, &total_heating_volume_m3_);
t->addMoreExplanation(offset, " total cooling volume (%f m3)", total_cooling_volume_m3_);
}
if (findKeyWithNr(MeasurementType::Instantaneous, ValueInformation::Volume, 0, 0, 3, &key, &t->values))
{
extractDVdouble(&t->values, key, &offset, &c1_volume_m3_);
t->addMoreExplanation(offset, " supply volume (%f m3)", c1_volume_m3_);
}
if (findKeyWithNr(MeasurementType::Instantaneous, ValueInformation::Volume, 0, 0, 4, &key, &t->values))
{
extractDVdouble(&t->values, key, &offset, &c2_volume_m3_);
t->addMoreExplanation(offset, " return volume (%f m3)", c2_volume_m3_);
}
if(findKey(MeasurementType::Instantaneous, ValueInformation::FlowTemperature, 0, 0, &key, &t->values)) {
extractDVdouble(&t->values, key, &offset, &t1_temperature_c_);
t->addMoreExplanation(offset, " supply temperature (%f °C)", t1_temperature_c_);
}
if(findKey(MeasurementType::Instantaneous, ValueInformation::ReturnTemperature, 0, 0, &key, &t->values)) {
extractDVdouble(&t->values, key, &offset, &t2_temperature_c_);
t->addMoreExplanation(offset, " return temperature (%f °C)", t2_temperature_c_);
}
}

Wyświetl plik

@ -162,6 +162,72 @@ double MeterMultical602::volumeFlow(Unit u)
void MeterMultical602::processContent(Telegram *t)
{
/*
(multical602) 14: 02 dif (16 Bit Integer/Binary Instantaneous value)
(multical602) 15: F9 vif (Enhanced identification)
(multical602) 16: FF vife (additive correction constant: unit of VIF * 10^0)
(multical602) 17: 15 vife (?)
(multical602) 18: 1113
(multical602) 1a: 04 dif (32 Bit Integer/Binary Instantaneous value)
(multical602) 1b: 06 vif (Energy kWh)
(multical602) 1c: * 690B0100 total energy consumption (68457.000000 kWh)
(multical602) 20: 04 dif (32 Bit Integer/Binary Instantaneous value)
(multical602) 21: EE vif (Units for H.C.A.)
(multical602) 22: FF vife (additive correction constant: unit of VIF * 10^0)
(multical602) 23: 07 vife (?)
(multical602) 24: C1BC0200
(multical602) 28: 04 dif (32 Bit Integer/Binary Instantaneous value)
(multical602) 29: EE vif (Units for H.C.A.)
(multical602) 2a: FF vife (additive correction constant: unit of VIF * 10^0)
(multical602) 2b: 08 vife (?)
(multical602) 2c: 90D40100
(multical602) 30: 04 dif (32 Bit Integer/Binary Instantaneous value)
(multical602) 31: 14 vif (Volume 10² m³)
(multical602) 32: * A9250400 total volume (2717.850000 m3)
(multical602) 36: 84 dif (32 Bit Integer/Binary Instantaneous value)
(multical602) 37: 40 dife (subunit=1 tariff=0 storagenr=0)
(multical602) 38: 14 vif (Volume 10² m³)
(multical602) 39: 00000000
(multical602) 3d: 84 dif (32 Bit Integer/Binary Instantaneous value)
(multical602) 3e: 80 dife (subunit=0 tariff=0 storagenr=0)
(multical602) 3f: 40 dife (subunit=2 tariff=0 storagenr=0)
(multical602) 40: 14 vif (Volume 10² m³)
(multical602) 41: 00000000
(multical602) 45: 02 dif (16 Bit Integer/Binary Instantaneous value)
(multical602) 46: FD vif (Second extension FD of VIF-codes)
(multical602) 47: 17 vife (Error flags (binary))
(multical602) 48: 0000
(multical602) 4a: 02 dif (16 Bit Integer/Binary Instantaneous value)
(multical602) 4b: 6C vif (Date type G)
(multical602) 4c: * B929 target date (2021-09-25 00:00)
(multical602) 4e: 42 dif (16 Bit Integer/Binary Instantaneous value storagenr=1)
(multical602) 4f: 6C vif (Date type G)
(multical602) 50: BF28
(multical602) 52: 44 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
(multical602) 53: 06 vif (Energy kWh)
(multical602) 54: 100A0100
(multical602) 58: 44 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
(multical602) 59: 14 vif (Volume 10² m³)
(multical602) 5a: D81A0400
(multical602) 5e: C4 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
(multical602) 5f: 40 dife (subunit=1 tariff=0 storagenr=1)
(multical602) 60: 14 vif (Volume 10² m³)
(multical602) 61: 00000000
(multical602) 65: C4 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
(multical602) 66: 80 dife (subunit=0 tariff=0 storagenr=1)
(multical602) 67: 40 dife (subunit=2 tariff=0 storagenr=1)
(multical602) 68: 14 vif (Volume 10² m³)
(multical602) 69: 00000000
(multical602) 6d: 04 dif (32 Bit Integer/Binary Instantaneous value)
(multical602) 6e: 3B vif (Volume flow l/h)
(multical602) 6f: * 39000000 volume flow (0.057000 m3/h)
(multical602) 73: 02 dif (16 Bit Integer/Binary Instantaneous value)
(multical602) 74: 59 vif (Flow temperature 10² °C)
(multical602) 75: * 2A17 T1 flow temperature (59.300000 °C)
(multical602) 77: 02 dif (16 Bit Integer/Binary Instantaneous value)
(multical602) 78: 5D vif (Return temperature 10² °C)
(multical602) 79: * 2912 T2 flow temperature (46.490000 °C)
*/
int offset;
string key;

Wyświetl plik

@ -69,6 +69,7 @@
X(multical21, C1_bit|T1_bit, WaterMeter, MULTICAL21, Multical21) \
X(multical302,C1_bit|T1_bit, HeatMeter, MULTICAL302, Multical302) \
X(multical403,C1_bit, HeatMeter, MULTICAL403, Multical403) \
X(multical602,C1_bit, HeatMeter, MULTICAL602, Multical602) \
X(multical603,C1_bit, HeatMeter, MULTICAL603, Multical603) \
X(multical803,C1_bit, HeatMeter, MULTICAL803, Multical803) \
X(omnipower, C1_bit, ElectricityMeter, OMNIPOWER, Omnipower) \

Wyświetl plik

@ -308,6 +308,11 @@ Received telegram from: 00043094
type: Gas meter (0x03)
ver: 0x01
driver: unismart
Received telegram from: 71727374
manufacturer: (BMT) BMETERS, Italy (0x9b4)
type: Heat/Cooling load meter (0x0d)
ver: 0x0b
driver: hydrocalm3
EOF
RES=$($PROG --logfile=$LOGFILE --t1 simulations/simulation_t1.txt 2>&1)

Wyświetl plik

@ -59,7 +59,8 @@ METERS="MyWarmWater supercom587 12345678 NOKEY
Hetta elf 01885619 NOKEY
DigiWasser dme_07 93929190 NOKEY
Votten aventieswm 61070071 A004EB23329A477F1DD2D7820B56EB3D
GasMeter unismart 00043094 00000000000000000000000000000000"
GasMeter unismart 00043094 00000000000000000000000000000000
HeatCool hydrocalm3 71727374 NOKEY"
cat simulations/simulation_t1.txt | grep '^{' > $TEST/test_expected.txt