kopia lustrzana https://github.com/weetmuts/wmbusmeters
371 wiersze
13 KiB
C++
371 wiersze
13 KiB
C++
/*
|
|
Copyright (C) 2017-2020 Fredrik Öhrström
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include"dvparser.h"
|
|
#include"meters.h"
|
|
#include"meters_common_implementation.h"
|
|
#include"wmbus.h"
|
|
#include"wmbus_utils.h"
|
|
#include"util.h"
|
|
|
|
#include <algorithm>
|
|
|
|
using namespace std;
|
|
|
|
struct MeterEvo868 : public virtual WaterMeter, public virtual MeterCommonImplementation {
|
|
MeterEvo868(MeterInfo &mi);
|
|
|
|
// Total water counted through the meter
|
|
double totalWaterConsumption(Unit u);
|
|
bool hasTotalWaterConsumption();
|
|
uint32_t error_flags_ {};
|
|
string fabrication_no_;
|
|
double consumption_at_set_date_m3_ {};
|
|
string set_date_;
|
|
double consumption_at_set_date_2_m3_ {};
|
|
string set_date_2_;
|
|
double max_flow_since_datetime_m3h_ {};
|
|
string max_flow_datetime_;
|
|
|
|
double consumption_at_history_date_m3_[12];
|
|
string history_date_[12];
|
|
|
|
string status();
|
|
|
|
private:
|
|
void processContent(Telegram *t);
|
|
|
|
double total_water_consumption_m3_ {};
|
|
string device_date_time_;
|
|
};
|
|
|
|
shared_ptr<WaterMeter> createEVO868(MeterInfo &mi)
|
|
{
|
|
return shared_ptr<WaterMeter>(new MeterEvo868(mi));
|
|
}
|
|
|
|
MeterEvo868::MeterEvo868(MeterInfo &mi) :
|
|
MeterCommonImplementation(mi, MeterDriver::EVO868)
|
|
{
|
|
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
|
|
|
addLinkMode(LinkMode::T1);
|
|
|
|
addPrint("total", Quantity::Volume,
|
|
[&](Unit u){ return totalWaterConsumption(u); },
|
|
"The total water consumption recorded by this meter.",
|
|
true, true);
|
|
|
|
addPrint("device_date_time", Quantity::Text,
|
|
[&](){ return device_date_time_; },
|
|
"Device date time.",
|
|
false, true);
|
|
|
|
addPrint("current_status", Quantity::Text,
|
|
[&](){ return status(); },
|
|
"Status of meter.",
|
|
true, true);
|
|
|
|
addPrint("fabrication_no", Quantity::Text,
|
|
[&](){ return fabrication_no_; },
|
|
"Fabrication number.",
|
|
false, true);
|
|
|
|
addPrint("consumption_at_set_date", Quantity::Volume,
|
|
[&](Unit u){ assertQuantity(u, Quantity::Volume); return convert(consumption_at_set_date_m3_, Unit::M3, u); },
|
|
"The total water consumption at the most recent billing period date.",
|
|
true, true);
|
|
|
|
addPrint("set_date", Quantity::Text,
|
|
[&](){ return set_date_; },
|
|
"The most recent billing period date.",
|
|
true, true);
|
|
|
|
addPrint("consumption_at_set_date_2", Quantity::Volume,
|
|
[&](Unit u){ assertQuantity(u, Quantity::Volume); return convert(consumption_at_set_date_2_m3_, Unit::M3, u); },
|
|
"The total water consumption at the second recent billing period date.",
|
|
false, true);
|
|
|
|
addPrint("set_date_2", Quantity::Text,
|
|
[&](){ return set_date_2_; },
|
|
"The second recent billing period date.",
|
|
false, true);
|
|
|
|
addPrint("max_flow_since_datetime", Quantity::Flow,
|
|
[&](Unit u){ assertQuantity(u, Quantity::Flow); return convert(max_flow_since_datetime_m3h_, Unit::M3H, u); },
|
|
"Maximum water flow since date time.",
|
|
false, true);
|
|
|
|
addPrint("max_flow_datetime", Quantity::Text,
|
|
[&](){ return max_flow_datetime_; },
|
|
"The datetime to which maximum flow is measured.",
|
|
false, true);
|
|
|
|
for (int i=1; i<=12; ++i)
|
|
{
|
|
string key = tostrprintf("consumption_at_history_%d", i);
|
|
string epl = tostrprintf("The total water consumption at the history date %d.", i);
|
|
|
|
addPrint(key, Quantity::Volume,
|
|
[this,i](Unit u){ assertQuantity(u, Quantity::Volume); return convert(consumption_at_history_date_m3_[i-1], Unit::M3, u); },
|
|
epl,
|
|
false, true);
|
|
key = tostrprintf("history_%d_date", i);
|
|
epl = tostrprintf("The history date %d.", i);
|
|
|
|
addPrint(key, Quantity::Text,
|
|
[this,i](){ return history_date_[i-1]; },
|
|
epl,
|
|
false, true);
|
|
}
|
|
|
|
}
|
|
|
|
void MeterEvo868::processContent(Telegram *t)
|
|
{
|
|
/*
|
|
(evo868) 11: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
|
(evo868) 12: 13 vif (Volume l)
|
|
(evo868) 13: * 06070000 total consumption (1.798000 m3)
|
|
|
|
(evo868) 17: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
|
(evo868) 18: 6D vif (Date and time type)
|
|
(evo868) 19: 1E31B121
|
|
|
|
(evo868) 1d: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
|
(evo868) 1e: FD vif (Second extension of VIF-codes)
|
|
(evo868) 1f: 17 vife (Error flags (binary))
|
|
(evo868) 20: 00000000
|
|
|
|
(evo868) 24: 0E dif (12 digit BCD Instantaneous value)
|
|
(evo868) 25: 78 vif (Fabrication no)
|
|
(evo868) 26: 788004812000
|
|
|
|
(evo868) 2c: 44 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
|
|
(evo868) 2d: 13 vif (Volume l)
|
|
(evo868) 2e: C9040000
|
|
|
|
(evo868) 32: 42 dif (16 Bit Integer/Binary Instantaneous value storagenr=1)
|
|
(evo868) 33: 6C vif (Date type G)
|
|
(evo868) 34: 9F2C
|
|
|
|
(evo868) 36: 84 dif (32 Bit Integer/Binary Instantaneous value)
|
|
(evo868) 37: 01 dife (subunit=0 tariff=0 storagenr=2)
|
|
(evo868) 38: 13 vif (Volume l)
|
|
(evo868) 39: C9040000
|
|
|
|
(evo868) 3d: 82 dif (16 Bit Integer/Binary Instantaneous value)
|
|
(evo868) 3e: 01 dife (subunit=0 tariff=0 storagenr=2)
|
|
(evo868) 3f: 6C vif (Date type G)
|
|
(evo868) 40: 9F2C
|
|
|
|
(evo868) 42: D3 dif (24 Bit Integer/Binary Maximum value storagenr=1)
|
|
(evo868) 43: 01 dife (subunit=0 tariff=0 storagenr=3)
|
|
(evo868) 44: 3B vif (Volume flow l/h)
|
|
(evo868) 45: 9A0200
|
|
|
|
(evo868) 48: C4 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
|
|
(evo868) 49: 01 dife (subunit=0 tariff=0 storagenr=3)
|
|
(evo868) 4a: 6D vif (Date and time type)
|
|
(evo868) 4b: 0534A721
|
|
|
|
(evo868) 4f: 81 dif (8 Bit Integer/Binary Instantaneous value)
|
|
(evo868) 50: 04 dife (subunit=0 tariff=0 storagenr=8)
|
|
(evo868) 51: FD vif (Second extension of VIF-codes)
|
|
(evo868) 52: 28 vife (Storage interval month(s))
|
|
(evo868) 53: 01
|
|
|
|
(evo868) 54: 82 dif (16 Bit Integer/Binary Instantaneous value)
|
|
(evo868) 55: 04 dife (subunit=0 tariff=0 storagenr=8)
|
|
(evo868) 56: 6C vif (Date type G)
|
|
(evo868) 57: 9F2C
|
|
|
|
(evo868) 59: 84 dif (32 Bit Integer/Binary Instantaneous value)
|
|
(evo868) 5a: 04 dife (subunit=0 tariff=0 storagenr=8)
|
|
(evo868) 5b: 13 vif (Volume l)
|
|
(evo868) 5c: C9040000
|
|
|
|
(evo868) 60: C4 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
|
|
(evo868) 61: 04 dife (subunit=0 tariff=0 storagenr=9)
|
|
(evo868) 62: 13 vif (Volume l)
|
|
(evo868) 63: 1B000000
|
|
|
|
(evo868) 67: 84 dif (32 Bit Integer/Binary Instantaneous value)
|
|
(evo868) 68: 05 dife (subunit=0 tariff=0 storagenr=10)
|
|
(evo868) 69: 13 vif (Volume l)
|
|
(evo868) 6a: 00000000
|
|
|
|
(evo868) 6e: C4 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
|
|
(evo868) 6f: 05 dife (subunit=0 tariff=0 storagenr=11)
|
|
(evo868) 70: 13 vif (Volume l)
|
|
(evo868) 71: 00000000
|
|
|
|
(evo868) 75: 84 dif (32 Bit Integer/Binary Instantaneous value)
|
|
(evo868) 76: 06 dife (subunit=0 tariff=0 storagenr=12)
|
|
(evo868) 77: 13 vif (Volume l)
|
|
(evo868) 78: 00000000
|
|
|
|
(evo868) 7c: C4 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
|
|
(evo868) 7d: 06 dife (subunit=0 tariff=0 storagenr=13)
|
|
(evo868) 7e: 13 vif (Volume l)
|
|
(evo868) 7f: 00000000
|
|
|
|
(evo868) 83: 84 dif (32 Bit Integer/Binary Instantaneous value)
|
|
(evo868) 84: 07 dife (subunit=0 tariff=0 storagenr=14)
|
|
(evo868) 85: 13 vif (Volume l)
|
|
(evo868) 86: 00000000
|
|
|
|
(evo868) 8a: C4 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
|
|
(evo868) 8b: 07 dife (subunit=0 tariff=0 storagenr=15)
|
|
(evo868) 8c: 13 vif (Volume l)
|
|
(evo868) 8d: 00000000
|
|
|
|
(evo868) 91: 84 dif (32 Bit Integer/Binary Instantaneous value)
|
|
(evo868) 92: 08 dife (subunit=0 tariff=0 storagenr=16)
|
|
(evo868) 93: 13 vif (Volume l)
|
|
(evo868) 94: 00000000
|
|
|
|
(evo868) 98: C4 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
|
|
(evo868) 99: 08 dife (subunit=0 tariff=0 storagenr=17)
|
|
(evo868) 9a: 13 vif (Volume l)
|
|
(evo868) 9b: 00000000
|
|
|
|
(evo868) 9f: 84 dif (32 Bit Integer/Binary Instantaneous value)
|
|
(evo868) a0: 09 dife (subunit=0 tariff=0 storagenr=18)
|
|
(evo868) a1: 13 vif (Volume l)
|
|
(evo868) a2: 00000000
|
|
|
|
(evo868) a6: C4 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
|
|
(evo868) a7: 09 dife (subunit=0 tariff=0 storagenr=19)
|
|
(evo868) a8: 13 vif (Volume l)
|
|
(evo868) a9: 00000000
|
|
*/
|
|
int offset;
|
|
string key;
|
|
|
|
if(findKey(MeasurementType::Instantaneous, ValueInformation::Volume, 0, 0, &key, &t->values)) {
|
|
extractDVdouble(&t->values, key, &offset, &total_water_consumption_m3_);
|
|
t->addMoreExplanation(offset, " total consumption (%f m3)", total_water_consumption_m3_);
|
|
}
|
|
|
|
if (findKey(MeasurementType::Instantaneous, ValueInformation::DateTime, 0, 0, &key, &t->values)) {
|
|
struct tm datetime;
|
|
extractDVdate(&t->values, key, &offset, &datetime);
|
|
device_date_time_ = strdatetime(&datetime);
|
|
t->addMoreExplanation(offset, " device datetime (%s)", device_date_time_.c_str());
|
|
}
|
|
|
|
extractDVuint32(&t->values, "04FD17", &offset, &error_flags_);
|
|
t->addMoreExplanation(offset, " error flags (%s)", status().c_str());
|
|
|
|
extractDVstring(&t->values, "0E78", &offset, &fabrication_no_);
|
|
reverse(fabrication_no_.begin(), fabrication_no_.end());
|
|
t->addMoreExplanation(offset, " fabrication no (%s)", fabrication_no_.c_str());
|
|
|
|
if(findKey(MeasurementType::Instantaneous, ValueInformation::Volume, 1, 0, &key, &t->values)) {
|
|
extractDVdouble(&t->values, key, &offset, &consumption_at_set_date_m3_);
|
|
t->addMoreExplanation(offset, " consumption at set date (%f m3)", consumption_at_set_date_m3_);
|
|
}
|
|
|
|
if (findKey(MeasurementType::Instantaneous, ValueInformation::Date, 1, 0, &key, &t->values)) {
|
|
struct tm date;
|
|
extractDVdate(&t->values, key, &offset, &date);
|
|
set_date_ = strdate(&date);
|
|
t->addMoreExplanation(offset, " set date (%s)", set_date_.c_str());
|
|
}
|
|
|
|
if(findKey(MeasurementType::Instantaneous, ValueInformation::Volume, 2, 0, &key, &t->values)) {
|
|
extractDVdouble(&t->values, key, &offset, &consumption_at_set_date_2_m3_);
|
|
t->addMoreExplanation(offset, " consumption at set date 2 (%f m3)", consumption_at_set_date_2_m3_);
|
|
}
|
|
|
|
if (findKey(MeasurementType::Instantaneous, ValueInformation::Date, 2, 0, &key, &t->values)) {
|
|
struct tm date;
|
|
extractDVdate(&t->values, key, &offset, &date);
|
|
set_date_2_ = strdate(&date);
|
|
t->addMoreExplanation(offset, " set date 2 (%s)", set_date_2_.c_str());
|
|
}
|
|
|
|
if(findKey(MeasurementType::Maximum, ValueInformation::VolumeFlow, 3, 0, &key, &t->values)) {
|
|
extractDVdouble(&t->values, key, &offset, &max_flow_since_datetime_m3h_);
|
|
t->addMoreExplanation(offset, " max flow (%f m3/h)", max_flow_since_datetime_m3h_);
|
|
}
|
|
|
|
if (findKey(MeasurementType::Instantaneous, ValueInformation::DateTime, 3, 0, &key, &t->values)) {
|
|
struct tm datetime;
|
|
extractDVdate(&t->values, key, &offset, &datetime);
|
|
max_flow_datetime_ = strdatetime(&datetime);
|
|
t->addMoreExplanation(offset, " max flow since datetime (%s)", max_flow_datetime_.c_str());
|
|
}
|
|
|
|
uint8_t month_increment = 0;
|
|
extractDVuint8(&t->values, "8104FD28", &offset, &month_increment);
|
|
t->addMoreExplanation(offset, " month increment (%d)", month_increment);
|
|
|
|
struct tm date;
|
|
if (findKey(MeasurementType::Instantaneous, ValueInformation::Date, 8, 0, &key, &t->values)) {
|
|
extractDVdate(&t->values, key, &offset, &date);
|
|
string start = strdate(&date);
|
|
t->addMoreExplanation(offset, " history starts with date (%s)", start.c_str());
|
|
}
|
|
|
|
// 12 months of historical data, starting in storage 8.
|
|
for (int i=1; i<=12; ++i)
|
|
{
|
|
if(findKey(MeasurementType::Instantaneous, ValueInformation::Volume, i+7, 0, &key, &t->values)) {
|
|
extractDVdouble(&t->values, key, &offset, &consumption_at_history_date_m3_[i-1]);
|
|
t->addMoreExplanation(offset, " consumption at history %d (%f m3)", i, consumption_at_history_date_m3_[i-1]);
|
|
struct tm d = date;
|
|
if (i>1) addMonths(&d, 1-i);
|
|
history_date_[i-1] = strdate(&d);
|
|
}
|
|
}
|
|
}
|
|
|
|
double MeterEvo868::totalWaterConsumption(Unit u)
|
|
{
|
|
assertQuantity(u, Quantity::Volume);
|
|
return convert(total_water_consumption_m3_, Unit::M3, u);
|
|
}
|
|
|
|
bool MeterEvo868::hasTotalWaterConsumption()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
string MeterEvo868::status()
|
|
{
|
|
if (error_flags_ == 0)
|
|
{
|
|
return "OK";
|
|
}
|
|
|
|
/* Possible errors according to datasheet:
|
|
overflow (threshold configurable, must be activated)
|
|
backflow (threshold set, configurable)
|
|
leak
|
|
meter blocked
|
|
non-used (days threshold set, configurable)
|
|
magnetic and mechanical tampering (removal)
|
|
*/
|
|
|
|
// How do we decode these?
|
|
string info;
|
|
strprintf(info, "ERROR bits %08x", error_flags_);
|
|
return info;
|
|
}
|