From 0a7f8a8e2be0f54dee41335059eaf0941d938cf7 Mon Sep 17 00:00:00 2001 From: Vyacheslav Karpukhin Date: Fri, 19 May 2023 03:46:29 +0200 Subject: [PATCH] Add partial support for proprietary Q walk-by telegrams (qheat, qwater drivers) --- src/driver_qheat.cc | 63 ++++++++++++++++++++++++++++++- src/driver_qwater.cc | 27 ++++++++++++- src/manufacturer_specificities.cc | 26 +++++++++++++ src/manufacturer_specificities.h | 2 + src/wmbus.cc | 24 +++++++----- src/wmbus.h | 2 + 6 files changed, 132 insertions(+), 12 deletions(-) diff --git a/src/driver_qheat.cc b/src/driver_qheat.cc index 25da972..a3fa204 100644 --- a/src/driver_qheat.cc +++ b/src/driver_qheat.cc @@ -16,12 +16,16 @@ */ #include"meters_common_implementation.h" +#include"manufacturer_specificities.h" namespace { struct Driver : public virtual MeterCommonImplementation { Driver(MeterInfo &mi, DriverInfo &di); + + protected: + void processContent(Telegram *t) override; }; static bool ok = registerDriver([](DriverInfo&di) @@ -32,7 +36,8 @@ namespace di.addLinkMode(LinkMode::C1); di.addDetection(MANUFACTURER_QDS, 0x04, 0x23); di.addDetection(MANUFACTURER_QDS, 0x04, 0x46); - // MANUFACTURER_QDS, 0x37, 0x23 waiting for telegram for test-suite. + di.addDetection(MANUFACTURER_QDS, 0x37, 0x23); + di.usesProcessContent(); di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr(new Driver(mi, di)); }); }); @@ -148,6 +153,51 @@ namespace .set(VIFRange::Date) ); } + + void Driver::processContent(Telegram *t) { + auto it = t->dv_entries.find("0779"); + if (it != t->dv_entries.end()) { + vector v; + auto entry = it->second.second; + hex2bin(entry.value.substr(0, 8), &v); + t->addId(v.begin()); + std::string info = "*** " + entry.value.substr(0, 8) + " tpl-id (" + t->ids.back() + ")"; + t->addSpecialExplanation(entry.offset, 4, KindOfData::CONTENT, Understanding::FULL, info.c_str()); + + v.clear(); + hex2bin(entry.value.substr(8, 4), &v); + uint16_t tpl_mfct = *(uint16_t *) (&v[0]); + info = "*** " + entry.value.substr(8, 4) + " tpl-mfct (" + manufacturerFlag(tpl_mfct) + ")"; + t->addSpecialExplanation(entry.offset + 4, 2, KindOfData::PROTOCOL, Understanding::FULL, info.c_str()); + + v.clear(); + hex2bin(entry.value.substr(12, 2), &v); + uint8_t tpl_version = v[0]; + info = "*** " + entry.value.substr(12, 2) + " tpl-version"; + t->addSpecialExplanation(entry.offset + 6, 1, KindOfData::PROTOCOL, Understanding::FULL, info.c_str()); + + v.clear(); + hex2bin(entry.value.substr(14, 2), &v); + uint8_t tpl_type = v[0]; + info = "*** " + entry.value.substr(14, 2) + " tpl-type (" + mediaType(v[0], tpl_mfct) + ")"; + t->addSpecialExplanation(entry.offset + 7, 1, KindOfData::PROTOCOL, Understanding::FULL, info.c_str()); + + t->tpl_id_found = true; + t->tpl_mfct = tpl_mfct; + t->tpl_version = tpl_version; + t->tpl_type = tpl_type; + } + + it = t->dv_entries.find("0DFF5F"); + if (it != t->dv_entries.end()) { + DVEntry entry = it->second.second; + qdsExtractWalkByField(t, this, entry, 24, 8, "0C05", "total_energy_consumption", Quantity::Energy); + qdsExtractWalkByField(t, this, entry, 32, 4, "426C", "last_year_date", Quantity::Text); + qdsExtractWalkByField(t, this, entry, 36, 8, "4C05", "last_year_energy_consumption", Quantity::Energy); + qdsExtractWalkByField(t, this, entry, 44, 4, "C2086C", "last_month_date", Quantity::Text); + qdsExtractWalkByField(t, this, entry, 48, 8, "CC0805", "last_month_energy_consumption", Quantity::Energy); + } + } } // Test: QHeato qheat 67228058 NOKEY @@ -160,3 +210,14 @@ namespace // telegram=|41449344796550674637727965506793444604dc0000200c0d000000004c0d00000000426cffffcc080d00000000c2086cdf2802fd170000326cffff046d3a0ddb29| // {"media":"heat","meter":"qheat","name":"Qheatoo","id":"67506579","status":"OK","total_energy_consumption_kwh":0,"last_month_date":"2022-08-31","last_month_energy_consumption_kwh":0,"last_year_date":"2127-15-31","last_year_energy_consumption_kwh":0,"device_date_time":"2022-09-27 13:58","device_error_date":"2127-15-31","timestamp":"1111-11-11T11:11:11Z"} // |Qheatoo;67506579;0;2022-08-31;0;1111-11-11 11:11.11 + +// Test: Qheatoo qheat 78563412 NOKEY +// telegram=|3C449344123456782337729876543293442304FE0000200C05682235004C0580253200426CDF2CCC080525153500C2086CFE24326CFFFF046D1811F225| +// {"media":"heat","meter":"qheat","name":"Qheatoo","id":"32547698","status":"OK","total_energy_consumption_kwh":35226.8,"last_month_date":"2023-04-30","last_month_energy_consumption_kwh":35152.5,"last_year_date":"2022-12-31","last_year_energy_consumption_kwh":32258,"device_date_time":"2023-05-18 17:24","device_error_date":"2127-15-31","timestamp":"1111-11-11T11:11:11Z"} +// |Qheatoo;32547698;35226.8;2023-04-30;35152.5;1111-11-11 11:11.11 + +// Test: Qheatoo qheat 78563411 NOKEY +// Comment: Proprietary Q walk-by message, these telegrams currently can be matched only by the first id +// telegram=|5344934411345678233778077998765431934423040dff5f350082fe00005f0107c005ffff68223500df2c80253200fe24251535005c03030000000000af03f508e91e1d2efc236e1fa218fe142f046d1911f225| +// {"media":"heat","meter":"qheat","name":"Qheatoo","id":"31547698","status":"OK","total_energy_consumption_kwh":35226.8,"last_month_date":"2023-04-30","last_month_energy_consumption_kwh":35152.5,"last_year_date":"2022-12-31","last_year_energy_consumption_kwh":32258,"device_date_time":"2023-05-18 17:25","timestamp":"1111-11-11T11:11:11Z"} +// |Qheatoo;31547698;35226.8;2023-04-30;35152.5;1111-11-11 11:11.11 diff --git a/src/driver_qwater.cc b/src/driver_qwater.cc index e7649d5..2148cb7 100644 --- a/src/driver_qwater.cc +++ b/src/driver_qwater.cc @@ -16,12 +16,15 @@ */ #include"meters_common_implementation.h" +#include"manufacturer_specificities.h" namespace { struct Driver : public virtual MeterCommonImplementation { Driver(MeterInfo &mi, DriverInfo &di); + protected: + void processContent(Telegram *t) override; }; static bool ok = registerDriver([](DriverInfo&di) @@ -41,6 +44,7 @@ namespace di.addDetection(MANUFACTURER_QDS, 0x07, 0x18); di.addDetection(MANUFACTURER_QDS, 0x06, 0x35); di.addDetection(MANUFACTURER_QDS, 0x07, 0x35); + di.usesProcessContent(); di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr(new Driver(mi, di)); }); }); @@ -132,6 +136,19 @@ namespace } } +void Driver::processContent(Telegram *t) { + auto it = t->dv_entries.find("0DFF5F"); + if (it == t->dv_entries.end()) { + return; + } + DVEntry entry = it->second.second; + qdsExtractWalkByField(t, this, entry, 24, 8, "0C13", "total", Quantity::Volume); + qdsExtractWalkByField(t, this, entry, 32, 4, "426C", "due", Quantity::PointInTime); + qdsExtractWalkByField(t, this, entry, 36, 8, "4C13", "due_date", Quantity::Volume); + qdsExtractWalkByField(t, this, entry, 44, 4, "C2086C", "due_17", Quantity::PointInTime); + qdsExtractWalkByField(t, this, entry, 48, 8, "CC0813", "due_17_date", Quantity::Volume); +} + // Test: MyQWater qwater 12353648 NOKEY // telegram=|374493444836351218067ac70000200c13911900004c1391170000426cbf2ccc081391170000c2086cbf2c02bb560000326cffff046d1e02de21fed0| // {"media":"warm water","meter":"qwater","name":"MyQWater","id":"12353648","status":"OK","total_m3":1.991,"due_date_m3":1.791,"due_date":"2021-12-31","due_17_date_m3":1.791,"due_17_date":"2021-12-31","error_date":"2128-03-31","volume_flow_m3h":0,"meter_datetime":"2022-01-30 02:30","timestamp":"1111-11-11T11:11:11Z"} @@ -139,8 +156,8 @@ namespace // And a second telegram that only updates the device date time. // telegram=|47449344483635121806780dff5f350082da0000600107c113ffff48200000bf2c91170000df2120200000008001000000060019001000160018000d001300350017002f046d370cc422c759| -// {"media":"warm water","meter":"qwater","name":"MyQWater","id":"12353648","status":"OK","total_m3":1.991,"due_date_m3":1.791,"due_date":"2021-12-31","due_17_date_m3":1.791,"due_17_date":"2021-12-31","error_date":"2128-03-31","volume_flow_m3h":0,"meter_datetime":"2022-02-04 12:55","timestamp":"1111-11-11T11:11:11Z"} -// |MyQWater;12353648;1.991;1.791;2021-12-31;OK;1111-11-11 11:11.11 +// {"media":"warm water","meter":"qwater","name":"MyQWater","id":"12353648","status":"OK","total_m3":2.048,"due_date_m3":1.791,"due_date":"2021-12-31","due_17_date_m3":2.02,"due_17_date":"2022-01-31","error_date":"2128-03-31","volume_flow_m3h":0,"meter_datetime":"2022-02-04 12:55","timestamp":"1111-11-11T11:11:11Z"} +// |MyQWater;12353648;2.048;1.791;2021-12-31;OK;1111-11-11 11:11.11 // Test: AnotherQWater qwater 66666666 NOKEY // telegram=|3C449344682268363537726666666693443507720000200C13670512004C1361100300426CBF2CCC081344501100C2086CDF28326CFFFF046D0813CF29| @@ -169,3 +186,9 @@ namespace // telegram=|394493449068171316077A0B000020_0C13358612004C1307851200426CDF2CCC081307851200C2086CDF2C02BB560000326CFFFF046D3014E121| // {"due_17_date": "2022-12-31","due_17_date_m3": 128.507,"due_date": "2022-12-31","due_date_m3": 128.507,"error_date": "2128-03-31","id": "13176890","media": "water","meter": "qwater","meter_datetime": "2023-01-01 20:48","name": "QWooo","status": "OK","timestamp": "1111-11-11T11:11:11Z","total_m3": 128.635,"volume_flow_m3h": 0} // |QWooo;13176890;128.635;128.507;2022-12-31;OK;1111-11-11 11:11.11 + +// Test: QWooo qwater 78563412 NOKEY +// Comment: Proprietary Q walk-by message +// telegram=|49449344123456781606780DFF5F3500824E00007F0007C113FFFF63961300DF2C82731200FE2463811300A400F200D100A900DD00E000E90006011601EA0027010F012F046D0211F225| +// {"due_17_date": "2023-04-30","due_17_date_m3": 138.163,"due_date": "2022-12-31","due_date_m3": 127.382,"id": "78563412","media": "warm water","meter": "qwater","meter_datetime": "2023-05-18 17:02","name": "QWooo","status": "OK","timestamp": "1111-11-11T11:11:11Z","total_m3": 139.663} +// |QWooo;78563412;139.663;127.382;2022-12-31;OK;1111-11-11 11:11.11 diff --git a/src/manufacturer_specificities.cc b/src/manufacturer_specificities.cc index 4b56a87..d786907 100644 --- a/src/manufacturer_specificities.cc +++ b/src/manufacturer_specificities.cc @@ -20,6 +20,7 @@ #include #include"manufacturers.h" #include"manufacturer_specificities.h" +#include"meters.h" std::set diehl_manufacturers = { MANUFACTURER_DME, @@ -347,3 +348,28 @@ const char *toString(DiehlAddressTransformMethod m) } return "?"; } + +void qdsExtractWalkByField(Telegram *t, Meter *driver, DVEntry &mfctEntry, int pos, int n, const string &key_s, const string &fieldName, Quantity quantity) { + string bytes = mfctEntry.value.substr(pos, n); + + DifVifKey key(key_s); + DVEntry fieldEntry(0, + key, + MeasurementType::Instantaneous, + key.vif(), + set(), + AnyStorageNr, + AnyTariffNr, + SubUnitNr(0), + bytes); + + FieldInfo *fieldInfo = driver->findFieldInfo(fieldName, quantity); + if (fieldInfo == nullptr) { + error("(qds) field info not found: %s\n", fieldName.c_str()); + } + + fieldInfo->performExtraction(driver, t, &fieldEntry); + string info = "*** " + bytes + " (" + fieldInfo->renderJson(driver, &fieldEntry) + ")"; + + t->addSpecialExplanation(mfctEntry.offset + pos / 2, n / 2, KindOfData::CONTENT, Understanding::FULL, info.c_str()); +} diff --git a/src/manufacturer_specificities.h b/src/manufacturer_specificities.h index c43c013..0088359 100644 --- a/src/manufacturer_specificities.h +++ b/src/manufacturer_specificities.h @@ -80,4 +80,6 @@ bool mustDecryptDiehlRealData(const vector& frame); // Diehl: decrypt real data payload (LFSR) bool decryptDielhRealData(Telegram *t, vector &frame, vector::iterator &pos, const vector &meterkey); +void qdsExtractWalkByField(Telegram *t, Meter *driver, DVEntry &mfctEntry, int pos, int n, const string &key_s, const string &fieldName, Quantity quantity); + #endif diff --git a/src/wmbus.cc b/src/wmbus.cc index b04afc6..f2eec09 100644 --- a/src/wmbus.cc +++ b/src/wmbus.cc @@ -229,6 +229,18 @@ LIST_OF_MANUFACTURERS } +void Telegram::addId(const vector::iterator &pos) +{ + string id = tostrprintf("%02x%02x%02x%02x", *(pos+3), *(pos+2), *(pos+1), *(pos+0)); + ids.push_back(id); + if (idsc.empty()) { + idsc = id; + } + else { + idsc += "," + id; + } +} + void Telegram::print() { uchar a=0, b=0, c=0, d=0; @@ -960,9 +972,7 @@ bool Telegram::parseDLL(vector::iterator &pos) } } // Add dll_id to ids. - string id = tostrprintf("%02x%02x%02x%02x", *(pos+3), *(pos+2), *(pos+1), *(pos+0)); - ids.push_back(id); - idsc = id; + addId(pos); addExplanationAndIncrementPos(pos, 4, KindOfData::PROTOCOL, Understanding::FULL, "%02x%02x%02x%02x dll-id (%s)", *(pos+0), *(pos+1), *(pos+2), *(pos+3), ids.back().c_str()); @@ -1052,9 +1062,7 @@ bool Telegram::parseELL(vector::iterator &pos) ell_id_b[3] = *(pos+3); // Add ell_id to ids. - string id = tostrprintf("%02x%02x%02x%02x", *(pos+3), *(pos+2), *(pos+1), *(pos+0)); - ids.push_back(id); - idsc = idsc+","+id; + addId(pos); addExplanationAndIncrementPos(pos, 4, KindOfData::PROTOCOL, Understanding::FULL, "%02x%02x%02x%02x ell-id", ell_id_b[0], ell_id_b[1], ell_id_b[2], ell_id_b[3]); @@ -1464,9 +1472,7 @@ bool Telegram::parseLongTPL(std::vector::iterator &pos) } // Add the tpl_id to ids. - string id = tostrprintf("%02x%02x%02x%02x", *(pos+3), *(pos+2), *(pos+1), *(pos+0)); - ids.push_back(id); - idsc = idsc+","+id; + addId(pos); addExplanationAndIncrementPos(pos, 4, KindOfData::PROTOCOL, Understanding::FULL, "%02x%02x%02x%02x tpl-id (%02x%02x%02x%02x)", diff --git a/src/wmbus.h b/src/wmbus.h index e156abd..0fb0b40 100644 --- a/src/wmbus.h +++ b/src/wmbus.h @@ -536,6 +536,8 @@ public: bool parseHANHeader(vector &input_frame); bool parseHAN(vector &input_frame, MeterKeys *mk, bool warn); + void addId(const vector::iterator &pos); + void print(); // A vector of indentations and explanations, to be printed