Now properly handles different dll id and tpl id.

pull/227/head
Fredrik Öhrström 2021-01-30 17:58:00 +01:00
rodzic b152fefc96
commit d50f13b240
11 zmienionych plików z 169 dodań i 51 usunięć

Wyświetl plik

@ -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

Wyświetl plik

@ -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);

Wyświetl plik

@ -23,6 +23,7 @@
#include<algorithm>
#include<memory.h>
#include<numeric>
#include<time.h>
#include<cmath>
@ -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<Print> &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<Print> &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<Print> &prints, vector
return s;
}
bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<uchar> input_frame, bool simulated, string *id)
bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<uchar> input_frame, bool simulated, string *ids)
{
Telegram t;
t.about = about;
@ -481,7 +493,7 @@ bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<ucha
if (simulated) t.markAsSimulated();
*id = t.id;
*ids = t.idsc;
if (!ok || !isTelegramForMe(&t))
{
@ -489,15 +501,15 @@ bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<ucha
return false;
}
verbose("(meter) %s %s handling telegram from %s\n", name().c_str(), meterName().c_str(), t.id.c_str());
verbose("(meter) %s %s handling telegram from %s\n", name().c_str(), meterName().c_str(), t.ids.back().c_str());
if (isDebugEnabled())
{
string msg = bin2hex(input_frame);
debug("(meter) %s %s \"%s\"\n", name().c_str(), t.id.c_str(), msg.c_str());
debug("(meter) %s %s \"%s\"\n", name().c_str(), t.ids.back().c_str(), msg.c_str());
}
ok = t.parse(input_frame, &meter_keys_);
ok = t.parse(input_frame, &meter_keys_, true);
if (!ok)
{
// Ignoring telegram since it could not be parsed.
@ -533,12 +545,33 @@ void MeterCommonImplementation::printMeter(Telegram *t,
*human_readable = concatFields(this, t, '\t', prints_, conversions_, true, selected_fields);
*fields = concatFields(this, t, separator, prints_, conversions_, false, selected_fields);
string mfct;
if (t->tpl_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<string> *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

Wyświetl plik

@ -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;

Wyświetl plik

@ -622,7 +622,22 @@ bool hasWildCard(string &mes)
return mes.find('*') != string::npos;
}
bool doesIdMatchExpressions(string& id, vector<string>& mes, bool *used_wildcard)
bool doesIdsMatchExpressions(vector<string> &ids, vector<string>& 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<string>& mes, bool *used_wildcard)
{
bool found_match = false;
bool found_negative_match = false;

Wyświetl plik

@ -104,8 +104,9 @@ void setAlarmShells(std::vector<std::string> &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<std::string>& ids, bool *used_wildcard);
bool doesIdMatchExpression(std::string id, std::string match_rule);
bool doesIdMatchExpressions(std::string id, std::vector<std::string>& match_rules, bool *used_wildcard);
bool doesIdsMatchExpressions(std::vector<std::string> &ids, std::vector<std::string>& match_rules, bool *used_wildcard);
bool isValidId(std::string id, bool accept_non_compliant);
bool isValidKey(std::string& key, MeterType mt);

Wyświetl plik

@ -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<uchar>::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<uchar>::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<uchar>::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<uchar>::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<uchar>::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<uchar>::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<uchar>::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<uchar>::iterator &pos)
bool Telegram::parseHeader(vector<uchar> &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<uchar>::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<uchar> &input_frame, MeterKeys *mk)
bool Telegram::parse(vector<uchar> &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<string> 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();

Wyświetl plik

@ -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<string> 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<uchar> &input_frame);
bool parse(vector<uchar> &input_frame, MeterKeys *mk);
void parserNoWarnings() { parser_warns_ = false; }
bool parse(vector<uchar> &input_frame, MeterKeys *mk, bool warn);
void print();
// A vector of indentations and explanations, to be printed

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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)