diff --git a/.gitignore b/.gitignore index 56e5052..082375a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ build_debug build_arm/ build_arm_debug/ archive/ +testaes/ testoutput/ -*~ \ No newline at end of file +*~ diff --git a/Makefile b/Makefile index 07c4116..6f6e96e 100644 --- a/Makefile +++ b/Makefile @@ -150,7 +150,7 @@ METER_OBJS:=\ $(BUILD)/meter_multical302.o \ $(BUILD)/meter_multical403.o \ $(BUILD)/meter_multical603.o \ - $(BUILD)/meter_multical803.o \ + $(BUILD)/meter_multical803.o \ $(BUILD)/meter_omnipower.o \ $(BUILD)/meter_q400.o \ $(BUILD)/meter_qcaloric.o \ @@ -167,6 +167,7 @@ METER_OBJS:=\ $(BUILD)/meter_whe5x.o \ $(BUILD)/meter_sensostar.o \ $(BUILD)/meter_gransystems_ccx01.o \ + $(BUILD)/manufacturer_specificities.o \ $(BUILD)/printer.o \ $(BUILD)/rtlsdr.o \ $(BUILD)/serial.o \ diff --git a/simulations/simulation_driver_detection.txt b/simulations/simulation_driver_detection.txt index 5ca9aac..80df115 100644 --- a/simulations/simulation_driver_detection.txt +++ b/simulations/simulation_driver_detection.txt @@ -2,5 +2,5 @@ # that should be properly recognized and mapped to an existing driver. # I.e. check that a driver can be deduced from each telegram. -# izar water meter with weird identification combo: mfct: DME (0x11a5) type: A/D converter (0x19) ver: 0x00 +# izar water meter with address swapping: mfct: DME (0x11a5) type: water meter (0x07) ver: 0x78 telegram=||1944A511780758280019A2610F001328ACC7F38E4D812CFC0D36| diff --git a/simulations/simulation_izars.txt b/simulations/simulation_izars.txt index 236d4ab..6437f47 100644 --- a/simulations/simulation_izars.txt +++ b/simulations/simulation_izars.txt @@ -1,8 +1,4 @@ -# The design philosophy of the izar water meters, is never ever let -# a water meter report itself as a water meter.... - -# Test IZAR RC 868 I R4 PL water meter telegram, it reports itself as an oil meter... duh? -# But wmbusmeters politely translates this into water. +# Test IZAR RC 868 I R4 PL water meter telegram telegram=|1944304C72242421D401A2|013D4013DD8B46A4999C1293E582CC| {"media":"water","meter":"izar","name":"IzarWater","id":"21242472","total_m3":3.488,"last_month_total_m3":3.486,"last_month_measure_date":"2019-09-30","remaining_battery_life_y":14.5,"current_alarms":"meter_blocked,underflow","previous_alarms":"no_alarm","timestamp":"1111-11-11T11:11:11Z"} @@ -10,14 +6,14 @@ telegram=|1944304C72242421D401A2|013D4013DD8B46A4999C1293E582CC| # Test new version of IZAR telegram=|2944A511780729662366A20118001378D3B3DB8CEDD77731F25832AAF3DA8CADF9774EA673172E8C61F2| -{"media":"water","meter":"izar","name":"IzarWater2","id":"66290778","total_m3":16.76,"last_month_total_m3":11.84,"last_month_measure_date":"2019-11-30","remaining_battery_life_y":12,"current_alarms":"no_alarm","previous_alarms":"no_alarm","timestamp":"1111-11-11T11:11:11Z"} +{"media":"water","meter":"izar","name":"IzarWater2","id":"23662907","total_m3":16.76,"last_month_total_m3":11.84,"last_month_measure_date":"2019-11-30","remaining_battery_life_y":12,"current_alarms":"no_alarm","previous_alarms":"no_alarm","timestamp":"1111-11-11T11:11:11Z"} -# Yet another silly version of IZAR, come again, type 0x20 = Breaker (electricity) for a water meter! +# Yet another version of IZAR telegram=|1944A511780779194820A1|21170013355F8EDB2D03C6912B1E37 -{"media":"water","meter":"izar","name":"IzarWater3","id":"19790778","total_m3":4.366,"last_month_total_m3":0,"last_month_measure_date":"2020-12-31","remaining_battery_life_y":11.5,"current_alarms":"no_alarm","previous_alarms":"no_alarm","timestamp":"1111-11-11T11:11:11Z"} +{"media":"water","meter":"izar","name":"IzarWater3","id":"48197907","total_m3":4.366,"last_month_total_m3":0,"last_month_measure_date":"2020-12-31","remaining_battery_life_y":11.5,"current_alarms":"no_alarm","previous_alarms":"no_alarm","timestamp":"1111-11-11T11:11:11Z"} -# And another izar sillines, this time its a "heat meter" and a mfct specific tpl ci field a3. +# And another izar, with a mfct specific tpl ci field a3. telegram=|1944304c9c5824210c04a363140013716577ec59e8663ab0d31c| {"media":"water","meter":"izar","name":"IzarWater4","id":"2124589c","total_m3":38.944,"last_month_total_m3":38.691,"last_month_measure_date":"2021-02-01","remaining_battery_life_y":10,"current_alarms":"no_alarm","previous_alarms":"no_alarm","timestamp":"1111-11-11T11:11:11Z"} diff --git a/src/main.cc b/src/main.cc index b2301c1..553f375 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1249,7 +1249,7 @@ bool start(Configuration *config) t.parse(frame, &mk, false); // Try a best effort parse, do not print any warnings. t.print(); t.explainParse("(wmbus)",0); - logTelegram(t.frame, 0, 0); + logTelegram(t.original, t.frame, 0, 0); return true; }); } diff --git a/src/manufacturer_specificities.cc b/src/manufacturer_specificities.cc new file mode 100644 index 0000000..cdf1260 --- /dev/null +++ b/src/manufacturer_specificities.cc @@ -0,0 +1,116 @@ +/* + Copyright (C) 2021 Vincent Privat + + 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 +#include"manufacturers.h" +#include"manufacturer_specificities.h" + +std::set diehl_manufacturers = { + MANUFACTURER_DME, + MANUFACTURER_EWT, + MANUFACTURER_HYD, + MANUFACTURER_SAP, + MANUFACTURER_SPL +}; + +// Diehl: is "A field" coded as version/type/serialnumber instead of standard serialnumber/version/type? +DiehlAddressTransformMethod mustTransformDiehlAddress(uchar c_field, int m_field, uchar ci_field, int tpl_cfg) +{ + if (diehl_manufacturers.find(m_field) != diehl_manufacturers.end()) + { + if (c_field == 0x44 || c_field == 0x46) // SND_NR / SND_IR + { + switch (ci_field) { + case 0x71: // Alarm + return DiehlAddressTransformMethod::SWAPPING; // Real Data + case 0x7A: // EN 13757-3 Application Layer (short tplh) + if (((tpl_cfg >> 8) & 0x10) == 0x10) + { + return DiehlAddressTransformMethod::SWAPPING; // Real Data + } + break; // OMS + case 0xA0: // Manufacturer specific + case 0xA1: // Manufacturer specific + case 0xA2: // Manufacturer specific + case 0xA3: // Manufacturer specific + case 0xA4: // Manufacturer specific + case 0xA5: // Manufacturer specific + case 0xA6: // Manufacturer specific + case 0xA7: // Manufacturer specific + if (m_field == MANUFACTURER_SAP) + { + return DiehlAddressTransformMethod::SAP_PRIOS; // SAP PRIOS + } + return DiehlAddressTransformMethod::SWAPPING; // PRIOS + case 0xB0: // Manufacturer specific + if (m_field == MANUFACTURER_SAP) + { + return DiehlAddressTransformMethod::SAP_PRIOS_STANDARD; // SAP PRIOS STD + } + break; // Reserved + case 0xA8: // Manufacturer specific + case 0xA9: // Manufacturer specific + case 0xAA: // Manufacturer specific + case 0xAB: // Manufacturer specific + case 0xAC: // Manufacturer specific + case 0xAD: // Manufacturer specific + case 0xAE: // Manufacturer specific + case 0xAF: // Manufacturer specific + case 0xB4: // Manufacturer specific + case 0xB5: // Manufacturer specific + case 0xB6: // Manufacturer specific + case 0xB7: // Manufacturer specific + break; // Reserved + case 0xB1: // Manufacturer specific + case 0xB2: // Manufacturer specific + case 0xB3: // Manufacturer specific + return DiehlAddressTransformMethod::SWAPPING; // PRIOS SCR + default: + break; // OMS + } + } + } + return DiehlAddressTransformMethod::NONE; +} + +// Diehl: transform "A field" to make it compliant to standard +void transformDiehlAddress(std::vector& frame, DiehlAddressTransformMethod transform_method) +{ + if (transform_method == DiehlAddressTransformMethod::SWAPPING) + { + debug("(diehl) Pre-processing: swapping address field\n"); + uchar version = frame[4]; + uchar type = frame[5]; + for (int i = 4; i < 8; i++) + { + frame[i] = frame[i+1]; + } + frame[8] = version; + frame[9] = type; + } + else if (transform_method == DiehlAddressTransformMethod::SAP_PRIOS) + { + debug("(diehl) Pre-processing: setting device type to water meter for SAP PRIOS\n"); + frame[8] = 0x00; // version field is used by IZAR as part of meter id on 5 bytes instead of 4 + frame[9] = 0x07; // water meter + } + else if (transform_method == DiehlAddressTransformMethod::SAP_PRIOS_STANDARD) + { + warning("(diehl) Pre-processing: SAP PRIOS STANDARD transformation not implemented!\n"); // TODO + } +} + diff --git a/src/manufacturer_specificities.h b/src/manufacturer_specificities.h new file mode 100644 index 0000000..58c4f84 --- /dev/null +++ b/src/manufacturer_specificities.h @@ -0,0 +1,36 @@ +/* + Copyright (C) 2021 Vincent Privat + + 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 . +*/ + +#ifndef MANUFACTURER_SPECIFICITIES_H +#define MANUFACTURER_SPECIFICITIES_H + +#include"util.h" + +enum class DiehlAddressTransformMethod { + NONE, + SWAPPING, + SAP_PRIOS, + SAP_PRIOS_STANDARD +}; + +// Diehl: Is "A field" coded as version/type/serialnumber instead of standard serialnumber/version/type? +DiehlAddressTransformMethod mustTransformDiehlAddress(uchar c_field, int m_field, uchar ci_field, int tpl_cfg); + +// Diehl: swap "A field" to make it compliant to standard +void transformDiehlAddress(std::vector& frame, DiehlAddressTransformMethod method); + +#endif diff --git a/src/meter_izar.cc b/src/meter_izar.cc index 9197b30..2798e78 100644 --- a/src/meter_izar.cc +++ b/src/meter_izar.cc @@ -63,7 +63,7 @@ private: uint32_t convertKey(const char *hex); uint32_t convertKey(const vector &bytes); uint32_t uint32FromBytes(const vector &data, int offset, bool reverse = false); - vector decodePrios(const vector &payload, uint32_t key); + vector decodePrios(const vector &origin, const vector &payload, uint32_t key); double remaining_battery_life; uint16_t h0_year; @@ -95,14 +95,8 @@ MeterIzar::MeterIzar(MeterInfo &mi) : keys.push_back(convertKey(PRIOS_DEFAULT_KEY2)); } - // media 0x01 Oil meter? why? - // media 0x15 Hot water - // medua 0x66 Woot? - addLinkMode(LinkMode::T1); - // Meters with different versions exist, don't set any to avoid warnings - addPrint("total", Quantity::Volume, [&](Unit u){ return totalWaterConsumption(u); }, "The total water consumption recorded by this meter.", @@ -251,7 +245,7 @@ void MeterIzar::processContent(Telegram *t) vector decoded_content; for (auto& key : keys) { - decoded_content = decodePrios(frame, key); + decoded_content = decodePrios(t->original.empty() ? frame : t->original, frame, key); if (!decoded_content.empty()) break; } @@ -293,16 +287,13 @@ void MeterIzar::processContent(Telegram *t) alarms.sensor_fraud_previously = frame[13] >> 2 & 0x1; alarms.mechanical_fraud_currently = frame[13] >> 1 & 0x1; alarms.mechanical_fraud_previously = frame[13] & 0x1; - - // override incorrectly reported medium (oil) - t->dll_type = 7; } -vector MeterIzar::decodePrios(const vector &frame, uint32_t key) +vector MeterIzar::decodePrios(const vector &origin, const vector &frame, uint32_t key) { // modify seed key with header values - key ^= uint32FromBytes(frame, 2); // manufacturer + address[0-1] - key ^= uint32FromBytes(frame, 6); // address[2-3] + version + type + key ^= uint32FromBytes(origin, 2); // manufacturer + address[0-1] + key ^= uint32FromBytes(origin, 6); // address[2-3] + version + type key ^= uint32FromBytes(frame, 10); // ci + some more bytes from the telegram... int size = frame.size() - 15; diff --git a/src/meters.cc b/src/meters.cc index 2c99794..e1372b1 100644 --- a/src/meters.cc +++ b/src/meters.cc @@ -518,7 +518,7 @@ bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector &src, vector *target) char const hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A','B','C','D','E','F'}; -std::string bin2hex(vector &target) { +std::string bin2hex(const vector &target) { std::string str; for (size_t i = 0; i < target.size(); ++i) { const char ch = target[i]; @@ -863,12 +863,21 @@ void debugPayload(string intro, vector &payload, vector::iterator } } -void logTelegram(vector &parsed, int header_size, int suffix_size) +void logTelegram(vector &original, vector &parsed, int header_size, int suffix_size) { if (isLogTelegramsEnabled()) { + vector logged = parsed; + if (!original.empty()) + { + logged = vector(parsed); + for (unsigned int i = 0; i < original.size(); i++) + { + logged[i] = original[i]; + } + } time_t diff = time(NULL)-telegrams_start_time_; - string parsed_hex = bin2hex(parsed); + string parsed_hex = bin2hex(logged); string header = parsed_hex.substr(0, header_size*2); string content = parsed_hex.substr(header_size*2); if (suffix_size == 0) diff --git a/src/util.h b/src/util.h index beacbcb..a21b114 100644 --- a/src/util.h +++ b/src/util.h @@ -43,7 +43,7 @@ uchar reverse(uchar c); bool hex2bin(const char* src, std::vector *target); bool hex2bin(std::string &src, std::vector *target); bool hex2bin(std::vector &src, std::vector *target); -std::string bin2hex(std::vector &target); +std::string bin2hex(const std::vector &target); std::string bin2hex(std::vector::iterator data, std::vector::iterator end, int len); std::string safeString(std::vector &target); void strprintf(std::string &s, const char* fmt, ...); @@ -88,7 +88,7 @@ bool isLogTelegramsEnabled(); void debugPayload(std::string intro, std::vector &payload); void debugPayload(std::string intro, std::vector &payload, std::vector::iterator &pos); -void logTelegram(std::vector &parsed, int header_size, int suffix_size); +void logTelegram(std::vector &original, std::vector &parsed, int header_size, int suffix_size); enum class Alarm { diff --git a/src/wmbus.cc b/src/wmbus.cc index f973525..64f1b72 100644 --- a/src/wmbus.cc +++ b/src/wmbus.cc @@ -23,6 +23,7 @@ #include"wmbus_common_implementation.h" #include"wmbus_utils.h" #include"dvparser.h" +#include"manufacturer_specificities.h" #include #include #include @@ -1579,6 +1580,23 @@ bool Telegram::parseTPL(vector::iterator &pos) return false; } +void Telegram::preProcess() +{ + if (frame.size() >= 15) + { + uchar c_field = frame[1]; + int m_field = frame[3] <<8 | frame[2]; + uchar ci_field = frame[10]; + int tpl_cfg = frame[14] <<8 | frame[13]; + DiehlAddressTransformMethod diehl_method = mustTransformDiehlAddress(c_field, m_field, ci_field, tpl_cfg); + if (diehl_method != DiehlAddressTransformMethod::NONE) + { + original = vector(frame.begin(), frame.begin() + 10); + transformDiehlAddress(frame, diehl_method); + } + } +} + bool Telegram::parseHeader(vector &input_frame) { bool ok; @@ -1592,6 +1610,8 @@ bool Telegram::parseHeader(vector &input_frame) vector::iterator pos = frame.begin(); // Parsed accumulates parsed bytes. parsed.clear(); + // Fixes quirks from non-compliant meters to make telegram compatible with the standard + preProcess(); ok = parseDLL(pos); if (!ok) return false; @@ -1626,6 +1646,8 @@ bool Telegram::parse(vector &input_frame, MeterKeys *mk, bool warn) vector::iterator pos = frame.begin(); // Parsed accumulates parsed bytes. parsed.clear(); + // Fixes quirks from non-compliant meters to make telegram compatible with the standard + preProcess(); // ┌──────────────────────────────────────────────┐ // │ │ // │ Parse DLL Data Link Layer for Wireless MBUS. │ diff --git a/src/wmbus.h b/src/wmbus.h index 5aa64cd..925287f 100644 --- a/src/wmbus.h +++ b/src/wmbus.h @@ -464,12 +464,18 @@ struct Telegram string autoDetectPossibleDrivers(); + // part of original telegram bytes, only filled if pre-processing modifies it + vector original; + private: bool is_simulated_ {}; bool parser_warns_ = true; MeterKeys *meter_keys {}; + // Fixes quirks from non-compliant meters to make telegram compatible with the standard + void preProcess(); + bool parseDLL(std::vector::iterator &pos); bool parseELL(std::vector::iterator &pos); bool parseNWL(std::vector::iterator &pos); diff --git a/tests/test_izars.sh b/tests/test_izars.sh index 115950d..1447693 100755 --- a/tests/test_izars.sh +++ b/tests/test_izars.sh @@ -14,8 +14,8 @@ mkdir -p $TEST rm -f $LOGFILE METERS="IzarWater izar 21242472 NOKEY - IzarWater2 izar 66290778 NOKEY - IzarWater3 izar 19790778 NOKEY + IzarWater2 izar 23662907 NOKEY + IzarWater3 izar 48197907 NOKEY IzarWater4 izar 2124589c NOKEY" cat simulations/simulation_izars.txt | grep '^{' > $TEST/test_expected.txt @@ -50,23 +50,23 @@ cat > $LOGFILE_EXPECTED <