diff --git a/simulations/simulation_c1.txt b/simulations/simulation_c1.txt index eaee970..835bc31 100644 --- a/simulations/simulation_c1.txt +++ b/simulations/simulation_c1.txt @@ -2,33 +2,33 @@ # full telegram, must come before short, so that the hash of the format signature can be remembered! telegram=|2A442D2C998734761B168D208971F81821|542F7802FF2071000413F81800004413F4180000615B05616717| -{"media":"cold water","meter":"multical21","name":"MyTapWater","id":"76348799","total_m3":6.392000,"target_m3":6.388000,"max_flow_m3h":0.000000,"flow_temperature":5,"external_temperature":23,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"} +{"media":"cold water","meter":"multical21","name":"MyTapWater","id":"76348799","total_m3":6.392,"target_m3":6.388,"max_flow_m3h":0,"flow_temperature_c":5,"external_temperature_c":23,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"} # short telegram telegram=|23442D2C998734761B168D208870F81821|09EA79EDA869F57100F8180000F41800000318| -{"media":"cold water","meter":"multical21","name":"MyTapWater","id":"76348799","total_m3":6.392000,"target_m3":6.388000,"max_flow_m3h":0.000000,"flow_temperature":3,"external_temperature":24,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"} +{"media":"cold water","meter":"multical21","name":"MyTapWater","id":"76348799","total_m3":6.392,"target_m3":6.388,"max_flow_m3h":0,"flow_temperature_c":3,"external_temperature_c":24,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"} # Tets Multical21 C1 telegrams with maximum flow configuration telegram=|2D442D2C776655441B168D2083B48D3A20|46887802FF20000004132F4E000092013B3D01A1015B028101E7FF0F03| -{"media":"cold water","meter":"multical21","name":"Vadden","id":"44556677","total_m3":20.015000,"target_m3":0.000000,"max_flow_m3h":0.317000,"flow_temperature":2,"external_temperature":3,"current_status":"","time_dry":"","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"} +{"media":"cold water","meter":"multical21","name":"Vadden","id":"44556677","total_m3":20.015,"target_m3":0,"max_flow_m3h":0.317,"flow_temperature_c":2,"external_temperature_c":3,"current_status":"","time_dry":"","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"} telegram=|21442D2C776655441B168D2079CC8C3A20|F4307912C40DFF00002F4E00003D010203| -{"media":"cold water","meter":"multical21","name":"Vadden","id":"44556677","total_m3":20.015000,"target_m3":0.000000,"max_flow_m3h":0.317000,"flow_temperature":2,"external_temperature":3,"current_status":"","time_dry":"","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"} +{"media":"cold water","meter":"multical21","name":"Vadden","id":"44556677","total_m3":20.015,"target_m3":0,"max_flow_m3h":0.317,"flow_temperature_c":2,"external_temperature_c":3,"current_status":"","time_dry":"","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"} # Test Multical302 C1 telegrams # short telegram, this is not a proper telegram! Please provide the output from --logtelegrams for a Multical302 meter! telegram=|25442D2C785634121b048D2093E13CBA20|0000790000000000000000000000000000| -{"media":"heat","meter":"multical302","name":"MyHeater","id":"12345678","total_kwh":0.000000,"total_volume_m3":0.000000,"current_kw":"0.000000","timestamp":"1111-11-11T11:11:11Z"} +{"media":"heat","meter":"multical302","name":"MyHeater","id":"12345678","total_kwh":0,"total_volume_m3":0,"current_kw":0,"timestamp":"1111-11-11T11:11:11Z"} # full telegram # Test Omnipower C1 telegrams telegram=|1E442D2C0771941501027AB3001080|04833B08340500| -{"media":"electricity","meter":"omnipower","name":"MyElectricity","id":"15947107","total_energy_consumption_kwh":341.000000,"current_power_consumption_kw":0.000000,"timestamp":"1111-11-11T11:11:11Z"} +{"media":"electricity","meter":"omnipower","name":"MyElectricity","id":"15947107","total_energy_consumption_kwh":341,"current_power_consumption_kw":0,"timestamp":"1111-11-11T11:11:11Z"} # Test QCaloric C1 telegrams telegram=|314493441234567835087a740000200b6e2701004b6e450100426c5f2ccb086e790000c2086c7f21326cffff046d200b7422| -{"media":"heat_cost_allocation","meter":"qcaloric","name":"MyElement","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"} +{"media":"heat_cost_allocation","meter":"qcaloric","name":"MyElement","id":"78563412","current_consumption_hca":127,"set_date":"2018-12-31","consumption_at_set_date_hca":145,"set_date_17":"2019-01-31","consumption_at_set_date_17_hca":79,"error_date":"2127-15-31","device_date_time":"2019-02-20 11:32","timestamp":"1111-11-11T11:11:11Z"} diff --git a/simulations/simulation_multiple_qcalorics.txt b/simulations/simulation_multiple_qcalorics.txt index f52ee5d..92b90d7 100644 --- a/simulations/simulation_multiple_qcalorics.txt +++ b/simulations/simulation_multiple_qcalorics.txt @@ -1,6 +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"} +{"media":"heat_cost_allocation","meter":"qcaloric","name":"Element","id":"78563412","current_consumption_hca":127,"set_date":"2018-12-31","consumption_at_set_date_hca":145,"set_date_17":"2019-01-31","consumption_at_set_date_17_hca":79,"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"} +{"media":"heat_cost_allocation","meter":"qcaloric","name":"Element","id":"78563413","current_consumption_hca":127,"set_date":"2018-12-31","consumption_at_set_date_hca":145,"set_date_17":"2019-01-31","consumption_at_set_date_17_hca":79,"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"} +{"media":"heat_cost_allocation","meter":"qcaloric","name":"Element","id":"78563414","current_consumption_hca":127,"set_date":"2018-12-31","consumption_at_set_date_hca":145,"set_date_17":"2019-01-31","consumption_at_set_date_17_hca":79,"error_date":"2127-15-31","device_date_time":"2019-02-20 11:32","timestamp":"1111-11-11T11:11:11Z"} diff --git a/simulations/simulation_shell.txt b/simulations/simulation_shell.txt index 49bbadd..2577d27 100644 --- a/simulations/simulation_shell.txt +++ b/simulations/simulation_shell.txt @@ -1,2 +1,2 @@ telegram=|A244EE4D785634123C067A8F000000|0C1348550000426CE1F14C130000000082046C21298C0413330000008D04931E3A3CFE3300000033000000330000003300000033000000330000003300000033000000330000003300000033000000330000004300000034180000046D0D0B5C2B03FD6C5E150082206C5C290BFD0F0200018C4079678885238310FD3100000082106C01018110FD610002FD66020002FD170000| -{"media":"warm water","meter":"supercom587","name":"MyWarmWater","id":"12345678","total_m3":5.548000,"timestamp":"1111-11-11T11:11:11Z"} +{"media":"warm water","meter":"supercom587","name":"MyWarmWater","id":"12345678","total_m3":5.548,"timestamp":"1111-11-11T11:11:11Z"} diff --git a/simulations/simulation_t1.txt b/simulations/simulation_t1.txt index a1b12f6..db617dc 100644 --- a/simulations/simulation_t1.txt +++ b/simulations/simulation_t1.txt @@ -1,35 +1,35 @@ # Test Supercom587 T1 telegrams telegram=|A244EE4D785634123C067A8F000000|0C1348550000426CE1F14C130000000082046C21298C0413330000008D04931E3A3CFE3300000033000000330000003300000033000000330000003300000033000000330000003300000033000000330000004300000034180000046D0D0B5C2B03FD6C5E150082206C5C290BFD0F0200018C4079678885238310FD3100000082106C01018110FD610002FD66020002FD170000| -{"media":"warm water","meter":"supercom587","name":"MyWarmWater","id":"12345678","total_m3":5.548000,"timestamp":"1111-11-11T11:11:11Z"} +{"media":"warm water","meter":"supercom587","name":"MyWarmWater","id":"12345678","total_m3":5.548,"timestamp":"1111-11-11T11:11:11Z"} telegram=|A244EE4D111111113C077AAC000000|0C1389490000426CE1F14C130000000082046C21298C0413010000008D04931E3A3CFE0100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000001600000031130000046D0A0C5C2B03FD6C60150082206C5C290BFD0F0200018C4079629885238310FD3100000082106C01018110FD610002FD66020002FD170000| -{"media":"water","meter":"supercom587","name":"MyColdWater","id":"11111111","total_m3":4.989000,"timestamp":"1111-11-11T11:11:11Z"} +{"media":"water","meter":"supercom587","name":"MyColdWater","id":"11111111","total_m3":4.989,"timestamp":"1111-11-11T11:11:11Z"} # Test iPerl T1 telegram, after decryption, its got 2f2f markers. telegram=|1E44AE4C9956341268077A36001000|2F2F0413181E0000023B00002F2F2F2F| -{"media":"water","meter":"iperl","name":"MoreWater","id":"12345699","total_m3":7.704000,"max_flow_m3h":0.000000,"timestamp":"1111-11-11T11:11:11Z"} +{"media":"water","meter":"iperl","name":"MoreWater","id":"12345699","total_m3":7.704,"max_flow_m3h":0,"timestamp":"1111-11-11T11:11:11Z"} # Test iPerl T1 telegram not encrypted, no 2f2f markers. telegram=|1844AE4C4455223368077A55000000|041389E20100023B0000| -{"media":"water","meter":"iperl","name":"WaterWater","id":"33225544","total_m3":123.529000,"max_flow_m3h":0.000000,"timestamp":"1111-11-11T11:11:11Z"} +{"media":"water","meter":"iperl","name":"WaterWater","id":"33225544","total_m3":123.529,"max_flow_m3h":0,"timestamp":"1111-11-11T11:11:11Z"} # Test at-wmbus-16-2 T1 telegram telegram=|6E4401062020202005077A9A006085|2F2F0F0A734393CC0000435B0183001A54E06F630291342510030F00007B013E0B00003E0B00003E0B00003E0B00003E0B00003E0B00003E0B0000650000003D0000003D0000003D00000000000000A0910CB003FFFFFFFFFFFFFFFFFFFFA62B| -{"media":"water","meter":"apator162","name":"Wasser","id":"20202020","total_m3":3.843000,"timestamp":"1111-11-11T11:11:11Z"} +{"media":"water","meter":"apator162","name":"Wasser","id":"20202020","total_m3":3.843,"timestamp":"1111-11-11T11:11:11Z"} # Test amiplus/apator electricity meter telegram=|4E4401061010101002027A00004005|2F2F0E035040691500000B2B300300066D00790C7423400C78371204860BABC8FC100000000E833C8074000000000BAB3C0000000AFDC9FC0136022F2F2F2F2F| -{"media":"electricity","meter":"amiplus","name":"MyElectricity1","id":"10101010","total_energy_consumption_kwh":15694.050000,"current_power_consumption_kw":0.330000,"total_energy_production_kwh":7.480000,"current_power_production_kw":0.000000,"device_date_time":"2019-03-20 12:57","timestamp":"1111-11-11T11:11:11Z"} +{"media":"electricity","meter":"amiplus","name":"MyElectricity1","id":"10101010","total_energy_consumption_kwh":15694.05,"current_power_consumption_kw":0.33,"total_energy_production_kwh":7.48,"current_power_production_kw":0,"device_date_time":"2019-03-20 12:57","timestamp":"1111-11-11T11:11:11Z"} # Test MKRadio3 T1 telegrams telegram=|2F446850313233347462A2|069F255900B029310000000306060906030609070606050509050505050407040605070500| -{"media":"warm water","meter":"mkradio3","name":"Duschen","id":"34333231","total_m3":13.800000,"target_m3":8.900000,"timestamp":"1111-11-11T11:11:11Z"} +{"media":"warm water","meter":"mkradio3","name":"Duschen","id":"34333231","total_m3":13.8,"target_m3":8.9,"timestamp":"1111-11-11T11:11:11Z"} # Test vario451 T1 telegrams telegram=|374468506549235827C3A2|129F25383300A8622600008200800A2AF862115175552877A36F26C9AB1CB24400000004000000000004908002| diff --git a/src/meter_amiplus.cc b/src/meter_amiplus.cc index a182386..74bea95 100644 --- a/src/meter_amiplus.cc +++ b/src/meter_amiplus.cc @@ -66,6 +66,9 @@ MeterAmiplus::MeterAmiplus(WMBus *bus, string& name, string& id, string& key) : // Oddly, this device has not been configured to send as a electricity meter, // but instead a device/media type that is used for gateway or relays or something? addMedia(0x37); // Radio converter (meter side) + + setExpectedVersion(0x02); + MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*)); } @@ -101,8 +104,6 @@ void MeterAmiplus::handleTelegram(Telegram *t) { t->a_field_address[0], t->a_field_address[1], t->a_field_address[2], t->a_field_address[3]); - t->expectVersion("amiplus", 0x02); - if (t->isEncrypted() && !useAes() && !t->isSimulated()) { warning("(amiplus) warning: telegram is encrypted but no key supplied!\n"); } diff --git a/src/meter_apator162.cc b/src/meter_apator162.cc index e619770..701fe2d 100644 --- a/src/meter_apator162.cc +++ b/src/meter_apator162.cc @@ -73,6 +73,9 @@ MeterApator162::MeterApator162(WMBus *bus, string& name, string& id, string& key { addMedia(0x06); addMedia(0x07); + + setExpectedVersion(0x05); + MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*)); } @@ -99,7 +102,6 @@ void MeterApator162::handleTelegram(Telegram *t) t->a_field_address[0], t->a_field_address[1], t->a_field_address[2], t->a_field_address[3]); - t->expectVersion("apator162", 0x05); if (t->isEncrypted() && !useAes() && !t->isSimulated()) { warning("(apator162) warning: telegram is encrypted but no key supplied!\n"); diff --git a/src/meter_iperl.cc b/src/meter_iperl.cc index 3e8190b..f63f3c6 100644 --- a/src/meter_iperl.cc +++ b/src/meter_iperl.cc @@ -39,91 +39,44 @@ struct MeterIperl : public virtual WaterMeter, public virtual MeterCommonImpleme // Total water counted through the meter double totalWaterConsumption(Unit u); bool hasTotalWaterConsumption(); - double targetWaterConsumption(Unit u); - bool hasTargetWaterConsumption(); double maxFlow(Unit u); bool hasMaxFlow(); - double flowTemperature(Unit u); - bool hasFlowTemperature(); - double externalTemperature(Unit u); - bool hasExternalTemperature(); - - string statusHumanReadable(); - string status(); - string timeDry(); - string timeReversed(); - string timeLeaking(); - string timeBursting(); - - void printMeter(Telegram *t, - string *human_readable, - string *fields, char separator, - string *json, - vector *envs); private: - void handleTelegram(Telegram *t); void processContent(Telegram *t); - string decodeTime(int time); - double total_water_consumption_ {}; - double max_flow_ {}; + double total_water_consumption_m3_ {}; + double max_flow_m3h_ {}; }; MeterIperl::MeterIperl(WMBus *bus, string& name, string& id, string& key) : MeterCommonImplementation(bus, name, id, key, MeterType::IPERL, MANUFACTURER_SEN, LinkMode::T1) { + setEncryptionMode(EncryptionMode::AES_CBC); + addMedia(0x06); addMedia(0x07); + + setExpectedVersion(0x68); + + addPrint("total", Quantity::Volume, + [&](Unit u){ return totalWaterConsumption(u); }, + "The total water consumption recorded by this meter.", + true, true); + + addPrint("max_flow", Quantity::Flow, + [&](Unit u){ return maxFlow(u); }, + "The maxium flow recorded during previous period.", + true, true); + MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*)); } - -double MeterIperl::totalWaterConsumption(Unit u) -{ - return total_water_consumption_; -} - unique_ptr createIperl(WMBus *bus, string& name, string& id, string& key) { return unique_ptr(new MeterIperl(bus,name,id,key)); } -void MeterIperl::handleTelegram(Telegram *t) -{ - if (!isTelegramForMe(t)) { - // This telegram is not intended for this meter. - return; - } - - verbose("(%s) telegram for %s %02x%02x%02x%02x\n", "iperl", - name().c_str(), - t->a_field_address[0], t->a_field_address[1], t->a_field_address[2], - t->a_field_address[3]); - - t->expectVersion("iperl", 0x68); - - if (t->isEncrypted() && !useAes() && !t->isSimulated()) { - warning("(iperl) warning: telegram is encrypted but no key supplied!\n"); - } - if (useAes()) { - vector aeskey = key(); - decryptMode5_AES_CBC(t, aeskey); - } else { - t->content = t->payload; - } - char log_prefix[256]; - snprintf(log_prefix, 255, "(%s) log", "iperl"); - logTelegram(log_prefix, t->parsed, t->content); - int content_start = t->parsed.size(); - processContent(t); - if (isDebugEnabled()) { - snprintf(log_prefix, 255, "(%s)", "iperl"); - t->explainParse(log_prefix, content_start); - } - triggerUpdate(t); -} - void MeterIperl::processContent(Telegram *t) { map> values; @@ -133,81 +86,20 @@ void MeterIperl::processContent(Telegram *t) string key; if(findKey(ValueInformation::Volume, 0, &key, &values)) { - extractDVdouble(&values, key, &offset, &total_water_consumption_); - t->addMoreExplanation(offset, " total consumption (%f m3)", total_water_consumption_); + extractDVdouble(&values, key, &offset, &total_water_consumption_m3_); + t->addMoreExplanation(offset, " total consumption (%f m3)", total_water_consumption_m3_); } if(findKey(ValueInformation::VolumeFlow, ANY_STORAGENR, &key, &values)) { - extractDVdouble(&values, key, &offset, &max_flow_); - t->addMoreExplanation(offset, " max flow (%f m3/h)", max_flow_); + extractDVdouble(&values, key, &offset, &max_flow_m3h_); + t->addMoreExplanation(offset, " max flow (%f m3/h)", max_flow_m3h_); } } -void MeterIperl::printMeter(Telegram *t, - string *human_readable, - string *fields, char separator, - string *json, - vector *envs) +double MeterIperl::totalWaterConsumption(Unit u) { - char buf[65536]; - buf[65535] = 0; - - snprintf(buf, sizeof(buf)-1, - "%s\t" - "%s\t" - "% 3.3f m3\t" - "% 3.3f m3/h\t" - "%s", - name().c_str(), - t->id.c_str(), - totalWaterConsumption(Unit::M3), - maxFlow(Unit::M3H), - datetimeOfUpdateHumanReadable().c_str()); - - *human_readable = buf; - - snprintf(buf, sizeof(buf)-1, - "%s%c" - "%s%c" - "%f%c" - "%f%c" - "%s", - name().c_str(), separator, - t->id.c_str(), separator, - totalWaterConsumption(Unit::M3), separator, - maxFlow(Unit::M3H), separator, - datetimeOfUpdateRobot().c_str()); - - *fields = buf; - -#define Q(x,y) "\""#x"\":"#y"," -#define QS(x,y) "\""#x"\":\""#y"\"," -#define QSE(x,y) "\""#x"\":\""#y"\"" - - snprintf(buf, sizeof(buf)-1, "{" - QS(media,%s) - QS(meter,iperl) - QS(name,%s) - QS(id,%s) - Q(total_m3,%f) - Q(max_flow_m3h,%f) - QSE(timestamp,%s) - "}", - mediaTypeJSON(t->a_field_device_type).c_str(), - name().c_str(), - t->id.c_str(), - totalWaterConsumption(Unit::M3), - maxFlow(Unit::M3H), - datetimeOfUpdateRobot().c_str()); - - *json = buf; - - envs->push_back(string("METER_JSON=")+*json); - envs->push_back(string("METER_TYPE=iperl")); - envs->push_back(string("METER_ID=")+t->id); - envs->push_back(string("METER_TOTAL_M3=")+to_string(totalWaterConsumption(Unit::M3))); - envs->push_back(string("METER_MAX_FLOW_M3H=")+to_string(maxFlow(Unit::M3H))); - envs->push_back(string("METER_TIMESTAMP=")+datetimeOfUpdateRobot()); + assertQuantity(u, Quantity::Volume); + return convert(total_water_consumption_m3_, Unit::M3, u); } bool MeterIperl::hasTotalWaterConsumption() @@ -215,72 +107,13 @@ bool MeterIperl::hasTotalWaterConsumption() return true; } -double MeterIperl::targetWaterConsumption(Unit u) -{ - return 0.0; -} - -bool MeterIperl::hasTargetWaterConsumption() -{ - return false; -} - double MeterIperl::maxFlow(Unit u) { - return max_flow_; + assertQuantity(u, Quantity::Flow); + return convert(max_flow_m3h_, Unit::M3H, u); } bool MeterIperl::hasMaxFlow() { return true; } - -double MeterIperl::flowTemperature(Unit u) -{ - return 127; -} - -bool MeterIperl::hasFlowTemperature() -{ - return false; -} - -double MeterIperl::externalTemperature(Unit u) -{ - return 127; -} - -bool MeterIperl::hasExternalTemperature() -{ - return false; -} - -string MeterIperl::statusHumanReadable() -{ - return ""; -} - -string MeterIperl::status() -{ - return ""; -} - -string MeterIperl::timeDry() -{ - return ""; -} - -string MeterIperl::timeReversed() -{ - return ""; -} - -string MeterIperl::timeLeaking() -{ - return ""; -} - -string MeterIperl::timeBursting() -{ - return ""; -} diff --git a/src/meter_mkradio3.cc b/src/meter_mkradio3.cc index 3b1f038..6e6a3f7 100644 --- a/src/meter_mkradio3.cc +++ b/src/meter_mkradio3.cc @@ -51,18 +51,22 @@ private: MKRadio3::MKRadio3(WMBus *bus, string& name, string& id, string& key) : MeterCommonImplementation(bus, name, id, key, MeterType::MKRADIO3, MANUFACTURER_TCH, LinkMode::T1) { + setEncryptionMode(EncryptionMode::None); + addMedia(0x62); addMedia(0x72); + setExpectedVersion(0x74); + addPrint("total", Quantity::Volume, [&](Unit u){ return totalWaterConsumption(u); }, "The total water consumption recorded by this meter.", - true); + true, true); addPrint("target", Quantity::Volume, [&](Unit u){ return targetWaterConsumption(u); }, "The total water consumption recorded at the beginning of this month.", - true); + true, true); MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*)); } diff --git a/src/meter_multical21.cc b/src/meter_multical21.cc index e2ff143..8e4cff3 100644 --- a/src/meter_multical21.cc +++ b/src/meter_multical21.cc @@ -77,27 +77,21 @@ struct MeterMultical21 : public virtual WaterMeter, public virtual MeterCommonIm string timeLeaking(); string timeBursting(); - void printMeter(Telegram *t, - string *human_readable, - string *fields, char separator, - string *json, - vector *envs); - private: - void handleTelegram(Telegram *t); + void processContent(Telegram *t); string decodeTime(int time); uint16_t info_codes_ {}; - double total_water_consumption_ {}; + double total_water_consumption_m3_ {}; bool has_total_water_consumption_ {}; - double target_volume_ {}; - bool has_target_volume_ {}; - double max_flow_ {}; + double target_water_consumption_m3_ {}; + bool has_target_water_consumption_ {}; + double max_flow_m3h_ {}; bool has_max_flow_ {}; - double flow_temperature_ { 127 }; + double flow_temperature_c_ { 127 }; bool has_flow_temperature_ {}; - double external_temperature_ { 127 }; + double external_temperature_c_ { 127 }; bool has_external_temperature_ {}; const char *meter_name_; // multical21 or flowiq3100 @@ -107,24 +101,80 @@ private: MeterMultical21::MeterMultical21(WMBus *bus, string& name, string& id, string& key, MeterType mt) : MeterCommonImplementation(bus, name, id, key, mt, MANUFACTURER_KAM, LinkMode::C1) { + setEncryptionMode(EncryptionMode::AES_CTR); + addMedia(0x16); // Water media if (type() == MeterType::MULTICAL21) { - expected_version_ = 0x1b; - meter_name_ = "multical21"; + setExpectedVersion(0x1b); } else if (type() == MeterType::FLOWIQ3100) { - expected_version_ = 0x1d; - meter_name_ = "flowiq3100"; + setExpectedVersion(0x1d); } else { assert(0); } + + addPrint("total", Quantity::Volume, + [&](Unit u){ return totalWaterConsumption(u); }, + "The total water consumption recorded by this meter.", + true, true); + + addPrint("target", Quantity::Volume, + [&](Unit u){ return targetWaterConsumption(u); }, + "The total water consumption recorded at the beginning of this month.", + true, true); + + addPrint("max_flow", Quantity::Flow, + [&](Unit u){ return maxFlow(u); }, + "The maxium flow recorded during previous period.", + true, true); + + addPrint("flow_temperature", Quantity::Temperature, + [&](Unit u){ return flowTemperature(u); }, + "The water temperature.", + true, true); + + addPrint("external_temperature", Quantity::Temperature, + [&](Unit u){ return externalTemperature(u); }, + "The external temperature outside of the meter.", + true, true); + + addPrint("", Quantity::Text, + [&](){ return statusHumanReadable(); }, + "Status of meter.", + true, false); + + addPrint("current_status", Quantity::Text, + [&](){ return status(); }, + "Status of meter.", + false, true); + + addPrint("time_dry", Quantity::Text, + [&](){ return timeDry(); }, + "Amount of time the meter has been dry.", + false, true); + + addPrint("time_reversed", Quantity::Text, + [&](){ return timeReversed(); }, + "Amount of time the meter has been reversed.", + false, true); + + addPrint("time_leaking", Quantity::Text, + [&](){ return timeLeaking(); }, + "Amount of time the meter has been leaking.", + false, true); + + addPrint("time_bursting", Quantity::Text, + [&](){ return timeBursting(); }, + "Amount of time the meter has been bursting.", + false, true); + MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*)); } - double MeterMultical21::totalWaterConsumption(Unit u) { - return total_water_consumption_; + assertQuantity(u, Quantity::Volume); + return convert(total_water_consumption_m3_, Unit::M3, u); } bool MeterMultical21::hasTotalWaterConsumption() @@ -134,17 +184,19 @@ bool MeterMultical21::hasTotalWaterConsumption() double MeterMultical21::targetWaterConsumption(Unit u) { - return target_volume_; + assertQuantity(u, Quantity::Volume); + return convert(target_water_consumption_m3_, Unit::M3, u); } bool MeterMultical21::hasTargetWaterConsumption() { - return has_target_volume_; + return has_target_water_consumption_; } double MeterMultical21::maxFlow(Unit u) { - return max_flow_; + assertQuantity(u, Quantity::Flow); + return convert(max_flow_m3h_, Unit::M3H, u); } bool MeterMultical21::hasMaxFlow() @@ -154,7 +206,8 @@ bool MeterMultical21::hasMaxFlow() double MeterMultical21::flowTemperature(Unit u) { - return flow_temperature_; + assertQuantity(u, Quantity::Temperature); + return convert(flow_temperature_c_, Unit::C, u); } bool MeterMultical21::hasFlowTemperature() @@ -164,7 +217,8 @@ bool MeterMultical21::hasFlowTemperature() double MeterMultical21::externalTemperature(Unit u) { - return external_temperature_; + assertQuantity(u, Quantity::Temperature); + return convert(external_temperature_c_, Unit::C, u); } bool MeterMultical21::hasExternalTemperature() @@ -190,38 +244,6 @@ unique_ptr createFlowIQ3100(WMBus *bus, string& name, string& id, st return createMulticalWaterMeter(bus, name, id, key, MeterType::FLOWIQ3100); } -void MeterMultical21::handleTelegram(Telegram *t) -{ - if (!isTelegramForMe(t)) { - // This telegram is not intended for this meter. - return; - } - - verbose("(%s) telegram for %s %02x%02x%02x%02x\n", meter_name_, - name().c_str(), - t->a_field_address[0], t->a_field_address[1], t->a_field_address[2], - t->a_field_address[3]); - - t->expectVersion(name().c_str(), expected_version_); - - if (useAes()) { - vector aeskey = key(); - decryptMode1_AES_CTR(t, aeskey); - } else { - t->content = t->payload; - } - char log_prefix[256]; - snprintf(log_prefix, 255, "(%s) log", meter_name_); - logTelegram(log_prefix, t->parsed, t->content); - int content_start = t->parsed.size(); - processContent(t); - if (isDebugEnabled()) { - snprintf(log_prefix, 255, "(%s)", meter_name_); - t->explainParse(log_prefix, content_start); - } - triggerUpdate(t); -} - void MeterMultical21::processContent(Telegram *t) { // 02 dif (16 Bit Integer/Binary Instantaneous value) @@ -335,31 +357,31 @@ void MeterMultical21::processContent(Telegram *t) t->addMoreExplanation(offset, " info codes (%s)", statusHumanReadable().c_str()); if(findKey(ValueInformation::Volume, 0, &key, &values)) { - extractDVdouble(&values, key, &offset, &total_water_consumption_); + extractDVdouble(&values, key, &offset, &total_water_consumption_m3_); has_total_water_consumption_ = true; - t->addMoreExplanation(offset, " total consumption (%f m3)", total_water_consumption_); + t->addMoreExplanation(offset, " total consumption (%f m3)", total_water_consumption_m3_); } if(findKey(ValueInformation::Volume, 1, &key, &values)) { - extractDVdouble(&values, key, &offset, &target_volume_); - has_target_volume_ = true; - t->addMoreExplanation(offset, " target consumption (%f m3)", target_volume_); + extractDVdouble(&values, key, &offset, &target_water_consumption_m3_); + has_target_water_consumption_ = true; + t->addMoreExplanation(offset, " target consumption (%f m3)", target_water_consumption_m3_); } if(findKey(ValueInformation::VolumeFlow, ANY_STORAGENR, &key, &values)) { - extractDVdouble(&values, key, &offset, &max_flow_); + extractDVdouble(&values, key, &offset, &max_flow_m3h_); has_max_flow_ = true; - t->addMoreExplanation(offset, " max flow (%f m3/h)", max_flow_); + t->addMoreExplanation(offset, " max flow (%f m3/h)", max_flow_m3h_); } if(findKey(ValueInformation::FlowTemperature, ANY_STORAGENR, &key, &values)) { - has_flow_temperature_ = extractDVdouble(&values, key, &offset, &flow_temperature_); - t->addMoreExplanation(offset, " flow temperature (%f °C)", flow_temperature_); + has_flow_temperature_ = extractDVdouble(&values, key, &offset, &flow_temperature_c_); + t->addMoreExplanation(offset, " flow temperature (%f °C)", flow_temperature_c_); } if(findKey(ValueInformation::ExternalTemperature, ANY_STORAGENR, &key, &values)) { - has_external_temperature_ = extractDVdouble(&values, key, &offset, &external_temperature_); - t->addMoreExplanation(offset, " external temperature (%f °C)", external_temperature_); + has_external_temperature_ = extractDVdouble(&values, key, &offset, &external_temperature_c_); + t->addMoreExplanation(offset, " external temperature (%f °C)", external_temperature_c_); } } @@ -485,129 +507,3 @@ string MeterMultical21::decodeTime(int time) return "?"; } } - -void MeterMultical21::printMeter(Telegram *t, - string *human_readable, - string *fields, char separator, - string *json, - vector *envs) -{ - char buf[65536]; - buf[65535] = 0; - - char ft[10], et[10]; - ft[9] = 0; - et[9] = 0; - - if (hasFlowTemperature()) { - snprintf(ft, sizeof(ft)-1, "% 2.0f", flowTemperature(Unit::C)); - } else { - ft[0] = '-'; - ft[1] = 0; - } - - if (hasExternalTemperature()) { - snprintf(et, sizeof(et)-1, "% 2.0f", externalTemperature(Unit::C)); - } else { - et[0] = '-'; - et[1] = 0; - } - - 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(), - t->id.c_str(), - totalWaterConsumption(Unit::M3), - targetWaterConsumption(Unit::M3), - maxFlow(Unit::M3H), - ft, - et, - statusHumanReadable().c_str(), - datetimeOfUpdateHumanReadable().c_str()); - - *human_readable = buf; - - 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, - t->id.c_str(), separator, - totalWaterConsumption(Unit::M3), separator, - targetWaterConsumption(Unit::M3), separator, - maxFlow(Unit::M3H), separator, - flowTemperature(Unit::C), separator, - externalTemperature(Unit::C), separator, - statusHumanReadable().c_str(), separator, - datetimeOfUpdateRobot().c_str()); - - *fields = buf; - -#define Q(x,y) "\""#x"\":"#y"," -#define QS(x,y) "\""#x"\":\""#y"\"," -#define QSE(x,y) "\""#x"\":\""#y"\"" - - snprintf(buf, sizeof(buf)-1, "{" - QS(media,%s) - QS(meter,%s) - QS(name,%s) - QS(id,%s) - Q(total_m3,%f) - Q(target_m3,%f) - Q(max_flow_m3h,%f) - Q(flow_temperature,%.0f) - Q(external_temperature,%.0f) - QS(current_status,%s) - QS(time_dry,%s) - QS(time_reversed,%s) - QS(time_leaking,%s) - QS(time_bursting,%s) - QSE(timestamp,%s) - "}", - mediaTypeJSON(t->a_field_device_type).c_str(), - meter_name_, - name().c_str(), - t->id.c_str(), - totalWaterConsumption(Unit::M3), - targetWaterConsumption(Unit::M3), - maxFlow(Unit::M3H), - flowTemperature(Unit::C), - externalTemperature(Unit::C), - status().c_str(), // DRY REVERSED LEAK BURST - timeDry().c_str(), - timeReversed().c_str(), - timeLeaking().c_str(), - timeBursting().c_str(), - datetimeOfUpdateRobot().c_str()); - - *json = buf; - - envs->push_back(string("METER_JSON=")+*json); - envs->push_back(string("METER_TYPE=")+meter_name_); - envs->push_back(string("METER_ID=")+t->id); - envs->push_back(string("METER_TOTAL_M3=")+to_string(totalWaterConsumption(Unit::M3))); - envs->push_back(string("METER_TARGET_M3=")+to_string(targetWaterConsumption(Unit::M3))); - envs->push_back(string("METER_MAX_FLOW_M3H=")+to_string(maxFlow(Unit::M3H))); - envs->push_back(string("METER_FLOW_TEMPERATURE=")+to_string(flowTemperature(Unit::C))); - envs->push_back(string("METER_EXTERNAL_TEMPERATURE=")+to_string(externalTemperature(Unit::C))); - envs->push_back(string("METER_STATUS=")+status()); - envs->push_back(string("METER_TIME_DRY=")+timeDry()); - envs->push_back(string("METER_TIME_REVERSED=")+timeReversed()); - envs->push_back(string("METER_TIME_LEAKING=")+timeLeaking()); - envs->push_back(string("METER_TIME_BURSTING=")+timeBursting()); - envs->push_back(string("METER_TIMESTAMP=")+datetimeOfUpdateRobot()); -} diff --git a/src/meter_multical302.cc b/src/meter_multical302.cc index 5e8bdec..6f40705 100644 --- a/src/meter_multical302.cc +++ b/src/meter_multical302.cc @@ -31,19 +31,10 @@ struct MeterMultical302 : public virtual HeatMeter, public virtual MeterCommonIm MeterMultical302(WMBus *bus, string& name, string& id, string& key); double totalEnergyConsumption(Unit u); - double currentPeriodEnergyConsumption(Unit u); - double previousPeriodEnergyConsumption(Unit u); double currentPowerConsumption(Unit u); double totalVolume(Unit u); - void printMeter(Telegram *t, - string *human_readable, - string *fields, char separator, - string *json, - vector *envs); - private: - void handleTelegram(Telegram *t); void processContent(Telegram *t); double total_energy_kwh_ {}; @@ -54,69 +45,52 @@ private: MeterMultical302::MeterMultical302(WMBus *bus, string& name, string& id, string& key) : MeterCommonImplementation(bus, name, id, key, MeterType::MULTICAL302, MANUFACTURER_KAM, LinkMode::C1) { + setEncryptionMode(EncryptionMode::AES_CTR); + addMedia(0x04); // Heat media + + addPrint("total", Quantity::Energy, + [&](Unit u){ return totalEnergyConsumption(u); }, + "The total energy consumption recorded by this meter.", + true, true); + + addPrint("total_volume", Quantity::Volume, + [&](Unit u){ return totalVolume(u); }, + "Total volume of heat media.", + true, true); + + addPrint("current", Quantity::Power, + [&](Unit u){ return currentPowerConsumption(u); }, + "Current power consumption.", + true, true); + MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*)); } +unique_ptr createMultical302(WMBus *bus, string& name, string& id, string& key) { + return unique_ptr(new MeterMultical302(bus,name,id,key)); +} + double MeterMultical302::totalEnergyConsumption(Unit u) { assertQuantity(u, Quantity::Energy); return convert(total_energy_kwh_, Unit::KWH, u); } -double MeterMultical302::currentPowerConsumption(Unit u) -{ - assertQuantity(u, Quantity::Power); - return convert(current_power_kw_, Unit::KW, u); -} - -double MeterMultical302::currentPeriodEnergyConsumption(Unit u) -{ - return 0; -} - -double MeterMultical302::previousPeriodEnergyConsumption(Unit u) -{ - return 0; -} - double MeterMultical302::totalVolume(Unit u) { assertQuantity(u, Quantity::Volume); return convert(total_volume_m3_, Unit::M3, u); } -void MeterMultical302::handleTelegram(Telegram *t) { - - if (!isTelegramForMe(t)) { - // This telegram is not intended for this meter. - return; - } - - verbose("(multical302) %s %02x%02x%02x%02x ", - name().c_str(), - t->a_field_address[0], t->a_field_address[1], t->a_field_address[2], - t->a_field_address[3]); - - if (t->isEncrypted() && !useAes() && !t->isSimulated()) { - warning("(multical302) warning: telegram is encrypted but no key supplied!\n"); - } - if (useAes()) { - vector aeskey = key(); - decryptMode1_AES_CTR(t, aeskey); - } else { - t->content = t->payload; - } - logTelegram("(multical302) log", t->parsed, t->content); - int content_start = t->parsed.size(); - processContent(t); - if (isDebugEnabled()) { - t->explainParse("(multical302)", content_start); - } - triggerUpdate(t); +double MeterMultical302::currentPowerConsumption(Unit u) +{ + assertQuantity(u, Quantity::Power); + return convert(current_power_kw_, Unit::KW, u); } -void MeterMultical302::processContent(Telegram *t) { +void MeterMultical302::processContent(Telegram *t) +{ vector::iterator bytes = t->content.begin(); int crc0 = t->content[0]; @@ -126,13 +100,6 @@ void MeterMultical302::processContent(Telegram *t) { t->addExplanation(bytes, 1, "%02x frame type (%s)", frame_type, frameTypeKamstrupC1(frame_type).c_str()); if (frame_type == 0x79) { - /*if (t->content.size() != 17) { - - warning("(multical302) warning: Unexpected length of frame %zu. Expected 17 bytes! ", t->content.size()); - padWithZeroesTo(&t->content, 17, &t->content); - warning("\n"); - }*/ - // This code should be rewritten to use parseDV see the Multical21 code. // But I cannot do this without more examples of 302 telegrams. t->addExplanation(bytes, 4, "%02x%02x%02x%02x unknown", t->content[3], t->content[4], t->content[5], t->content[6]); @@ -159,12 +126,6 @@ void MeterMultical302::processContent(Telegram *t) { } else if (frame_type == 0x78) { - /*if (t->content.size() != 26) { - warning("(multical302) warning: Unexpected length of frame %zu. Expected 26 bytes! ", t->content.size()); - padWithZeroesTo(&t->content, 26, &t->content); - warning("\n"); - }*/ - // This code should be rewritten to use parseDV see the Multical21 code. // But I cannot do this without more examples of 302 telegrams. vector unknowns; @@ -184,68 +145,3 @@ void MeterMultical302::processContent(Telegram *t) { warning("(multical302) warning: unknown frame %02x (did you use the correct encryption key?)\n", frame_type); } } - -unique_ptr createMultical302(WMBus *bus, string& name, string& id, string& key) { - return unique_ptr(new MeterMultical302(bus,name,id,key)); -} - -void MeterMultical302::printMeter(Telegram *t, - string *human_readable, - string *fields, char separator, - string *json, - vector *envs) -{ - char buf[65536]; - buf[65535] = 0; - - 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(), - t->id.c_str(), - totalEnergyConsumption(Unit::KWH), - totalVolume(Unit::M3), - currentPowerConsumption(Unit::KW), - datetimeOfUpdateHumanReadable().c_str()); - - *human_readable = buf; - - snprintf(buf, sizeof(buf)-1, "%s%c%s%c%f%c%f%c%f%c%s", - name().c_str(), separator, - t->id.c_str(), separator, - totalEnergyConsumption(Unit::KWH), separator, - totalVolume(Unit::M3), separator, - currentPowerConsumption(Unit::KW), separator, - datetimeOfUpdateRobot().c_str()); - - *fields = buf; - -#define Q(x,y) "\""#x"\":"#y"," -#define QS(x,y) "\""#x"\":\""#y"\"," -#define QSE(x,y) "\""#x"\":\""#y"\"" - - snprintf(buf, sizeof(buf)-1, "{" - QS(media,heat) - QS(meter,multical302) - QS(name,%s) - QS(id,%s) - Q(total_kwh,%f) - Q(total_volume_m3,%f) - QS(current_kw,%f) - QSE(timestamp,%s) - "}", - name().c_str(), - t->id.c_str(), - totalEnergyConsumption(Unit::KWH), - totalVolume(Unit::M3), - currentPowerConsumption(Unit::KW), - datetimeOfUpdateRobot().c_str()); - - *json = buf; - - envs->push_back(string("METER_JSON=")+*json); - envs->push_back(string("METER_TYPE=multical302")); - envs->push_back(string("METER_ID=")+t->id); - envs->push_back(string("METER_TOTAL_KWH=")+to_string(totalEnergyConsumption(Unit::KWH))); - envs->push_back(string("METER_TOTAL_VOLUME_M3=")+to_string(totalVolume(Unit::M3))); - envs->push_back(string("METER_CURRENT_KW=")+to_string(currentPowerConsumption(Unit::KW))); - envs->push_back(string("METER_TIMESTAMP=")+datetimeOfUpdateRobot()); -} diff --git a/src/meter_omnipower.cc b/src/meter_omnipower.cc index 69b8d74..793b75f 100644 --- a/src/meter_omnipower.cc +++ b/src/meter_omnipower.cc @@ -55,6 +55,9 @@ MeterOmnipower::MeterOmnipower(WMBus *bus, string& name, string& id, string& key MeterCommonImplementation(bus, name, id, key, MeterType::OMNIPOWER, MANUFACTURER_KAM, LinkMode::C1) { addMedia(0x02); + + setExpectedVersion(0x01); + MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*)); } @@ -90,7 +93,6 @@ 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]); - t->expectVersion("omnipower", 0x01); if (t->isEncrypted() && !useAes() && !t->isSimulated()) { warning("(omnipower) warning: telegram is encrypted but no key supplied!\n"); diff --git a/src/meter_qcaloric.cc b/src/meter_qcaloric.cc index 829b6d7..6765cfd 100644 --- a/src/meter_qcaloric.cc +++ b/src/meter_qcaloric.cc @@ -62,7 +62,12 @@ private: MeterQCaloric::MeterQCaloric(WMBus *bus, string& name, string& id, string& key) : MeterCommonImplementation(bus, name, id, key, MeterType::QCALORIC, MANUFACTURER_QDS, LinkMode::C1) { + setEncryptionMode(EncryptionMode::None); + addMedia(0x08); + + setExpectedVersion(0x35); + MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*)); } @@ -93,8 +98,6 @@ 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]); - t->expectVersion("qcaloric", 0x35); - if (t->isEncrypted() && !useAes() && !t->isSimulated()) { warning("(qcaloric) warning: telegram is encrypted but no key supplied!\n"); } diff --git a/src/meter_supercom587.cc b/src/meter_supercom587.cc index 5b489b5..b1e4367 100644 --- a/src/meter_supercom587.cc +++ b/src/meter_supercom587.cc @@ -40,9 +40,7 @@ struct MeterSupercom587 : public virtual WaterMeter, public virtual MeterCommonI bool hasTotalWaterConsumption(); private: - void handleTelegram(Telegram *t); void processContent(Telegram *t); - string decodeTime(int time); double total_water_consumption_m3_ {}; }; @@ -55,59 +53,21 @@ unique_ptr createSupercom587(WMBus *bus, string& name, string& id, s MeterSupercom587::MeterSupercom587(WMBus *bus, string& name, string& id, string& key) : MeterCommonImplementation(bus, name, id, key, MeterType::SUPERCOM587, MANUFACTURER_SON, LinkMode::T1) { + setEncryptionMode(EncryptionMode::AES_CBC); + addMedia(0x06); addMedia(0x07); + setExpectedVersion(0x3c); + addPrint("total", Quantity::Volume, [&](Unit u){ return totalWaterConsumption(u); }, "The total water consumption recorded by this meter.", - true); + true, true); MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*)); } -double MeterSupercom587::totalWaterConsumption(Unit u) -{ - assertQuantity(u, Quantity::Volume); - return convert(total_water_consumption_m3_, Unit::M3, u); -} - - -void MeterSupercom587::handleTelegram(Telegram *t) -{ - if (!isTelegramForMe(t)) { - // This telegram is not intended for this meter. - return; - } - - verbose("(%s) telegram for %s %02x%02x%02x%02x\n", "supercom587", - name().c_str(), - t->a_field_address[0], t->a_field_address[1], t->a_field_address[2], - t->a_field_address[3]); - - t->expectVersion("supercom587", 0x3c); - - if (t->isEncrypted() && !useAes() && !t->isSimulated()) { - warning("(supercom587) warning: telegram is encrypted but no key supplied!\n"); - } - if (useAes()) { - vector aeskey = key(); - decryptMode5_AES_CBC(t, aeskey); - } else { - t->content = t->payload; - } - char log_prefix[256]; - snprintf(log_prefix, 255, "(%s) log", "supercom587"); - logTelegram(log_prefix, t->parsed, t->content); - int content_start = t->parsed.size(); - processContent(t); - if (isDebugEnabled()) { - snprintf(log_prefix, 255, "(%s)", "supercom587"); - t->explainParse(log_prefix, content_start); - } - triggerUpdate(t); -} - void MeterSupercom587::processContent(Telegram *t) { // Meter record: @@ -121,6 +81,12 @@ void MeterSupercom587::processContent(Telegram *t) t->addMoreExplanation(offset, " total consumption (%f m3)", total_water_consumption_m3_); } +double MeterSupercom587::totalWaterConsumption(Unit u) +{ + assertQuantity(u, Quantity::Volume); + return convert(total_water_consumption_m3_, Unit::M3, u); +} + bool MeterSupercom587::hasTotalWaterConsumption() { return true; diff --git a/src/meter_vario451.cc b/src/meter_vario451.cc index 6736b5a..3b79f07 100644 --- a/src/meter_vario451.cc +++ b/src/meter_vario451.cc @@ -51,23 +51,25 @@ unique_ptr createVario451(WMBus *bus, string& name, string& id, strin MeterVario451::MeterVario451(WMBus *bus, string& name, string& id, string& key) : MeterCommonImplementation(bus, name, id, key, MeterType::VARIO451, MANUFACTURER_TCH, LinkMode::T1) { + setEncryptionMode(EncryptionMode::None); + addMedia(0x04); // C telegrams addMedia(0xC3); // T telegrams addPrint("total", Quantity::Energy, [&](Unit u){ return totalEnergyConsumption(u); }, "The total energy consumption recorded by this meter.", - true); + true, true); addPrint("current", Quantity::Energy, [&](Unit u){ return currentPeriodEnergyConsumption(u); }, "Energy consumption so far in this billing period.", - true); + true, true); addPrint("previous", Quantity::Energy, [&](Unit u){ return previousPeriodEnergyConsumption(u); }, "Energy consumption in previous billing period.", - true); + true, true); MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*)); } diff --git a/src/meters.cc b/src/meters.cc index dad24a6..e05dee4 100644 --- a/src/meters.cc +++ b/src/meters.cc @@ -66,9 +66,16 @@ void MeterCommonImplementation::addMedia(int m) } void MeterCommonImplementation::addPrint(string vname, Quantity vquantity, - function getValueFunc, string help, bool field) + function getValueFunc, string help, bool field, bool json) { - prints_.push_back( { vname, vquantity, defaultUnitForQuantity(vquantity), getValueFunc, help, field }); + prints_.push_back( { vname, vquantity, defaultUnitForQuantity(vquantity), getValueFunc, NULL, help, field, json }); +} + +void MeterCommonImplementation::addPrint(string vname, Quantity vquantity, + function getValueFunc, + string help, bool field, bool json) +{ + prints_.push_back( { vname, vquantity, defaultUnitForQuantity(vquantity), NULL, getValueFunc, help, field, json } ); } void MeterCommonImplementation::addManufacturer(int m) @@ -240,13 +247,20 @@ string concatFields(Meter *m, Telegram *t, char c, vector &prints, vector { if (p.field) { - Unit u = replaceWithConversionUnit(p.default_unit, cs); - double v = p.getValueFunc(u); - if (hr) { - s += format3fdot3f(v); - s += " "+unitToStringHR(u); - } else { - s += to_string(v); + if (p.getValueDouble) + { + Unit u = replaceWithConversionUnit(p.default_unit, cs); + double v = p.getValueDouble(u); + if (hr) { + s += valueToString(v, u); + s += " "+unitToStringHR(u); + } else { + s += to_string(v); + } + } + if (p.getValueString) + { + s += p.getValueString(); } s += c; } @@ -268,7 +282,7 @@ void MeterCommonImplementation::handleTelegram(Telegram *t) t->a_field_address[0], t->a_field_address[1], t->a_field_address[2], t->a_field_address[3]); - //t->expectVersion("mkradio3", 0x74); + t->expectVersion(meterName().c_str(), expectedVersion()); if (t->isEncrypted() && !useAes() && !t->isSimulated()) { @@ -277,7 +291,12 @@ void MeterCommonImplementation::handleTelegram(Telegram *t) } if (useAes()) { vector aeskey = key(); - decryptMode1_AES_CTR(t, aeskey); + if (encryptionMode() == EncryptionMode::AES_CTR) { + decryptMode1_AES_CTR(t, aeskey); + } + if (encryptionMode() == EncryptionMode::AES_CBC) { + decryptMode1_AES_CTR(t, aeskey); + } } else { t->content = t->payload; } @@ -317,17 +336,22 @@ void MeterCommonImplementation::printMeter(Telegram *t, s += "\"id\":\""+t->id+"\","; for (Print p : prints_) { - if (p.field) + if (p.json) { string default_unit = unitToStringLowerCase(p.default_unit); string var = p.vname; - s += "\""+var+"_"+default_unit+"\":"+to_string(p.getValueFunc(p.default_unit))+","; + if (p.getValueString) { + s += "\""+var+"\":\""+p.getValueString()+"\","; + } + if (p.getValueDouble) { + s += "\""+var+"_"+default_unit+"\":"+valueToString(p.getValueDouble(p.default_unit), p.default_unit)+","; - Unit u = replaceWithConversionUnit(p.default_unit, conversions_); - if (u != p.default_unit) - { - string unit = unitToStringLowerCase(u); - s += "\""+var+"_"+unit+"\":"+to_string(p.getValueFunc(u))+","; + Unit u = replaceWithConversionUnit(p.default_unit, conversions_); + if (u != p.default_unit) + { + string unit = unitToStringLowerCase(u); + s += "\""+var+"_"+unit+"\":"+valueToString(p.getValueDouble(u), u)+","; + } } } } @@ -341,20 +365,25 @@ void MeterCommonImplementation::printMeter(Telegram *t, for (Print p : prints_) { - if (p.field) + if (p.json) { string default_unit = unitToStringUpperCase(p.default_unit); string var = p.vname; - std::transform(var.begin(), var.end(), var.begin(), ::toupper); - string envvar = "METER_"+var+"_"+default_unit+"="+to_string(p.getValueFunc(p.default_unit)); - envs->push_back(envvar); - - Unit u = replaceWithConversionUnit(p.default_unit, conversions_); - if (u != p.default_unit) - { - string unit = unitToStringUpperCase(u); - string envvar = "METER_"+var+"_"+unit+"="+to_string(p.getValueFunc(u)); + if (p.getValueString) { + s += "\""+var+"\":\""+p.getValueString()+"\","; + } + if (p.getValueDouble) { + std::transform(var.begin(), var.end(), var.begin(), ::toupper); + string envvar = "METER_"+var+"_"+default_unit+"="+valueToString(p.getValueDouble(p.default_unit), p.default_unit); envs->push_back(envvar); + + Unit u = replaceWithConversionUnit(p.default_unit, conversions_); + if (u != p.default_unit) + { + string unit = unitToStringUpperCase(u); + string envvar = "METER_"+var+"_"+unit+"="+valueToString(p.getValueDouble(u), u); + envs->push_back(envvar); + } } } } @@ -393,3 +422,23 @@ double ElectricityMeter::currentPowerProduction() { return -47.11; } double HeatCostMeter::currentConsumption() { return -47.11; } string HeatCostMeter::setDate() { return "47.11"; } double HeatCostMeter::consumptionAtSetDate() { return -47.11; } + +void MeterCommonImplementation::setEncryptionMode(EncryptionMode em) +{ + enc_mode_ = em; +} + +EncryptionMode MeterCommonImplementation::encryptionMode() +{ + return enc_mode_; +} + +void MeterCommonImplementation::setExpectedVersion(int version) +{ + expected_meter_version_ = version; +} + +int MeterCommonImplementation::expectedVersion() +{ + return expected_meter_version_; +} diff --git a/src/meters.h b/src/meters.h index df6df74..302b452 100644 --- a/src/meters.h +++ b/src/meters.h @@ -50,7 +50,8 @@ using namespace std; typedef unsigned char uchar; -struct Meter { +struct Meter +{ virtual vector ids() = 0; virtual string meterName() = 0; virtual string name() = 0; @@ -75,6 +76,8 @@ struct Meter { virtual bool isTelegramForMe(Telegram *t) = 0; virtual bool useAes() = 0; virtual vector key() = 0; + virtual EncryptionMode encryptionMode() = 0; + virtual int expectedVersion() = 0; // Dynamically access all data received for the meter. virtual std::vector getRecords() = 0; diff --git a/src/meters_common_implementation.h b/src/meters_common_implementation.h index 29f9c94..073b04e 100644 --- a/src/meters_common_implementation.h +++ b/src/meters_common_implementation.h @@ -29,9 +29,11 @@ struct Print string vname; // Value name, like: total current previous target Quantity quantity; // Quantity: Energy, Volume Unit default_unit; // Default unit for above quantity: KWH, M3 - function getValueFunc; // Callback to fetch the value from the meter. + function getValueDouble; // Callback to fetch the value from the meter. + function getValueString; // Callback to fetch the value from the meter. string help; // Helpful information on this meters use of this value. bool field; // If true, print in hr/fields output. + bool json; // If true, print in json and shell env variables. }; struct MeterCommonImplementation : public virtual Meter @@ -49,6 +51,7 @@ struct MeterCommonImplementation : public virtual Meter void onUpdate(function cb); int numUpdates(); + EncryptionMode encMode(); bool isTelegramForMe(Telegram *t); bool useAes(); vector key(); @@ -68,10 +71,17 @@ struct MeterCommonImplementation : public virtual Meter protected: void triggerUpdate(Telegram *t); + void setExpectedVersion(int version); + int expectedVersion(); void addConversions(std::vector cs); void addMedia(int media); void addManufacturer(int m); - void addPrint(string vname, Quantity vquantity, function getValueFunc, string help, bool field); + void addPrint(string vname, Quantity vquantity, + function getValueFunc, string help, bool field, bool json); + void addPrint(string vname, Quantity vquantity, + function getValueFunc, string help, bool field, bool json); + void setEncryptionMode(EncryptionMode em); + EncryptionMode encryptionMode(); void handleTelegram(Telegram *t); void printMeter(Telegram *t, string *human_readable, @@ -84,6 +94,7 @@ protected: private: MeterType type_ {}; + int expected_meter_version_ {}; vector media_; set manufacturers_; string name_; @@ -95,6 +106,7 @@ private: bool use_aes_ {}; time_t datetime_of_update_ {}; LinkMode required_link_mode_ {}; + EncryptionMode enc_mode_ {}; protected: std::map> values_; diff --git a/src/units.cc b/src/units.cc index 0529773..e41337d 100644 --- a/src/units.cc +++ b/src/units.cc @@ -133,3 +133,16 @@ Unit replaceWithConversionUnit(Unit u, vector cs) } return u; } + +string valueToString(double v, Unit u) +{ + string s = to_string(v); + while (s.back() == '0') s.pop_back(); + if (s.back() == '.') { + s.pop_back(); + if (s.length() == 0) return "0"; + return s; + } + if (s.length() == 0) return "0"; + return s; +} diff --git a/src/units.h b/src/units.h index d6a9c25..5a78a2e 100644 --- a/src/units.h +++ b/src/units.h @@ -27,6 +27,7 @@ X(Volume,M3) \ X(Flow,M3H) \ X(Temperature,C) \ + X(Text,TXT) \ #define LIST_OF_UNITS \ @@ -36,7 +37,8 @@ X(L,l,l,Volume,"litre") \ X(KW,kw,kW,Power,"kilo Watt") \ X(M3H,m3h,m3/h,Flow,"cubic meters per hour") \ - X(C,c,°C,Temperature,"celsius") \ + X(C,c,°C,Temperature,"celsius") \ + X(TXT,txt,txt,Text,"text") \ enum class Unit { @@ -63,6 +65,7 @@ Unit defaultUnitForQuantity(Quantity q); std::string unitToStringHR(Unit u); std::string unitToStringLowerCase(Unit u); std::string unitToStringUpperCase(Unit u); +std::string valueToString(double v, Unit u); Unit replaceWithConversionUnit(Unit u, std::vector cs); diff --git a/src/wmbus.cc b/src/wmbus.cc index e2c26bb..ca8eaca 100644 --- a/src/wmbus.cc +++ b/src/wmbus.cc @@ -559,7 +559,7 @@ void Telegram::explainParse(string intro, int from) void Telegram::expectVersion(const char *info, int v) { - if (a_field_version != v) { + if (v != 0 && a_field_version != v) { warning("(%s) expected telegram with version 0x%02x, but got version 0x%02x !\n", info, v, a_field_version); } } diff --git a/src/wmbus.h b/src/wmbus.h index 4036548..e6034fb 100644 --- a/src/wmbus.h +++ b/src/wmbus.h @@ -61,6 +61,13 @@ LinkMode isLinkMode(const char *arg); // aka little endian. #define SN_ENC_BITS 0xc0 +enum class EncryptionMode +{ + None, + AES_CBC, + AES_CTR +}; + using namespace std; struct Telegram {