From df9403696f46b4130bbb75d43c99a95ea61ec316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20=C3=96hrstr=C3=B6m?= Date: Sat, 26 Nov 2022 22:21:34 +0100 Subject: [PATCH] Refactor hydrodigit to new format. --- simulations/simulation_t1.txt | 6 +-- src/driver_hydrocalm3.cc | 4 +- src/driver_hydrodigit.cc | 69 +++++++++++++++++++++++++ src/dvparser.cc | 20 ++++---- src/dvparser.h | 2 + src/main.cc | 2 + src/meter_detection.h | 3 -- src/meter_hydrodigit.cc | 96 ----------------------------------- src/meters.cc | 67 ++++++++++++------------ src/meters.h | 1 - 10 files changed, 120 insertions(+), 150 deletions(-) create mode 100644 src/driver_hydrodigit.cc delete mode 100644 src/meter_hydrodigit.cc diff --git a/simulations/simulation_t1.txt b/simulations/simulation_t1.txt index 898ba71..5ae12e4 100644 --- a/simulations/simulation_t1.txt +++ b/simulations/simulation_t1.txt @@ -138,12 +138,12 @@ telegram=||6644242328001081640E7266567464A51170071F0050052C411A08674048DD6BA82A0 # Test BMeters HydroDigit water telegram telegram=|4E44B4098686868613077AF0004005_2F2F0C1366380000046D27287E2A0F150E00000000C10000D10000E60000FD00000C01002F0100410100540100680100890000A00000B30000002F2F2F2F2F2F| {"media":"water","meter":"hydrodigit","name":"HydrodigitWater","id":"86868686","total_m3":3.866,"meter_datetime":"2019-10-30 08:39","timestamp":"1111-11-11T11:11:11Z"} -|HydrodigitWater;86868686;3.866000;2019-10-30 08:39;1111-11-11 11:11.11 +|HydrodigitWater;86868686;3.866;2019-10-30 08:39;1111-11-11 11:11.11 # Test another pair of BMeters HydroDigit water telegram telegram=|3044B4090123456713067A190020052F2F_0C1315000000046D0136A7270F050B000000002F2F2F2F2F2F2F2F2F2F2F69E5| {"media":"warm water","meter":"hydrodigit","name":"HydrodigitWater2","id":"67452301","total_m3":0.015,"meter_datetime":"2021-07-07 22:01","timestamp":"1111-11-11T11:11:11Z"} -|HydrodigitWater2;67452301;0.015000;2021-07-07 22:01;1111-11-11 11:11.11 +|HydrodigitWater2;67452301;0.015;2021-07-07 22:01;1111-11-11 11:11.11 # Test Q400 water telegram telegram=|2E4409077272727210077AD71020052F2F_046D040D742C041377000000446D0000612C4413000000002F2F2F2F2F2F| @@ -295,7 +295,7 @@ telegram=|6044B8059430040001037A1D005085E2B670BCF1A5C87E0C1A51DA18924EF984613DA2 # Test Hydrocal M3 heat/cooling meter telegram=|8E44B409747372710B0D7A798080052F2F_0C0E59600100046D1D36B9290C13679947000C0E000000000C13590000000C13000000000C13000000000A5A18020A5E11020F823D06003D06003D06003D0600140600620500480400E402001601000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002F2F| {"media":"heat/cooling load","meter":"hydrocalm3","name":"HeatCool","id":"71727374","status": "TPL_MFCT_80","total_heating_kwh":4460.833333,"total_cooling_kwh":0,"device_datetime":"2021-09-25 22:29","total_heating_m3":479.967,"total_cooling_m3":0.059,"c1_volume_m3":0,"c2_volume_m3":0,"supply_temperature_c":21.8,"return_temperature_c":21.1,"timestamp":"1111-11-11T11:11:11Z"} -|HeatCool;71727374;4460.833333;0;1111-11-11 11:11.11 +|HeatCool;71727374;4460.833333;2021-09-25 22:29;1111-11-11 11:11.11 # Test Weptech Munia temperature hygrometer telegram=|2E44B05C82340100021B7A460000002F2F0A6601020AFB1A570602FD971D00002F2F2F2F2F2F2F2F2F2F2F2F2F2F2F| diff --git a/src/driver_hydrocalm3.cc b/src/driver_hydrocalm3.cc index 40db729..8e4d352 100644 --- a/src/driver_hydrocalm3.cc +++ b/src/driver_hydrocalm3.cc @@ -50,7 +50,7 @@ namespace VifScaling::Auto, FieldMatcher::build() .set(MeasurementType::Instantaneous) - .set(VIFRange::EnergyMJ) + .set(VIFRange::AnyEnergyVIF) .set(IndexNr(1)) ); @@ -154,4 +154,4 @@ namespace // Test:HeatCool hydrocalm3 71727374 NOKEY // telegram=|8E44B409747372710B0D7A798080052F2F_0C0E59600100046D1D36B9290C13679947000C0E000000000C13590000000C13000000000C13000000000A5A18020A5E11020F823D06003D06003D06003D0600140600620500480400E402001601000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002F2F| // {"media":"heat/cooling load","meter":"hydrocalm3","name":"HeatCool","id":"71727374","status": "TPL_MFCT_80","total_heating_kwh":4460.833333,"total_cooling_kwh":0,"device_datetime":"2021-09-25 22:29","total_heating_m3":479.967,"total_cooling_m3":0.059,"c1_volume_m3":0,"c2_volume_m3":0,"supply_temperature_c":21.8,"return_temperature_c":21.1,"timestamp":"1111-11-11T11:11:11Z"} -// |HeatCool;71727374;4460.833333;0;1111-11-11 11:11.11 +// |HeatCool;71727374;4460.833333;2021-09-25 22:29;1111-11-11 11:11.11 diff --git a/src/driver_hydrodigit.cc b/src/driver_hydrodigit.cc new file mode 100644 index 0000000..57e969e --- /dev/null +++ b/src/driver_hydrodigit.cc @@ -0,0 +1,69 @@ +/* + Copyright (C) 2019-2022 Fredrik Öhrström (gpl-3.0-or-later) + + 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include"meters_common_implementation.h" + +namespace +{ + struct Driver : public virtual MeterCommonImplementation + { + Driver(MeterInfo &mi, DriverInfo &di); + }; + + static bool ok = registerDriver([](DriverInfo&di) + { + di.setName("hydrodigit"); + di.setDefaultFields("name,id,total_m3,meter_datetime,timestamp"); + di.setMeterType(MeterType::WaterMeter); + di.addLinkMode(LinkMode::T1); + di.addDetection(MANUFACTURER_BMT, 0x06, 0x13); + di.addDetection(MANUFACTURER_BMT, 0x07, 0x13); + di.addDetection(MANUFACTURER_BMT, 0x07, 0x15); + di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr(new Driver(mi, di)); }); + }); + + Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di) + { + addNumericFieldWithExtractor( + "total", + "The total water consumption recorded by this meter.", + PrintProperty::JSON | PrintProperty::IMPORTANT, + Quantity::Volume, + VifScaling::Auto, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::Volume) + ); + + addNumericFieldWithExtractor( + "meter", + "Meter timestamp for measurement.", + PrintProperty::JSON, + Quantity::PointInTime, + VifScaling::Auto, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::DateTime), + Unit::DateTimeLT + ); + } +} + +// Test: HydrodigitWater hydrodigit 86868686 NOKEY +// telegram=|4E44B4098686868613077AF0004005_2F2F0C1366380000046D27287E2A0F150E00000000C10000D10000E60000FD00000C01002F0100410100540100680100890000A00000B30000002F2F2F2F2F2F| +// {"media":"water","meter":"hydrodigit","name":"HydrodigitWater","id":"86868686","total_m3":3.866,"meter_datetime":"2019-10-30 08:39","timestamp":"1111-11-11T11:11:11Z"} +// |HydrodigitWater;86868686;3.866;2019-10-30 08:39;1111-11-11 11:11.11 diff --git a/src/dvparser.cc b/src/dvparser.cc index e1208a5..8ad12d8 100644 --- a/src/dvparser.cc +++ b/src/dvparser.cc @@ -69,6 +69,14 @@ LIST_OF_VIF_COMBINABLES return VIFCombinable::None; } +Unit toDefaultUnit(Vif v) +{ +#define X(name,from,to,quantity,unit) { if (from <= v.intValue() && v.intValue() <= to) return unit; } +LIST_OF_VIF_RANGES +#undef X + return Unit::Unknown; +} + Unit toDefaultUnit(VIFRange v) { switch (v) { @@ -1154,16 +1162,8 @@ bool extractDVdate(map> *dv_entries, bool DVEntry::extractDate(struct tm *out) { - // This will install the correct timezone - // offset tm_gmtoff into the timestamp. - time_t t = time(NULL); - localtime_r(&t, out); - out->tm_hour = 0; - out->tm_min = 0; - out->tm_sec = 0; - out->tm_mday = 0; - out->tm_mon = 0; - out->tm_year = 0; + memset(out, 0, sizeof(*out)); + out->tm_isdst = -1; // Figure out the dst automatically! vector v; hex2bin(value, &v); diff --git a/src/dvparser.h b/src/dvparser.h index 3a49119..8873b53 100644 --- a/src/dvparser.h +++ b/src/dvparser.h @@ -236,6 +236,8 @@ private: int nr_; }; +Unit toDefaultUnit(Vif v); + enum class DVEntryCounterType { UNKNOWN, diff --git a/src/main.cc b/src/main.cc index 8c19a4b..1b4b53a 100644 --- a/src/main.cc +++ b/src/main.cc @@ -81,6 +81,8 @@ shared_ptr printer_; int main(int argc, char **argv) { + tzset(); // Load the current timezone. + auto config = parseCommandLine(argc, argv); if (config->version) diff --git a/src/meter_detection.h b/src/meter_detection.h index 02020b0..5133ef3 100644 --- a/src/meter_detection.h +++ b/src/meter_detection.h @@ -28,9 +28,6 @@ // #define METER_DETECTION \ X(CCx01, MANUFACTURER_GSS, 0x02, 0x01) \ - X(HYDRODIGIT,MANUFACTURER_BMT, 0x06, 0x13) \ - X(HYDRODIGIT,MANUFACTURER_BMT, 0x07, 0x13) \ - X(HYDRODIGIT,MANUFACTURER_BMT, 0x07, 0x15) \ X(LSE_08, MANUFACTURER_LSE, 0x08, 0x01) \ X(MULTICAL302,MANUFACTURER_KAM, 0x04, 0x30) \ X(MULTICAL302,MANUFACTURER_KAM, 0x0d, 0x30) \ diff --git a/src/meter_hydrodigit.cc b/src/meter_hydrodigit.cc deleted file mode 100644 index 535fd0a..0000000 --- a/src/meter_hydrodigit.cc +++ /dev/null @@ -1,96 +0,0 @@ -/* - Copyright (C) 2019-2020 Fredrik Öhrström (gpl-3.0-or-later) - - 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 - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#include"dvparser.h" -#include"meters.h" -#include"meters_common_implementation.h" -#include"wmbus.h" -#include"wmbus_utils.h" -#include"util.h" - -using namespace std; - -struct MeterHydrodigit : public virtual MeterCommonImplementation { - MeterHydrodigit(MeterInfo &mi); - - // Total water counted through the meter - double totalWaterConsumption(Unit u); - bool hasTotalWaterConsumption(); - -private: - void processContent(Telegram *t); - - double total_water_consumption_m3_ {}; - string meter_datetime_; -}; - -shared_ptr createHydrodigit(MeterInfo &mi) -{ - return shared_ptr(new MeterHydrodigit(mi)); -} - -MeterHydrodigit::MeterHydrodigit(MeterInfo &mi) : - MeterCommonImplementation(mi, "hydrodigit") -{ - setMeterType(MeterType::WaterMeter); - - setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV); - - addLinkMode(LinkMode::T1); - - addPrint("total", Quantity::Volume, - [&](Unit u){ return totalWaterConsumption(u); }, - "The total water consumption recorded by this meter.", - PrintProperty::FIELD | PrintProperty::JSON); - - addPrint("meter_datetime", Quantity::Text, - [&](){ return meter_datetime_; }, - "Meter timestamp for measurement.", - PrintProperty::FIELD | PrintProperty::JSON); -} - -void MeterHydrodigit::processContent(Telegram *t) -{ - int offset; - string key; - - if(findKey(MeasurementType::Instantaneous, VIFRange::Volume, 0, 0, &key, &t->dv_entries)) { - extractDVdouble(&t->dv_entries, key, &offset, &total_water_consumption_m3_); - t->addMoreExplanation(offset, " total consumption (%f m3)", total_water_consumption_m3_); - } - - if (findKey(MeasurementType::Instantaneous, VIFRange::DateTime, 0, 0, &key, &t->dv_entries)) { - struct tm datetime; - extractDVdate(&t->dv_entries, key, &offset, &datetime); - meter_datetime_ = strdatetime(&datetime); - t->addMoreExplanation(offset, " meter_datetime (%s)", meter_datetime_.c_str()); - } - - vector data; - t->extractMfctData(&data); -} - -double MeterHydrodigit::totalWaterConsumption(Unit u) -{ - assertQuantity(u, Quantity::Volume); - return convert(total_water_consumption_m3_, Unit::M3, u); -} - -bool MeterHydrodigit::hasTotalWaterConsumption() -{ - return true; -} diff --git a/src/meters.cc b/src/meters.cc index 2dd9314..74f77b2 100644 --- a/src/meters.cc +++ b/src/meters.cc @@ -1193,6 +1193,7 @@ bool checkCommonField(string *buf, string field, Meter *m, Telegram *t, char c, bool checkPrintableField(string *buf, string field, Meter *m, Telegram *t, char c, vector &fields, bool human_readable) { + for (FieldInfo &fi : fields) { if (fi.xuantity() == Quantity::Text) @@ -1206,40 +1207,34 @@ bool checkPrintableField(string *buf, string field, Meter *m, Telegram *t, char } else { - // Doubles have to be converted into the proper unit. - string default_unit = unitToStringLowerCase(fi.defaultUnit()); - string var = fi.vname()+"_"+default_unit; - if (field == var) + if (fi.defaultUnit() == Unit::DateLT) { - // Default unit. - *buf += valueToString(m->getNumericValue(&fi, fi.defaultUnit()), fi.defaultUnit()); - if (human_readable) - { - *buf += " "; - *buf += unitToStringHR(fi.defaultUnit()); - } + *buf += strdate(m->getNumericValue(&fi, Unit::DateLT)); + *buf += c; + return true; + } + else if (fi.defaultUnit() == Unit::DateTimeLT) + { + *buf += strdatetime(m->getNumericValue(&fi, Unit::DateTimeLT)); *buf += c; return true; } else { - // Added conversion unit. - Unit u = fi.defaultUnit(); - if (u != fi.defaultUnit()) + // Doubles have to be converted into the proper unit. + string default_unit = unitToStringLowerCase(fi.defaultUnit()); + string var = fi.vname()+"_"+default_unit; + if (field == var) { - string unit = unitToStringLowerCase(u); - string var = fi.vname()+"_"+unit; - if (field == var) + // Default unit. + *buf += valueToString(m->getNumericValue(&fi, fi.defaultUnit()), fi.defaultUnit()); + if (human_readable) { - *buf += valueToString(m->getNumericValue(&fi, u), u); - if (human_readable) - { - *buf += " "; - *buf += unitToStringHR(u); - } - *buf += c; - return true; + *buf += " "; + *buf += unitToStringHR(fi.defaultUnit()); } + *buf += c; + return true; } } } @@ -2429,26 +2424,28 @@ bool FieldInfo::extractNumeric(Meter *m, Telegram *t, DVEntry *dve) { struct tm datetime; dve->extractDate(&datetime); - // TODO Figure out why I have to use a temporary time_t offset here instead - // of just subtracting datetime.tm_gmtoff, which gives the wrong result. - time_t offset = datetime.tm_gmtoff; - time_t tmp = mktime(&datetime)-offset; + time_t tmp = mktime(&datetime); + string bbb = strdatetime(tmp); extracted_double_value = tmp; } else if (matcher_.vif_range == VIFRange::Date) { struct tm date; dve->extractDate(&date); - //time_t offset = date.tm_gmtoff; time_t tmp = mktime(&date); extracted_double_value = tmp; } - else if (matcher_.vif_range != VIFRange::Any && - matcher_.vif_range != VIFRange::AnyVolumeVIF && - matcher_.vif_range != VIFRange::AnyEnergyVIF && - matcher_.vif_range != VIFRange::AnyPowerVIF && - matcher_.vif_range != VIFRange::None) + else if (matcher_.vif_range == VIFRange::AnyEnergyVIF || + matcher_.vif_range == VIFRange::AnyVolumeVIF || + matcher_.vif_range == VIFRange::AnyPowerVIF) { + // Find the actual unit used in the telegram. + decoded_unit = toDefaultUnit(dve->vif); + } + else if (matcher_.vif_range != VIFRange::Any && + matcher_.vif_range != VIFRange::None) + { + // Pick the default unit for this range. decoded_unit = toDefaultUnit(matcher_.vif_range); } diff --git a/src/meters.h b/src/meters.h index c708dec..dbda358 100644 --- a/src/meters.h +++ b/src/meters.h @@ -62,7 +62,6 @@ LIST_OF_METER_TYPES X(auto, 0, AutoMeter, AUTO, Auto) \ X(unknown, 0, UnknownMeter, UNKNOWN, Unknown) \ X(gransystems,T1_bit, ElectricityMeter, CCx01, CCx01) \ - X(hydrodigit, T1_bit, WaterMeter, HYDRODIGIT, Hydrodigit) \ X(multical302,C1_bit|T1_bit, HeatMeter, MULTICAL302, Multical302) \ X(multical403,C1_bit, HeatMeter, MULTICAL403, Multical403) \ X(multical602,C1_bit, HeatMeter, MULTICAL602, Multical602) \