Now converted almost all meters.

pull/31/head
weetmuts 2019-05-04 19:56:17 +02:00
rodzic 2a47115cb9
commit fa1e08e7a3
21 zmienionych plików z 317 dodań i 625 usunięć

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -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<string> *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<WaterMeter> createIperl(WMBus *bus, string& name, string& id, string& key)
{
return unique_ptr<WaterMeter>(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<uchar> 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<string,pair<int,DVEntry>> 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<string> *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 "";
}

Wyświetl plik

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

Wyświetl plik

@ -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<string> *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<WaterMeter> 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<uchar> 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<string> *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());
}

Wyświetl plik

@ -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<string> *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<HeatMeter> createMultical302(WMBus *bus, string& name, string& id, string& key) {
return unique_ptr<HeatMeter>(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<uchar> 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<uchar>::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<uchar> 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<HeatMeter> createMultical302(WMBus *bus, string& name, string& id, string& key) {
return unique_ptr<HeatMeter>(new MeterMultical302(bus,name,id,key));
}
void MeterMultical302::printMeter(Telegram *t,
string *human_readable,
string *fields, char separator,
string *json,
vector<string> *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());
}

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -51,23 +51,25 @@ unique_ptr<HeatMeter> 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*));
}

Wyświetl plik

@ -66,9 +66,16 @@ void MeterCommonImplementation::addMedia(int m)
}
void MeterCommonImplementation::addPrint(string vname, Quantity vquantity,
function<double(Unit)> getValueFunc, string help, bool field)
function<double(Unit)> 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<string()> 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<Print> &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<uchar> 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_;
}

Wyświetl plik

@ -50,7 +50,8 @@ using namespace std;
typedef unsigned char uchar;
struct Meter {
struct Meter
{
virtual vector<string> 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<uchar> key() = 0;
virtual EncryptionMode encryptionMode() = 0;
virtual int expectedVersion() = 0;
// Dynamically access all data received for the meter.
virtual std::vector<std::string> getRecords() = 0;

Wyświetl plik

@ -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<double(Unit)> getValueFunc; // Callback to fetch the value from the meter.
function<double(Unit)> getValueDouble; // Callback to fetch the value from the meter.
function<string()> 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<void(Telegram*,Meter*)> cb);
int numUpdates();
EncryptionMode encMode();
bool isTelegramForMe(Telegram *t);
bool useAes();
vector<uchar> 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<Unit> cs);
void addMedia(int media);
void addManufacturer(int m);
void addPrint(string vname, Quantity vquantity, function<double(Unit)> getValueFunc, string help, bool field);
void addPrint(string vname, Quantity vquantity,
function<double(Unit)> getValueFunc, string help, bool field, bool json);
void addPrint(string vname, Quantity vquantity,
function<std::string()> 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<int> media_;
set<int> 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<std::string,std::pair<int,std::string>> values_;

Wyświetl plik

@ -133,3 +133,16 @@ Unit replaceWithConversionUnit(Unit u, vector<Unit> 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;
}

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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