From d50f13b240abaef6ec3c4a7ad624ffa7ae4e4298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20=C3=96hrstr=C3=B6m?= Date: Sat, 30 Jan 2021 17:58:00 +0100 Subject: [PATCH] Now properly handles different dll id and tpl id. --- simulations/simulation_t1.txt | 8 ++-- src/main.cc | 3 +- src/meters.cc | 73 ++++++++++++++++++++++++++-------- src/printer.cc | 4 +- src/util.cc | 17 +++++++- src/util.h | 5 ++- src/wmbus.cc | 74 +++++++++++++++++++++++++++-------- src/wmbus.h | 10 +++-- test.sh | 5 +++ tests/test_aes.sh | 3 +- tests/test_listen_to_all.sh | 18 ++++++++- 11 files changed, 169 insertions(+), 51 deletions(-) diff --git a/simulations/simulation_t1.txt b/simulations/simulation_t1.txt index d80b70f..0807841 100644 --- a/simulations/simulation_t1.txt +++ b/simulations/simulation_t1.txt @@ -144,19 +144,19 @@ telegram=|2E4409077272727210077AD7102005CC2FF08D057E306D8C3078AE44AD6E3D37F8515B # Test electricity meter with eBZ wMB E01. telegram=|5B445A149922992202378C20F6900F002C25BC9E0000BF48954821BC508D72992299225A140102F6003007102F2F040330F92A0004A9FF01FF24000004A9FF026A29000004A9FF03460600000DFD11063132333435362F2F2F2F2F2F| -{"media":"radio converter (meter side)","meter":"ebzwmbe","name":"Elen1","id":"22992299","total_energy_consumption_kwh":2816.304,"current_power_consumption_kw":0.21679,"current_power_consumption_phase1_kw":0.09471,"current_power_consumption_phase2_kw":0.10602,"current_power_consumption_phase3_kw":0.01606,"customer":"123456","timestamp":"1111-11-11T11:11:11Z"} +{"media":"electricity","meter":"ebzwmbe","name":"Elen1","id":"22992299","total_energy_consumption_kwh":2816.304,"current_power_consumption_kw":0.21679,"current_power_consumption_phase1_kw":0.09471,"current_power_consumption_phase2_kw":0.10602,"current_power_consumption_phase3_kw":0.01606,"customer":"123456","timestamp":"1111-11-11T11:11:11Z"} |Elen1;22992299;2816.304000;0.216790;0.094710;0.106020;0.016060;1111-11-11 11:11.11 # Test electricity meter with ESYS-WM20 # static telegram -telegram=|7B4479169977997730378C208B900F002C25E4EF0A002EA98E7D58B3ADC57290779977991611028B005087102F2F|0DFD090F34302e3030562030303030303030300D790E31323334353637383839595345310DFD100AAAAAAAAAAAAAAAAAAAAA0D780E31323334353637383930594553312F2F2F2F2F2F2F2F2F2F2F| -{"media":"radio converter (meter side)","meter":"esyswm","name":"Elen2","id":"77997799","total_energy_consumption_kwh":0,"current_power_consumption_kw":0,"total_energy_production_kwh":0,"total_energy_consumption_tariff1_kwh":0,"total_energy_consumption_tariff2_kwh":0,"current_power_consumption_phase1_kw":0,"current_power_consumption_phase2_kw":0,"current_power_consumption_phase3_kw":0,"enhanced_id":"1ESY9887654321","version":"40.00V 00000000","location_hex":"AAAAAAAAAAAAAAAAAAAA","fabrication_no":"1SEY0987654321","timestamp":"1111-11-11T11:11:11Z"} +telegram=|7B4479169977997730378C208B900F002C25E4EF0A002EA98E7D58B3ADC57299779977991611028B005087102F2F|0DFD090F34302e3030562030303030303030300D790E31323334353637383839595345310DFD100AAAAAAAAAAAAAAAAAAAAA0D780E31323334353637383930594553312F2F2F2F2F2F2F2F2F2F2F| +{"media":"electricity","meter":"esyswm","name":"Elen2","id":"77997799","total_energy_consumption_kwh":0,"current_power_consumption_kw":0,"total_energy_production_kwh":0,"total_energy_consumption_tariff1_kwh":0,"total_energy_consumption_tariff2_kwh":0,"current_power_consumption_phase1_kw":0,"current_power_consumption_phase2_kw":0,"current_power_consumption_phase3_kw":0,"enhanced_id":"1ESY9887654321","version":"40.00V 00000000","location_hex":"AAAAAAAAAAAAAAAAAAAA","fabrication_no":"1SEY0987654321","timestamp":"1111-11-11T11:11:11Z"} |Elen2;77997799;0.000000;0.000000;0.000000;0.000000;0.000000;0.000000;0.000000;0.000000;1ESY9887654321;1111-11-11 11:11.11 # dynamic telegram telegram=|7B4479169977997730378C20F0900F002C2549EE0A0077C19D3D1A08ABCD729977997779161102F0005007102F2F|0702F5C3FA000000000007823C5407000000000000841004E081020084200415000000042938AB000004A9FF01FA0A000004A9FF02050A000004A9FF03389600002F2F2F2F2F2F2F2F2F2F2F2F2F| -{"media":"radio converter (meter side)","meter":"esyswm","name":"Elen2","id":"77997799","total_energy_consumption_kwh":1643.4165,"current_power_consumption_kw":0.43832,"total_energy_production_kwh":0.1876,"total_energy_consumption_tariff1_kwh":1643.2,"total_energy_consumption_tariff2_kwh":0.21,"current_power_consumption_phase1_kw":0.0281,"current_power_consumption_phase2_kw":0.02565,"current_power_consumption_phase3_kw":0.38456,"enhanced_id":"1ESY9887654321","version":"40.00V 00000000","location_hex":"AAAAAAAAAAAAAAAAAAAA","fabrication_no":"1SEY0987654321","timestamp":"1111-11-11T11:11:11Z"} +{"media":"electricity","meter":"esyswm","name":"Elen2","id":"77997799","total_energy_consumption_kwh":1643.4165,"current_power_consumption_kw":0.43832,"total_energy_production_kwh":0.1876,"total_energy_consumption_tariff1_kwh":1643.2,"total_energy_consumption_tariff2_kwh":0.21,"current_power_consumption_phase1_kw":0.0281,"current_power_consumption_phase2_kw":0.02565,"current_power_consumption_phase3_kw":0.38456,"enhanced_id":"1ESY9887654321","version":"40.00V 00000000","location_hex":"AAAAAAAAAAAAAAAAAAAA","fabrication_no":"1SEY0987654321","timestamp":"1111-11-11T11:11:11Z"} |Elen2;77997799;1643.416500;0.438320;0.187600;1643.200000;0.210000;0.028100;0.025650;0.384560;1ESY9887654321;1111-11-11 11:11.11 # Test electricity meter eHZ-P diff --git a/src/main.cc b/src/main.cc index 0028050..0059688 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1247,8 +1247,7 @@ bool start(Configuration *config) Telegram t; t.about = about; MeterKeys mk; - t.parserNoWarnings(); // Try a best effort parse, do not print any warnings. - t.parse(frame, &mk); + 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); diff --git a/src/meters.cc b/src/meters.cc index ca88739..2c99794 100644 --- a/src/meters.cc +++ b/src/meters.cc @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -79,15 +80,15 @@ struct MeterManagerImplementation : public virtual MeterManager bool handled = false; - string id; + string ids; for (auto &m : meters_) { - bool h = m->handleTelegram(about, data, simulated, &id); + bool h = m->handleTelegram(about, data, simulated, &ids); if (h) handled = true; } if (isVerboseEnabled() && !handled) { - verbose("(wmbus) telegram from %s ignored by all configured meters!\n", id.c_str()); + verbose("(wmbus) telegram from %s ignored by all configured meters!\n", ids.c_str()); } return handled; } @@ -266,10 +267,10 @@ LIST_OF_METERS bool MeterCommonImplementation::isTelegramForMe(Telegram *t) { - debug("(meter) %s: for me? %s\n", name_.c_str(), t->id.c_str()); + debug("(meter) %s: for me? %s\n", name_.c_str(), t->idsc.c_str()); bool used_wildcard = false; - bool id_match = doesIdMatchExpressions(t->id, ids_, &used_wildcard); + bool id_match = doesIdsMatchExpressions(t->ids, ids_, &used_wildcard); if (!id_match) { // The id must match. @@ -278,6 +279,10 @@ bool MeterCommonImplementation::isTelegramForMe(Telegram *t) } bool valid_driver = isMeterDriverValid(type_, t->dll_mfct, t->dll_type, t->dll_version); + if (!valid_driver && t->tpl_id_found) + { + valid_driver = isMeterDriverValid(type_, t->tpl_mfct, t->tpl_type, t->tpl_version); + } if (!valid_driver) { @@ -291,7 +296,7 @@ bool MeterCommonImplementation::isTelegramForMe(Telegram *t) // to many meters and some received matched meter telegrams are not from the right meter type, // ie their driver does not match. Lets just ignore telegrams that probably cannot be decoded properly. verbose("(meter) ignoring telegram from %s since it matched a wildcard id rule but driver does not match.\n", - t->id.c_str()); + t->idsc.c_str()); return false; } @@ -363,7 +368,14 @@ string concatAllFields(Meter *m, Telegram *t, char c, vector &prints, vec string s; s = ""; s += m->name() + c; - s += t->id + c; + if (t->ids.size() > 0) + { + s += t->ids.back() + c; + } + else + { + s += c; + } for (Print p : prints) { if (p.field) @@ -409,7 +421,7 @@ string concatFields(Meter *m, Telegram *t, char c, vector &prints, vector } if (field == "id") { - s += t->id + c; + s += t->ids.back() + c; continue; } if (field == "timestamp") @@ -473,7 +485,7 @@ string concatFields(Meter *m, Telegram *t, char c, vector &prints, vector return s; } -bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector input_frame, bool simulated, string *id) +bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector input_frame, bool simulated, string *ids) { Telegram t; t.about = about; @@ -481,7 +493,7 @@ bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vectortpl_id_found) + { + mfct = mediaTypeJSON(t->tpl_type, t->tpl_mfct); + } + else if (t->ell_id_found) + { + mfct = mediaTypeJSON(t->ell_type, t->ell_mfct); + } + else + { + mfct = mediaTypeJSON(t->dll_type, t->dll_mfct); + } + string s; s += "{"; - s += "\"media\":\""+mediaTypeJSON(t->dll_type, t->dll_mfct)+"\","; + s += "\"media\":\""+mfct+"\","; s += "\"meter\":\""+meterName()+"\","; s += "\"name\":\""+name()+"\","; - s += "\"id\":\""+t->id+"\","; + if (t->ids.size() > 0) + { + s += "\"id\":\""+t->ids.back()+"\","; + } + else + { + s += "\"id\":\"\","; + } for (Print p : prints_) { if (p.json) @@ -583,7 +616,14 @@ void MeterCommonImplementation::printMeter(Telegram *t, envs->push_back(string("METER_JSON=")+*json); envs->push_back(string("METER_TYPE=")+meterName()); envs->push_back(string("METER_NAME=")+name()); - envs->push_back(string("METER_ID=")+t->id); + if (t->ids.size() > 0) + { + envs->push_back(string("METER_ID=")+t->ids.back()); + } + else + { + envs->push_back(string("METER_ID=")); + } for (Print p : prints_) { @@ -683,7 +723,6 @@ ELLSecurityMode MeterCommonImplementation::expectedELLSecurityMode() void detectMeterDriver(int manufacturer, int media, int version, vector *drivers) { - drivers->clear(); #define X(TY,MA,ME,VE) { if (manufacturer == MA && (media == ME || ME == -1) && (version == VE || VE == -1)) { drivers->push_back(toMeterName(MeterType::TY)); }} METER_DETECTION #undef X diff --git a/src/printer.cc b/src/printer.cc index c92aff9..ffde9d1 100644 --- a/src/printer.cc +++ b/src/printer.cc @@ -91,10 +91,10 @@ void Printer::printFiles(Meter *meter, Telegram *t, string &human_readable, stri snprintf(filename, 127, "%s/%s", meterfiles_dir_.c_str(), meter->name().c_str()); break; case MeterFileNaming::Id: - snprintf(filename, 127, "%s/%s", meterfiles_dir_.c_str(), t->id.c_str()); + snprintf(filename, 127, "%s/%s", meterfiles_dir_.c_str(), t->ids.back().c_str()); break; case MeterFileNaming::NameId: - snprintf(filename, 127, "%s/%s-%s", meterfiles_dir_.c_str(), meter->name().c_str(), t->id.c_str()); + snprintf(filename, 127, "%s/%s-%s", meterfiles_dir_.c_str(), meter->name().c_str(), t->ids.back().c_str()); break; } string stamp; diff --git a/src/util.cc b/src/util.cc index cb08392..d906d2d 100644 --- a/src/util.cc +++ b/src/util.cc @@ -622,7 +622,22 @@ bool hasWildCard(string &mes) return mes.find('*') != string::npos; } -bool doesIdMatchExpressions(string& id, vector& mes, bool *used_wildcard) +bool doesIdsMatchExpressions(vector &ids, vector& mes, bool *used_wildcard) +{ + bool match = false; + for (string &id : ids) + { + if (doesIdMatchExpressions(id, mes, used_wildcard)) + { + match = true; + } + // Go through all ids even though there is an early match. + // This way we can see if theres an exact match later. + } + return match; +} + +bool doesIdMatchExpressions(string id, vector& mes, bool *used_wildcard) { bool found_match = false; bool found_negative_match = false; diff --git a/src/util.h b/src/util.h index 21d57c7..beacbcb 100644 --- a/src/util.h +++ b/src/util.h @@ -104,8 +104,9 @@ void setAlarmShells(std::vector &alarm_shells); bool isValidMatchExpression(std::string id, bool non_compliant); bool isValidMatchExpressions(std::string ids, bool non_compliant); -bool doesIdMatchExpression(std::string id, std::string match); -bool doesIdMatchExpressions(std::string& id, std::vector& ids, bool *used_wildcard); +bool doesIdMatchExpression(std::string id, std::string match_rule); +bool doesIdMatchExpressions(std::string id, std::vector& match_rules, bool *used_wildcard); +bool doesIdsMatchExpressions(std::vector &ids, std::vector& match_rules, bool *used_wildcard); bool isValidId(std::string id, bool accept_non_compliant); bool isValidKey(std::string& key, MeterType mt); diff --git a/src/wmbus.cc b/src/wmbus.cc index 3ffbe6d..dc91d96 100644 --- a/src/wmbus.cc +++ b/src/wmbus.cc @@ -238,6 +238,18 @@ void Telegram::print() notice(" ver: 0x%02x\n", dll_version); + if (tpl_id_found) + { + notice(" Concerning meter: %02x%02x%02x%02x\n", tpl_id_b[3],tpl_id_b[2],tpl_id_b[1],tpl_id_b[0]); + notice(" manufacturer: (%s) %s (0x%02x)\n", + manufacturerFlag(tpl_mfct).c_str(), + manufacturer(tpl_mfct).c_str(), + tpl_mfct); + notice(" type: %s (0x%02x)\n", mediaType(tpl_type, dll_mfct).c_str(), tpl_type); + + notice(" ver: 0x%02x\n", tpl_version); + + } if (about.device != "") { notice(" device: %s\n", about.device.c_str()); @@ -804,9 +816,12 @@ bool Telegram::parseDLL(vector::iterator &pos) dll_id[i] = *(pos+3-i); } } - strprintf(id, "%02x%02x%02x%02x", *(pos+3), *(pos+2), *(pos+1), *(pos+0)); + // 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; addExplanationAndIncrementPos(pos, 4, "%02x%02x%02x%02x dll-id (%s)", - *(pos+0), *(pos+1), *(pos+2), *(pos+3), id.c_str()); + *(pos+0), *(pos+1), *(pos+2), *(pos+3), ids.back().c_str()); dll_version = *(pos+0); dll_type = *(pos+1); @@ -893,6 +908,10 @@ bool Telegram::parseELL(vector::iterator &pos) ell_id_b[2] = *(pos+2); 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; addExplanationAndIncrementPos(pos, 4, "%02x%02x%02x%02x ell-id", ell_id_b[0], ell_id_b[1], ell_id_b[2], ell_id_b[3]); @@ -922,12 +941,10 @@ bool Telegram::parseELL(vector::iterator &pos) if (ell_sec_mode == ELLSecurityMode::AES_CTR) { - bool decrypt_ok = decrypt_ELL_AES_CTR(this, frame, pos, meter_keys->confidentiality_key); - // Actually this ctr decryption always succeeds, if wrong key, it will decrypt to garbage. - if (!decrypt_ok) + if (meter_keys) { - decryption_failed = true; - return true; + decrypt_ELL_AES_CTR(this, frame, pos, meter_keys->confidentiality_key); + // Actually this ctr decryption always succeeds, if wrong key, it will decrypt to garbage. } // Now the frame from pos and onwards has been decrypted, perhaps. } @@ -1199,7 +1216,7 @@ bool Telegram::parseTPLConfig(std::vector::iterator &pos) debugPayload("(wmbus) input to kdf for enc", input); - if (meter_keys->confidentiality_key.size() != 16) + if (meter_keys == NULL || meter_keys->confidentiality_key.size() != 16) { if (isSimulated()) { @@ -1256,6 +1273,10 @@ bool Telegram::parseLongTPL(std::vector::iterator &pos) tpl_id_b[2] = *(pos+2); tpl_id_b[3] = *(pos+3); + // 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; addExplanationAndIncrementPos(pos, 4, "%02x%02x%02x%02x tpl-id (%02x%02x%02x%02x)", tpl_id_b[0], tpl_id_b[1], tpl_id_b[2], tpl_id_b[3], tpl_id_b[3], tpl_id_b[2], tpl_id_b[1], tpl_id_b[0]); @@ -1322,6 +1343,7 @@ bool Telegram::potentiallyDecrypt(vector::iterator &pos) { if (tpl_sec_mode == TPLSecurityMode::AES_CBC_IV) { + if (!meter_keys) return false; bool ok = decrypt_TPL_AES_CBC_IV(this, frame, pos, meter_keys->confidentiality_key); if (!ok) return false; // Now the frame from pos and onwards has been decrypted. @@ -1350,7 +1372,7 @@ bool Telegram::potentiallyDecrypt(vector::iterator &pos) } else if (tpl_sec_mode == TPLSecurityMode::AES_CBC_NO_IV) { - if (!meter_keys->hasConfidentialityKey() && isSimulated()) + if (meter_keys == NULL || (!meter_keys->hasConfidentialityKey() && isSimulated())) { CHECK(2); addExplanationAndIncrementPos(pos, 2, "%02x%02x (already) decrypted check bytes", *(pos+0), *(pos+1)); @@ -1564,26 +1586,42 @@ bool Telegram::parseTPL(vector::iterator &pos) bool Telegram::parseHeader(vector &input_frame) { bool ok; + // Parsing the header is used to extract the ids, so that we can + // match the telegram towards any known ids and thus keys. + // No need to warn. + parser_warns_ = false; + decryption_failed = false; explanations.clear(); frame = input_frame; vector::iterator pos = frame.begin(); // Parsed accumulates parsed bytes. parsed.clear(); - // ┌──────────────────────────────────────────────┐ - // │ │ - // │ Parse DLL Data Link Layer for Wireless MBUS. │ - // │ │ - // └──────────────────────────────────────────────┘ - ok = parseDLL(pos); if (!ok) return false; + // At the worst, only the DLL is parsed. That is fine. + ok = parseELL(pos); + if (!ok) return true; + // Could not decrypt stop here. + if (decryption_failed) return true; + + ok = parseNWL(pos); + if (!ok) return true; + + ok = parseAFL(pos); + if (!ok) return true; + + ok = parseTPL(pos); + if (!ok) return true; + return true; } -bool Telegram::parse(vector &input_frame, MeterKeys *mk) +bool Telegram::parse(vector &input_frame, MeterKeys *mk, bool warn) { + parser_warns_ = warn; + decryption_failed = false; explanations.clear(); meter_keys = mk; assert(meter_keys != NULL); @@ -1663,6 +1701,10 @@ string Telegram::autoDetectPossibleDrivers() { vector drivers; detectMeterDriver(dll_mfct, dll_type, dll_version, &drivers); + if (tpl_id_found) + { + detectMeterDriver(tpl_mfct, tpl_type, tpl_version, &drivers); + } string possibles; for (string d : drivers) possibles = possibles+d+" "; if (possibles != "") possibles.pop_back(); diff --git a/src/wmbus.h b/src/wmbus.h index 746f29f..5aa64cd 100644 --- a/src/wmbus.h +++ b/src/wmbus.h @@ -345,8 +345,11 @@ struct Telegram // If a warning is printed mark this. bool triggered_warning {}; - // The meter address as a string usually printed on the meter. - string id; + // The different ids found, the first is th dll_id, ell_id, nwl_id, and the last is the tpl_id. + vector ids; + // Ids separated by commas + string idsc; + // If decryption failed, set this to true, to prevent further processing. bool decryption_failed {}; @@ -443,8 +446,7 @@ struct Telegram bool handled {}; // Set to true, when a meter has accepted the telegram. bool parseHeader(vector &input_frame); - bool parse(vector &input_frame, MeterKeys *mk); - void parserNoWarnings() { parser_warns_ = false; } + bool parse(vector &input_frame, MeterKeys *mk, bool warn); void print(); // A vector of indentations and explanations, to be printed diff --git a/test.sh b/test.sh index 7ffc2a4..be30a01 100755 --- a/test.sh +++ b/test.sh @@ -96,6 +96,11 @@ if [ "$?" != "0" ]; then RC="1"; fi tests/test_ignore_duplicates.sh $PROG if [ "$?" != "0" ]; then RC="1"; fi +./tests/test_match_dll_and_tpl_id.sh $PROG +if [ "$?" != "0" ]; then RC="1"; fi + +echo Slower tests... + tests/test_pipe.sh $PROG if [ "$?" != "0" ]; then RC="1"; fi diff --git a/tests/test_aes.sh b/tests/test_aes.sh index 2fa3704..d2c9cf7 100755 --- a/tests/test_aes.sh +++ b/tests/test_aes.sh @@ -15,8 +15,7 @@ cat simulations/simulation_aes.msg | grep '^[CT]' | tr -d '#' > $TEST/test_input cat $TEST/test_input.txt | $PROG --format=json "stdin:rtlwmbus" \ ApWater apator162 88888888 00000000000000000000000000000000 \ Vatten multical21 76348799 28F64A24988064A079AA2C807D6102AE \ - Wasser supercom587 77777777 5065747220486F6C79737A6577736B69 \ - > $TEST/test_output.txt 2> $TEST/test_stderr.txt + Wasser supercom587 77777777 5065747220486F6C79737A6577736B69 > $TEST/test_output.txt 2> $TEST/test_stderr.txt cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_response.txt diff $TEST/test_expected.txt $TEST/test_response.txt diff --git a/tests/test_listen_to_all.sh b/tests/test_listen_to_all.sh index 35fc906..6896f6e 100755 --- a/tests/test_listen_to_all.sh +++ b/tests/test_listen_to_all.sh @@ -79,7 +79,11 @@ Received telegram from: 88018801 manufacturer: (INE) INNOTAS Elektronik, Germany (0x25c5) type: Heat Cost Allocator (0x08) ver: 0x55 - driver: eurisii + Concerning meter: 88018801 + manufacturer: (INE) INNOTAS Elektronik, Germany (0x25c5) + type: Heat Cost Allocator (0x08) + ver: 0x55 + driver: eurisii eurisii Received telegram from: 00010204 manufacturer: (LAS) Lansen Systems, Sweden (0x3033) type: Smoke detector (0x1a) @@ -154,16 +158,28 @@ Received telegram from: 22992299 manufacturer: (EBZ) eBZ, Germany (0x145a) type: Radio converter (meter side) (0x37) ver: 0x02 + Concerning meter: 22992299 + manufacturer: (EBZ) eBZ, Germany (0x145a) + type: Electricity meter (0x02) + ver: 0x01 driver: ebzwmbe Received telegram from: 77997799 manufacturer: (ESY) EasyMeter (0x1679) type: Radio converter (meter side) (0x37) ver: 0x30 + Concerning meter: 77997799 + manufacturer: (ETY) Unknown (0x1699) + type: Electricity meter (0x02) + ver: 0x11 driver: esyswm Received telegram from: 77997799 manufacturer: (ESY) EasyMeter (0x1679) type: Radio converter (meter side) (0x37) ver: 0x30 + Concerning meter: 77997799 + manufacturer: (ESY) EasyMeter (0x1679) + type: Electricity meter (0x02) + ver: 0x11 driver: esyswm Received telegram from: 55995599 manufacturer: (EMH) EMH metering formerly EMH Elektrizitatszahler (0x15a8)