From 7e72fe0f3f1c12240ed1985ea0ba04796134231c Mon Sep 17 00:00:00 2001 From: weetmuts Date: Tue, 5 Mar 2019 21:19:05 +0100 Subject: [PATCH] Added support for multiple comma separated ids. --- README.md | 3 + simulations/simulation_multiple_qcalorics.txt | 6 ++ src/main.cc | 12 ++-- src/meter_iperl.cc | 28 +++----- src/meter_multical21.cc | 26 +++---- src/meter_multical302.cc | 27 +++----- src/meter_omnipower.cc | 23 +++---- src/meter_qcaloric.cc | 25 +++---- src/meter_supercom587.cc | 28 +++----- src/meters.cc | 68 +++++++++++++------ src/meters.h | 8 +-- src/meters_common_implementation.h | 16 ++--- src/printer.cc | 4 +- src/printer.h | 2 +- src/util.cc | 34 ++++++++-- src/util.h | 2 + test.sh | 3 +- tests/test_c1_meters.sh | 1 + tests/test_config1.sh | 3 +- tests/test_multiple_ids.sh | 64 +++++++++++++++++ tests/test_oneshot.sh | 22 ++++++ tests/test_shell.sh | 3 +- tests/test_t1_meters.sh | 3 +- wmbusmeters.1 | 2 +- 24 files changed, 266 insertions(+), 147 deletions(-) create mode 100644 simulations/simulation_multiple_qcalorics.txt create mode 100755 tests/test_multiple_ids.sh create mode 100755 tests/test_oneshot.sh diff --git a/README.md b/README.md index 0a1ad9f..f652845 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,9 @@ check with `tail -f /var/log/syslog` and `tail -f /var/log/wmbusmeters/wmbusmete The latest reading of the meter can also be found here: /var/log/wmbusmeters/meter_readings/MyTapWater +You can use several ids using `id=1111111,2222222,3333333` or you can listen to all +meters of a certain type `id=*`. + # Run using config files If you cannot install as a daemon, then you can also start diff --git a/simulations/simulation_multiple_qcalorics.txt b/simulations/simulation_multiple_qcalorics.txt new file mode 100644 index 0000000..f52ee5d --- /dev/null +++ b/simulations/simulation_multiple_qcalorics.txt @@ -0,0 +1,6 @@ +telegram=|314493441234567835087a740000200b6e2701004b6e450100426c5f2ccb086e790000c2086c7f21326cffff046d200b7422| +{"media":"heat_cost_allocation","meter":"qcaloric","name":"Element","id":"78563412","current_consumption_hca":127.000000,"set_date":"2018-12-31","consumption_at_set_date_hca":145.000000,"set_date_17":"2019-01-31","consumption_at_set_date_17_hca":79.000000,"error_date":"2127-15-31","device_date_time":"2019-02-20 11:32","timestamp":"1111-11-11T11:11:11Z"} +telegram=|314493441334567835087a740000200b6e2701004b6e450100426c5f2ccb086e790000c2086c7f21326cffff046d200b7422| +{"media":"heat_cost_allocation","meter":"qcaloric","name":"Element","id":"78563413","current_consumption_hca":127.000000,"set_date":"2018-12-31","consumption_at_set_date_hca":145.000000,"set_date_17":"2019-01-31","consumption_at_set_date_17_hca":79.000000,"error_date":"2127-15-31","device_date_time":"2019-02-20 11:32","timestamp":"1111-11-11T11:11:11Z"} +telegram=|314493441434567835087a740000200b6e2701004b6e450100426c5f2ccb086e790000c2086c7f21326cffff046d200b7422| +{"media":"heat_cost_allocation","meter":"qcaloric","name":"Element","id":"78563414","current_consumption_hca":127.000000,"set_date":"2018-12-31","consumption_at_set_date_hca":145.000000,"set_date_17":"2019-01-31","consumption_at_set_date_17_hca":79.000000,"error_date":"2127-15-31","device_date_time":"2019-02-20 11:32","timestamp":"1111-11-11T11:11:11Z"} diff --git a/src/main.cc b/src/main.cc index 67851ac..0adb4f6 100644 --- a/src/main.cc +++ b/src/main.cc @@ -37,7 +37,7 @@ using namespace std; -void oneshotCheck(Configuration *cmdline, SerialCommunicationManager *manager, Meter *meter, vector> &meters); +void oneshotCheck(Configuration *cmdline, SerialCommunicationManager *manager, Telegram *t, Meter *meter, vector> &meters); void startUsingCommandline(Configuration *cmdline); void startUsingConfigFiles(string root, bool is_daemon); void startDaemon(string pid_file); // Will use config files. @@ -277,7 +277,8 @@ void startUsingCommandline(Configuration *config) if (config->list_shell_envs) { string ignore1, ignore2, ignore3; vector envs; - meters.back()->printMeter("", + Telegram t; + meters.back()->printMeter(&t, &ignore1, &ignore2, config->separator, &ignore3, @@ -290,8 +291,8 @@ void startUsingCommandline(Configuration *config) } exit(0); } - meters.back()->onUpdate([&](string id, Meter* meter) { output->print(id,meter); }); -// meters.back()->onUpdate([&](string id, Meter* meter) { oneshotCheck(config, manager.get(), meter, meters); }); + meters.back()->onUpdate([&](Telegram*t,Meter* meter) { output->print(t,meter); }); + meters.back()->onUpdate([&](Telegram*t, Meter* meter) { oneshotCheck(config, manager.get(), t, meter, meters); }); } } else { notice("No meters configured. Printing id:s of all telegrams heard!\n\n"); @@ -314,7 +315,7 @@ void startUsingCommandline(Configuration *config) } } -void oneshotCheck(Configuration *config, SerialCommunicationManager *manager, Meter *meter, vector> &meters) +void oneshotCheck(Configuration *config, SerialCommunicationManager *manager, Telegram *t, Meter *meter, vector> &meters) { if (!config->oneshot) return; @@ -322,6 +323,7 @@ void oneshotCheck(Configuration *config, SerialCommunicationManager *manager, Me if (m->numUpdates() == 0) return; } // All meters have received at least one update! Stop! + verbose("(main) all meters have received at least one update, stopping.\n"); manager->stop(); } diff --git a/src/meter_iperl.cc b/src/meter_iperl.cc index c73b96b..679de6e 100644 --- a/src/meter_iperl.cc +++ b/src/meter_iperl.cc @@ -55,7 +55,7 @@ struct MeterIperl : public virtual WaterMeter, public virtual MeterCommonImpleme string timeLeaking(); string timeBursting(); - void printMeter(string id, + void printMeter(Telegram *t, string *human_readable, string *fields, char separator, string *json, @@ -70,8 +70,10 @@ private: }; MeterIperl::MeterIperl(WMBus *bus, string& name, string& id, string& key) : - MeterCommonImplementation(bus, name, id, key, IPERL_METER, MANUFACTURER_SEN, 0x16, LinkModeT1) + MeterCommonImplementation(bus, name, id, key, IPERL_METER, MANUFACTURER_SEN, LinkModeT1) { + addMedia(0x06); + addMedia(0x07); MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*)); } @@ -98,15 +100,7 @@ void MeterIperl::handleTelegram(Telegram *t) t->a_field_address[0], t->a_field_address[1], t->a_field_address[2], t->a_field_address[3]); - if (t->a_field_device_type != 0x07 && t->a_field_device_type != 0x06) { - warning("(%s) expected telegram for cold or warm water media, but got \"%s\"!\n", "iperl", - mediaType(t->m_field, t->a_field_device_type).c_str()); - } - - updateMedia(t->a_field_device_type); - - if (t->m_field != manufacturer() || - t->a_field_version != 0x68) { + if (t->a_field_version != 0x68) { warning("(%s) expected telegram from SEN meter with version 0x%02x, " "but got \"%s\" meter with version 0x%02x !\n", "iperl", 0x68, @@ -149,7 +143,7 @@ void MeterIperl::processContent(Telegram *t) t->addMoreExplanation(offset, " total consumption (%f m3)", total_water_consumption_); } -void MeterIperl::printMeter(string id, +void MeterIperl::printMeter(Telegram *t, string *human_readable, string *fields, char separator, string *json, @@ -164,7 +158,7 @@ void MeterIperl::printMeter(string id, "% 3.3f m3\t" "%s", name().c_str(), - id.c_str(), + t->id.c_str(), totalWaterConsumption(), datetimeOfUpdateHumanReadable().c_str()); @@ -176,7 +170,7 @@ void MeterIperl::printMeter(string id, "%f%c" "%s", name().c_str(), separator, - id.c_str(), separator, + t->id.c_str(), separator, totalWaterConsumption(), separator, datetimeOfUpdateRobot().c_str()); @@ -194,9 +188,9 @@ void MeterIperl::printMeter(string id, Q(total_m3,%f) QSE(timestamp,%s) "}", - mediaType(manufacturer(), media()).c_str(), + mediaType(manufacturer(), t->a_field_device_type).c_str(), name().c_str(), - id.c_str(), + t->id.c_str(), totalWaterConsumption(), datetimeOfUpdateRobot().c_str()); @@ -204,7 +198,7 @@ void MeterIperl::printMeter(string id, envs->push_back(string("METER_JSON=")+*json); envs->push_back(string("METER_TYPE=iperl")); - envs->push_back(string("METER_ID=")+id); + envs->push_back(string("METER_ID=")+t->id); envs->push_back(string("METER_TOTAL_M3=")+to_string(totalWaterConsumption())); envs->push_back(string("METER_TIMESTAMP=")+datetimeOfUpdateRobot()); } diff --git a/src/meter_multical21.cc b/src/meter_multical21.cc index 81a8f39..3e28cfb 100644 --- a/src/meter_multical21.cc +++ b/src/meter_multical21.cc @@ -77,7 +77,7 @@ struct MeterMultical21 : public virtual WaterMeter, public virtual MeterCommonIm string timeLeaking(); string timeBursting(); - void printMeter(string id, + void printMeter(Telegram *t, string *human_readable, string *fields, char separator, string *json, @@ -105,8 +105,10 @@ private: }; MeterMultical21::MeterMultical21(WMBus *bus, string& name, string& id, string& key, MeterType mt) : - MeterCommonImplementation(bus, name, id, key, mt, MANUFACTURER_KAM, 0x16, LinkModeC1) + MeterCommonImplementation(bus, name, id, key, mt, MANUFACTURER_KAM, LinkModeC1) { + addMedia(0x16); // Water media + if (type() == MULTICAL21_METER) { expected_version_ = 0x1b; meter_name_ = "multical21"; @@ -200,13 +202,7 @@ void MeterMultical21::handleTelegram(Telegram *t) t->a_field_address[0], t->a_field_address[1], t->a_field_address[2], t->a_field_address[3]); - if (t->a_field_device_type != 0x16) { - warning("(%s) expected telegram for water media, but got \"%s\"!\n", meter_name_, - mediaType(t->m_field, t->a_field_device_type).c_str()); - } - - if (t->m_field != manufacturer() || - t->a_field_version != expected_version_) { + if (t->a_field_version != expected_version_) { warning("(%s) expected telegram from KAM meter with version 0x%02x, " "but got \"%s\" meter with version 0x%02x !\n", meter_name_, expected_version_, @@ -496,7 +492,7 @@ string MeterMultical21::decodeTime(int time) } } -void MeterMultical21::printMeter(string id, +void MeterMultical21::printMeter(Telegram *t, string *human_readable, string *fields, char separator, string *json, @@ -525,7 +521,7 @@ void MeterMultical21::printMeter(string id, snprintf(buf, sizeof(buf)-1, "%s\t%s\t% 3.3f m3\t% 3.3f m3\t% 3.3f m3/h\t%s°C\t%s°C\t%s\t%s", name().c_str(), - id.c_str(), + t->id.c_str(), totalWaterConsumption(), targetWaterConsumption(), maxFlow(), @@ -538,7 +534,7 @@ void MeterMultical21::printMeter(string id, snprintf(buf, sizeof(buf)-1, "%s%c" "%s%c" "%f%c" "%f%c" "%f%c" "%.0f%c" "%.0f%c" "%s%c" "%s", name().c_str(), separator, - id.c_str(), separator, + t->id.c_str(), separator, totalWaterConsumption(), separator, targetWaterConsumption(), separator, maxFlow(), separator, @@ -570,10 +566,10 @@ void MeterMultical21::printMeter(string id, QS(time_bursting,%s) QSE(timestamp,%s) "}", - mediaType(manufacturer(), media()).c_str(), + mediaType(manufacturer(), t->a_field_device_type).c_str(), meter_name_, name().c_str(), - id.c_str(), + t->id.c_str(), totalWaterConsumption(), targetWaterConsumption(), maxFlow(), @@ -590,7 +586,7 @@ void MeterMultical21::printMeter(string id, envs->push_back(string("METER_JSON=")+*json); envs->push_back(string("METER_TYPE=")+meter_name_); - envs->push_back(string("METER_ID=")+id); + envs->push_back(string("METER_ID=")+t->id); envs->push_back(string("METER_TOTAL_M3=")+to_string(totalWaterConsumption())); envs->push_back(string("METER_TARGET_M3=")+to_string(targetWaterConsumption())); envs->push_back(string("METER_MAX_FLOW_M3H=")+to_string(maxFlow())); diff --git a/src/meter_multical302.cc b/src/meter_multical302.cc index 3a6e13e..f710fbc 100644 --- a/src/meter_multical302.cc +++ b/src/meter_multical302.cc @@ -35,7 +35,7 @@ struct MeterMultical302 : public virtual HeatMeter, public virtual MeterCommonIm double totalVolume(); - void printMeter(string id, + void printMeter(Telegram *t, string *human_readable, string *fields, char separator, string *json, @@ -51,8 +51,9 @@ private: }; MeterMultical302::MeterMultical302(WMBus *bus, string& name, string& id, string& key) : - MeterCommonImplementation(bus, name, id, key, MULTICAL302_METER, MANUFACTURER_KAM, 0x04, LinkModeC1) + MeterCommonImplementation(bus, name, id, key, MULTICAL302_METER, MANUFACTURER_KAM, LinkModeC1) { + addMedia(0x04); // Heat media MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*)); } @@ -83,18 +84,6 @@ void MeterMultical302::handleTelegram(Telegram *t) { t->a_field_address[0], t->a_field_address[1], t->a_field_address[2], t->a_field_address[3]); - if (t->a_field_device_type != 0x04) { - warning("(multical302) expected telegram for heat media, but got \"%s\"!\n", - mediaType(t->m_field, t->a_field_device_type).c_str()); - } - - /* - if (t->m_field != manufacturer() || - t->a_field_version != 0x1b) { - warning("(multical302) expected telegram from KAM meter with version 0x1b, but got \"%s\" version 0x2x !\n", - manufacturerFlag(t->m_field).c_str(), t->a_field_version); - }*/ - if (useAes()) { vector aeskey = key(); decryptMode1_AES_CTR(t, aeskey); @@ -183,7 +172,7 @@ unique_ptr createMultical302(WMBus *bus, string& name, string& id, st return unique_ptr(new MeterMultical302(bus,name,id,key)); } -void MeterMultical302::printMeter(string id, +void MeterMultical302::printMeter(Telegram *t, string *human_readable, string *fields, char separator, string *json, @@ -194,7 +183,7 @@ void MeterMultical302::printMeter(string id, snprintf(buf, sizeof(buf)-1, "%s\t%s\t% 3.3f kwh\t% 3.3f m3\t% 3.3f kwh\t%s", name().c_str(), - id.c_str(), + t->id.c_str(), totalEnergyConsumption(), totalVolume(), currentPowerConsumption(), @@ -204,7 +193,7 @@ void MeterMultical302::printMeter(string id, snprintf(buf, sizeof(buf)-1, "%s%c%s%c%f%c%f%c%f%c%s", name().c_str(), separator, - id.c_str(), separator, + t->id.c_str(), separator, totalEnergyConsumption(), separator, totalVolume(), separator, currentPowerConsumption(), separator, @@ -227,7 +216,7 @@ void MeterMultical302::printMeter(string id, QSE(timestamp,%s) "}", name().c_str(), - id.c_str(), + t->id.c_str(), totalEnergyConsumption(), totalVolume(), currentPowerConsumption(), @@ -237,7 +226,7 @@ void MeterMultical302::printMeter(string id, envs->push_back(string("METER_JSON=")+*json); envs->push_back(string("METER_TYPE=multical302")); - envs->push_back(string("METER_ID=")+id); + envs->push_back(string("METER_ID=")+t->id); envs->push_back(string("METER_TOTAL_KWH=")+to_string(totalEnergyConsumption())); envs->push_back(string("METER_TOTAL_VOLUME_M3=")+to_string(totalVolume())); envs->push_back(string("METER_CURRENT_KW=")+to_string(currentPowerConsumption())); diff --git a/src/meter_omnipower.cc b/src/meter_omnipower.cc index 89b4dfd..c112045 100644 --- a/src/meter_omnipower.cc +++ b/src/meter_omnipower.cc @@ -35,7 +35,7 @@ struct MeterOmnipower : public virtual ElectricityMeter, public virtual MeterCom double totalEnergyConsumption(); double currentPowerConsumption(); - void printMeter(string id, + void printMeter(Telegram *t, string *human_readable, string *fields, char separator, string *json, @@ -50,8 +50,9 @@ private: }; MeterOmnipower::MeterOmnipower(WMBus *bus, string& name, string& id, string& key) : - MeterCommonImplementation(bus, name, id, key, OMNIPOWER_METER, MANUFACTURER_KAM, 0x04, LinkModeC1) + MeterCommonImplementation(bus, name, id, key, OMNIPOWER_METER, MANUFACTURER_KAM, LinkModeC1) { + addMedia(0x02); MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*)); } @@ -77,13 +78,7 @@ void MeterOmnipower::handleTelegram(Telegram *t) { t->a_field_address[0], t->a_field_address[1], t->a_field_address[2], t->a_field_address[3]); - if (t->a_field_device_type != 0x02) { - warning("(omnipower) expected telegram for electricity media, but got \"%s\"!\n", - mediaType(t->m_field, t->a_field_device_type).c_str()); - } - - if (t->m_field != manufacturer() || - t->a_field_version != 0x01) { + if (t->a_field_version != 0x01) { warning("(omnipower) expected telegram from KAM meter with version 0x01, but got \"%s\" version 0x2x !\n", manufacturerFlag(t->m_field).c_str(), t->a_field_version); } @@ -124,7 +119,7 @@ unique_ptr createOmnipower(WMBus *bus, string& name, string& i return unique_ptr(new MeterOmnipower(bus,name,id,key)); } -void MeterOmnipower::printMeter(string id, +void MeterOmnipower::printMeter(Telegram *t, string *human_readable, string *fields, char separator, string *json, @@ -136,7 +131,7 @@ void MeterOmnipower::printMeter(string id, snprintf(buf, sizeof(buf)-1, "%s\t%s\t% 3.3f kwh\t% 3.3f kwh\t%s", name().c_str(), - id.c_str(), + t->id.c_str(), totalEnergyConsumption(), currentPowerConsumption(), datetimeOfUpdateHumanReadable().c_str()); @@ -145,7 +140,7 @@ void MeterOmnipower::printMeter(string id, snprintf(buf, sizeof(buf)-1, "%s%c%s%c%f%c%f%c%s", name().c_str(), separator, - id.c_str(), separator, + t->id.c_str(), separator, totalEnergyConsumption(), separator, currentPowerConsumption(), separator, datetimeOfUpdateRobot().c_str()); @@ -166,7 +161,7 @@ void MeterOmnipower::printMeter(string id, QSE(timestamp,%s) "}", name().c_str(), - id.c_str(), + t->id.c_str(), totalEnergyConsumption(), currentPowerConsumption(), datetimeOfUpdateRobot().c_str()); @@ -175,7 +170,7 @@ void MeterOmnipower::printMeter(string id, envs->push_back(string("METER_JSON=")+*json); envs->push_back(string("METER_TYPE=omnipower")); - envs->push_back(string("METER_ID=")+id); + envs->push_back(string("METER_ID=")+t->id); envs->push_back(string("METER_TOTAL_KWH=")+to_string(totalEnergyConsumption())); envs->push_back(string("METER_CURRENT_KW=")+to_string(currentPowerConsumption())); envs->push_back(string("METER_TIMESTAMP=")+datetimeOfUpdateRobot()); diff --git a/src/meter_qcaloric.cc b/src/meter_qcaloric.cc index a2e7527..34cc865 100644 --- a/src/meter_qcaloric.cc +++ b/src/meter_qcaloric.cc @@ -36,7 +36,7 @@ struct MeterQCaloric : public virtual HeatCostMeter, public virtual MeterCommonI string setDate(); double consumptionAtSetDate(); - void printMeter(string id, + void printMeter(Telegram *t, string *human_readable, string *fields, char separator, string *json, @@ -60,8 +60,9 @@ private: }; MeterQCaloric::MeterQCaloric(WMBus *bus, string& name, string& id, string& key) : - MeterCommonImplementation(bus, name, id, key, QCALORIC_METER, MANUFACTURER_QDS, 0x08, LinkModeC1) + MeterCommonImplementation(bus, name, id, key, QCALORIC_METER, MANUFACTURER_QDS, LinkModeC1) { + addMedia(0x08); MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*)); } @@ -92,14 +93,8 @@ void MeterQCaloric::handleTelegram(Telegram *t) { t->a_field_address[0], t->a_field_address[1], t->a_field_address[2], t->a_field_address[3]); - if (t->a_field_device_type != 0x08) { - warning("(qcaloric) expected telegram for heat cost allocator, but got \"%s\"!\n", - mediaType(t->m_field, t->a_field_device_type).c_str()); - } - - if (t->m_field != manufacturer() || - t->a_field_version != 0x35) { - warning("(qcaloric) expected telegram from QDS meter with version 0x01, but got \"%s\" version 0x%02x !\n", + if (t->a_field_version != 0x35) { + warning("(qcaloric) expected telegram from QDS meter with version 0x35, but got \"%s\" version 0x%02x !\n", manufacturerFlag(t->m_field).c_str(), t->a_field_version); } @@ -227,7 +222,7 @@ unique_ptr createQCaloric(WMBus *bus, string& name, string& id, s return unique_ptr(new MeterQCaloric(bus,name,id,key)); } -void MeterQCaloric::printMeter(string id, +void MeterQCaloric::printMeter(Telegram *t, string *human_readable, string *fields, char separator, string *json, @@ -239,7 +234,7 @@ void MeterQCaloric::printMeter(string id, snprintf(buf, sizeof(buf)-1, "%s\t%s\t% 3.0f hca\t%s\t% 3.0f hca\t%s", name().c_str(), - id.c_str(), + t->id.c_str(), currentConsumption(), setDate().c_str(), consumptionAtSetDate(), @@ -249,7 +244,7 @@ void MeterQCaloric::printMeter(string id, snprintf(buf, sizeof(buf)-1, "%s%c%s%c%f%c%s%c%f%c%s", name().c_str(), separator, - id.c_str(), separator, + t->id.c_str(), separator, currentConsumption(), separator, setDate().c_str(), separator, consumptionAtSetDate(), separator, @@ -276,7 +271,7 @@ void MeterQCaloric::printMeter(string id, QSE(timestamp,%s) "}", name().c_str(), - id.c_str(), + t->id.c_str(), currentConsumption(), setDate().c_str(), consumptionAtSetDate(), @@ -290,7 +285,7 @@ void MeterQCaloric::printMeter(string id, envs->push_back(string("METER_JSON=")+*json); envs->push_back(string("METER_TYPE=qcaloric")); - envs->push_back(string("METER_ID=")+id); + envs->push_back(string("METER_ID=")+t->id); envs->push_back(string("METER_CURRENT_CONSUMPTION_HCA=")+to_string(currentConsumption())); envs->push_back(string("METER_SET_DATE=")+setDate()); envs->push_back(string("METER_CONSUMPTION_AT_SET_DATE_HCA=")+to_string(consumptionAtSetDate())); diff --git a/src/meter_supercom587.cc b/src/meter_supercom587.cc index b227794..db7a1b9 100644 --- a/src/meter_supercom587.cc +++ b/src/meter_supercom587.cc @@ -54,7 +54,7 @@ struct MeterSupercom587 : public virtual WaterMeter, public virtual MeterCommonI string timeLeaking(); string timeBursting(); - void printMeter(string id, + void printMeter(Telegram *t, string *human_readable, string *fields, char separator, string *json, @@ -69,8 +69,10 @@ private: }; MeterSupercom587::MeterSupercom587(WMBus *bus, string& name, string& id, string& key) : - MeterCommonImplementation(bus, name, id, key, SUPERCOM587_METER, MANUFACTURER_SON, 0x16, LinkModeT1) + MeterCommonImplementation(bus, name, id, key, SUPERCOM587_METER, MANUFACTURER_SON, LinkModeT1) { + addMedia(0x06); + addMedia(0x07); MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*)); } @@ -97,15 +99,7 @@ void MeterSupercom587::handleTelegram(Telegram *t) t->a_field_address[0], t->a_field_address[1], t->a_field_address[2], t->a_field_address[3]); - if (t->a_field_device_type != 0x07 && t->a_field_device_type != 0x06) { - warning("(%s) expected telegram for cold or warm water media, but got \"%s\"!\n", "supercom587", - mediaType(t->m_field, t->a_field_device_type).c_str()); - } - - updateMedia(t->a_field_device_type); - - if (t->m_field != manufacturer() || - t->a_field_version != 0x3c) { + if (t->a_field_version != 0x3c) { warning("(%s) expected telegram from SON meter with version 0x%02x, " "but got \"%s\" meter with version 0x%02x !\n", "supercom587", 0x3c, @@ -144,7 +138,7 @@ void MeterSupercom587::processContent(Telegram *t) t->addMoreExplanation(offset, " total consumption (%f m3)", total_water_consumption_); } -void MeterSupercom587::printMeter(string id, +void MeterSupercom587::printMeter(Telegram *t, string *human_readable, string *fields, char separator, string *json, @@ -159,7 +153,7 @@ void MeterSupercom587::printMeter(string id, "% 3.3f m3\t" "%s", name().c_str(), - id.c_str(), + t->id.c_str(), totalWaterConsumption(), datetimeOfUpdateHumanReadable().c_str()); @@ -171,7 +165,7 @@ void MeterSupercom587::printMeter(string id, "%f%c" "%s", name().c_str(), separator, - id.c_str(), separator, + t->id.c_str(), separator, totalWaterConsumption(), separator, datetimeOfUpdateRobot().c_str()); @@ -189,9 +183,9 @@ void MeterSupercom587::printMeter(string id, Q(total_m3,%f) QSE(timestamp,%s) "}", - mediaType(manufacturer(), media()).c_str(), + mediaType(manufacturer(), t->a_field_device_type).c_str(), name().c_str(), - id.c_str(), + t->id.c_str(), totalWaterConsumption(), datetimeOfUpdateRobot().c_str()); @@ -199,7 +193,7 @@ void MeterSupercom587::printMeter(string id, envs->push_back(string("METER_JSON=")+*json); envs->push_back(string("METER_TYPE=supercom587")); - envs->push_back(string("METER_ID=")+id); + envs->push_back(string("METER_ID=")+t->id); envs->push_back(string("METER_TOTAL_M3=")+to_string(totalWaterConsumption())); envs->push_back(string("METER_TIMESTAMP=")+datetimeOfUpdateRobot()); } diff --git a/src/meters.cc b/src/meters.cc index d0100bf..fa3ce84 100644 --- a/src/meters.cc +++ b/src/meters.cc @@ -21,13 +21,13 @@ #include MeterCommonImplementation::MeterCommonImplementation(WMBus *bus, string& name, string& id, string& key, - MeterType type, int manufacturer, int media, + MeterType type, int manufacturer, LinkMode required_link_mode) : - type_(type), manufacturer_(manufacturer), media_(media), name_(name), bus_(bus), + type_(type), manufacturer_(manufacturer), name_(name), bus_(bus), required_link_mode_(required_link_mode) { use_aes_ = true; - id_ = id; + ids_ = splitIds(id); if (key.length() == 0) { use_aes_ = false; } else { @@ -45,14 +45,19 @@ int MeterCommonImplementation::manufacturer() return manufacturer_; } -int MeterCommonImplementation::media() +vector MeterCommonImplementation::media() { return media_; } -string MeterCommonImplementation::id() +void MeterCommonImplementation::addMedia(int m) { - return id_; + media_.push_back(m); +} + +vector MeterCommonImplementation::ids() +{ + return ids_; } string MeterCommonImplementation::name() @@ -70,7 +75,7 @@ LinkMode MeterCommonImplementation::requiredLinkMode() return required_link_mode_; } -void MeterCommonImplementation::onUpdate(function cb) +void MeterCommonImplementation::onUpdate(function cb) { on_update_.push_back(cb); } @@ -125,16 +130,46 @@ LinkMode toMeterLinkMode(string& type) bool MeterCommonImplementation::isTelegramForMe(Telegram *t) { - if (manufacturer_ != 0 && t->m_field != manufacturer_) { + debug("(meter) %s: for me? %s\n", name_.c_str(), t->id.c_str()); + + bool id_match = false; + for (auto id : ids_) + { + if (id == t->id) { + id_match = true; + break; + } + if (id == "*") { + id_match = true; + break; + } + } + + if (!id_match) { + debug("(meter) %s: not for me: not my id\n", name_.c_str()); return false; } - if (id_ == t->id) { - return true; + + if (manufacturer_ != 0 && t->m_field != manufacturer_) { + debug("(meter) %s: not for me: manufacturer differs\n", name_.c_str()); + return false; } - if (id_ == "*") { - return true; + + bool media_match = false; + for (auto m : media_) { + if (m == t->a_field_device_type) { + media_match = true; + break; + } } - return false; + + if (!media_match) { + debug("(meter) %s: not for me: no media match\n", name_.c_str()); + return false; + } + + debug("(meter) %s: yes for me\n", name_.c_str()); + return true; } bool MeterCommonImplementation::useAes() @@ -171,11 +206,6 @@ void MeterCommonImplementation::triggerUpdate(Telegram *t) { datetime_of_update_ = time(NULL); num_updates_++; - for (auto &cb : on_update_) if (cb) cb(t->id, this); + for (auto &cb : on_update_) if (cb) cb(t, this); t->handled = true; } - -void MeterCommonImplementation::updateMedia(int media) -{ - media_ = media; -} diff --git a/src/meters.h b/src/meters.h index a75b78a..6bc897c 100644 --- a/src/meters.h +++ b/src/meters.h @@ -37,21 +37,21 @@ using namespace std; typedef unsigned char uchar; struct Meter { - virtual string id() = 0; + virtual vector ids() = 0; virtual string name() = 0; virtual MeterType type() = 0; virtual int manufacturer() = 0; - virtual int media() = 0; + virtual vector media() = 0; virtual WMBus *bus() = 0; virtual LinkMode requiredLinkMode() = 0; virtual string datetimeOfUpdateHumanReadable() = 0; virtual string datetimeOfUpdateRobot() = 0; - virtual void onUpdate(function cb) = 0; + virtual void onUpdate(function cb) = 0; virtual int numUpdates() = 0; - virtual void printMeter(string id, + virtual void printMeter(Telegram *t, string *human_readable, string *fields, char separator, string *json, diff --git a/src/meters_common_implementation.h b/src/meters_common_implementation.h index a8ec919..82895a8 100644 --- a/src/meters_common_implementation.h +++ b/src/meters_common_implementation.h @@ -24,18 +24,18 @@ struct MeterCommonImplementation : public virtual Meter { - string id(); + vector ids(); string name(); MeterType type(); int manufacturer(); - int media(); + vector media(); WMBus *bus(); LinkMode requiredLinkMode(); string datetimeOfUpdateHumanReadable(); string datetimeOfUpdateRobot(); - void onUpdate(function cb); + void onUpdate(function cb); int numUpdates(); bool isTelegramForMe(Telegram *t); @@ -47,7 +47,7 @@ struct MeterCommonImplementation : public virtual Meter uint16_t getRecordAsUInt16(std::string record); MeterCommonImplementation(WMBus *bus, string& name, string& id, string& key, - MeterType type, int manufacturer, int media, + MeterType type, int manufacturer, LinkMode required_link_mode); ~MeterCommonImplementation() = default; @@ -55,18 +55,18 @@ struct MeterCommonImplementation : public virtual Meter protected: void triggerUpdate(Telegram *t); - void updateMedia(int media); + void addMedia(int media); private: MeterType type_ {}; int manufacturer_ {}; - int media_ {}; + vector media_ {}; string name_; - string id_; + vector ids_; vector key_; WMBus *bus_ {}; - vector> on_update_; + vector> on_update_; int num_updates_ {}; bool use_aes_ {}; time_t datetime_of_update_ {}; diff --git a/src/printer.cc b/src/printer.cc index 579d702..357567e 100644 --- a/src/printer.cc +++ b/src/printer.cc @@ -36,13 +36,13 @@ Printer::Printer(bool json, bool fields, char separator, overwrite_ = overwrite; } -void Printer::print(string id, Meter *meter) +void Printer::print(Telegram *t, Meter *meter) { string human_readable, fields, json; vector envs; bool printed = false; - meter->printMeter(id, &human_readable, &fields, separator_, &json, &envs); + meter->printMeter(t, &human_readable, &fields, separator_, &json, &envs); if (shell_cmdlines_.size() > 0) { printShells(meter, envs); diff --git a/src/printer.h b/src/printer.h index d33fe90..ddf1708 100644 --- a/src/printer.h +++ b/src/printer.h @@ -29,7 +29,7 @@ struct Printer { vector shell_cmdlines, bool overwrite); - void print(string id, Meter *meter); + void print(Telegram *t, Meter *meter); private: diff --git a/src/util.cc b/src/util.cc index a8aed25..421ee05 100644 --- a/src/util.cc +++ b/src/util.cc @@ -303,12 +303,17 @@ void debug(const char* fmt, ...) { } } -bool isValidId(string& id) +bool isValidId(string& ids) { - if (id == "*") return true; - if (id.length() != 8) return false; - for (int i=0; i<8; ++i) { - if (id[i]<'0' || id[i]>'9') return false; + vector v = splitIds(ids); + + for (auto id : v) + { + if (id == "*") return true; + if (id.length() != 8) return false; + for (int i=0; i<8; ++i) { + if (id[i]<'0' || id[i]>'9') return false; + } } return true; } @@ -332,6 +337,23 @@ bool isFrequency(std::string& fq) return true; } +vector splitIds(string& ids) +{ + vector r; + bool eof, err; + vector v (ids.begin(), ids.end()); + auto i = v.begin(); + + for (;;) { + auto id = eatTo(v, i, ',', 16, &eof, &err); + if (err) break; + trimWhitespace(&id); + r.push_back(id); + if (eof) break; + } + return r; +} + void incrementIV(uchar *iv, size_t len) { uchar *p = iv+len-1; while (p >= iv) { @@ -444,7 +466,7 @@ string eatTo(vector &v, vector::iterator &i, int c, size_t max, bo i++; max--; } - if (c != -1 && *i != c) + if (c != -1 && i != v.end() && *i != c) { *err = true; } diff --git a/src/util.h b/src/util.h index 60aca23..e8fdd4b 100644 --- a/src/util.h +++ b/src/util.h @@ -69,6 +69,8 @@ bool isValidId(std::string& id); bool isValidKey(std::string& key); bool isFrequency(std::string& fq); +std::vector splitIds(std::string& id); + void incrementIV(uchar *iv, size_t len); bool checkCharacterDeviceExists(const char *tty, bool fail_if_not); diff --git a/test.sh b/test.sh index 7411231..e63bde1 100755 --- a/test.sh +++ b/test.sh @@ -14,5 +14,6 @@ tests/test_shell.sh $PROG tests/test_meterfiles.sh $PROG tests/test_config1.sh $PROG tests/test_logfile.sh $PROG - tests/test_listen_to_all.sh $PROG +tests/test_multiple_ids.sh $PROG +tests/test_oneshot.sh $PROG diff --git a/tests/test_c1_meters.sh b/tests/test_c1_meters.sh index e2b4df1..27e0d50 100755 --- a/tests/test_c1_meters.sh +++ b/tests/test_c1_meters.sh @@ -25,4 +25,5 @@ then fi else echo Failure. + exit 1 fi diff --git a/tests/test_config1.sh b/tests/test_config1.sh index 0df450a..b0eba3a 100755 --- a/tests/test_config1.sh +++ b/tests/test_config1.sh @@ -17,5 +17,6 @@ then echo Config1 OK fi else - Failure. + echo Failure. + exit 1 fi diff --git a/tests/test_multiple_ids.sh b/tests/test_multiple_ids.sh new file mode 100755 index 0000000..b5f51fb --- /dev/null +++ b/tests/test_multiple_ids.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +PROG="$1" + +mkdir -p testoutput + +TEST=testoutput +SIM=simulations/simulation_multiple_qcalorics.txt + +cat $SIM | grep '^{' > $TEST/test_expected.txt +$PROG --format=json $SIM \ + Element qcaloric '*' '' \ + > $TEST/test_output.txt + +if [ "$?" == "0" ] +then + cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt + diff $TEST/test_expected.txt $TEST/test_responses.txt + if [ "$?" == "0" ] + then + echo Wildcard \'\*\' id OK + fi +else + echo Failure. + exit 1 +fi + +cat $SIM | grep '^{' | grep -v 78563414 > $TEST/test_expected.txt + +$PROG --format=json $SIM \ + Element qcaloric '78563412,78563413' '' \ + > $TEST/test_output.txt + +if [ "$?" == "0" ] +then + cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt + diff $TEST/test_expected.txt $TEST/test_responses.txt + if [ "$?" == "0" ] + then + echo Multiple ids2 OK + fi +else + echo Failure. + exit 1 +fi + +cat $SIM | grep '^{' > $TEST/test_expected.txt + +$PROG --format=json $SIM \ + Element qcaloric '78563412,78563413,78563414' '' \ + > $TEST/test_output.txt + +if [ "$?" == "0" ] +then + cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt + diff $TEST/test_expected.txt $TEST/test_responses.txt + if [ "$?" == "0" ] + then + echo Multiple ids3 OK + fi +else + echo Failure. + exit 1 +fi diff --git a/tests/test_oneshot.sh b/tests/test_oneshot.sh new file mode 100755 index 0000000..b12ede8 --- /dev/null +++ b/tests/test_oneshot.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +PROG="$1" + +mkdir -p testoutput + +TEST=testoutput +SIM=simulations/simulation_c1.txt + +cat $SIM | grep '^{' > $TEST/test_expected.txt + +$PROG --oneshot --verbose $SIM MyHeater multical302 '*' '' MyTapWater multical21 76348799 '' > $TEST/test_output.txt + +RES=$(cat $TEST/test_output.txt | grep -o "all meters have received at least one update, stopping.") + +if [ "$RES" = "all meters have received at least one update, stopping." ] +then + echo Oneshot OK +else + echo Fail oneshot check! + exit 1 +fi diff --git a/tests/test_shell.sh b/tests/test_shell.sh index 8d3c916..8c8a492 100755 --- a/tests/test_shell.sh +++ b/tests/test_shell.sh @@ -17,5 +17,6 @@ then echo SHELL OK fi else - Failure. + echo Failure. + exit 1 fi diff --git a/tests/test_t1_meters.sh b/tests/test_t1_meters.sh index acc8aba..4a1bb33 100755 --- a/tests/test_t1_meters.sh +++ b/tests/test_t1_meters.sh @@ -21,5 +21,6 @@ then echo T1 OK fi else - Failure. + echo Failure. + exit 1 fi diff --git a/wmbusmeters.1 b/wmbusmeters.1 index c0eb84c..aee73d5 100644 --- a/wmbusmeters.1 +++ b/wmbusmeters.1 @@ -70,7 +70,7 @@ mqtt_publish) sent to a REST API (eg curl) or store it in a database .TP \fBmeter_type\fR multical21/flowiq3100/supercom587/iperl/multical302/omnipower/qcaloric .TP -\fBmeter_id\fR an 8 digit number, usually printed on the meter +\fBmeter_id\fR one or more 8 digit numbers separated with commas, or a single '*' wildcard. .TP \fBmeter_key\fR a unique key for the meter, if meter telegrams are not encrypted, you must supply an empty key: ""