Added evo868 water wmbus module.

pull/221/head
Fredrik Öhrström 2021-01-26 22:22:59 +01:00
rodzic f29e819afd
commit 73c18c1831
11 zmienionych plików z 466 dodań i 0 usunięć

Wyświetl plik

@ -1,4 +1,5 @@
Added support for evo868 water meter addon module.
Added support for whe542 heat cost allocator.
Marc Kolly improved the Waterstar meter and added better error codes

Wyświetl plik

@ -129,6 +129,7 @@ METER_OBJS:=\
$(BUILD)/meter_em24.o \
$(BUILD)/meter_emerlin868.o \
$(BUILD)/meter_ev200.o \
$(BUILD)/meter_evo868.o \
$(BUILD)/meter_eurisii.o \
$(BUILD)/meter_fhkvdataiii.o \
$(BUILD)/meter_fhkvdataiv.o \

Wyświetl plik

@ -267,6 +267,7 @@ Diehl/Sappel IZAR RC 868 I R4 PL (izar) (non-standard protocol)
Diehl HYDRUS (hydrus)
Elster Merlin 868 (emerlin868)
Elster V200H (ev200)
Maddalena EVO 868 (evo868)
Honeywell Q400 (q400)
Kamstrup Multical 21 (multical21)
Kamstrup flowIQ 2200 (flowiq2200)

Wyświetl plik

@ -218,3 +218,8 @@ telegram=|294468506935639176F0A0|009F2782290060822900000401D6311AF93E1BF93E008DC
telegram=|36446850626262624543A1|009F2777010060780000000A000000000000000000000000000000000000000000000000A0400000B4010000|
{"media":"heat","meter":"compact5","name":"Heating","id":"62626262","total_kwh":495,"current_kwh":120,"previous_kwh":375,"timestamp":"1111-11-11T11:11:11Z"}
|Heating;62626262;495.000000;120.000000;375.000000;1111-11-11 11:11.11
# Test Maddalena EVO 868 wmbus module on water meter
telegram=|aa4424347677787950077ac10000202f2f041306070000046d1e31b12104fd17000000000e787880048120004413c9040000426c9f2c840113c904000082016c9f2cd3013b9a0200c4016d0534a7218104fd280182046c9f2c840413c9040000c404131b00000084051300000000c405130000000084061300000000c406130000000084071300000000c407130000000084081300000000c408130000000084091300000000c4091300000000ffff|
{"media":"water","meter":"evo868","name":"Votchka","id":"79787776","total_m3":1.798,"device_date_time":"2021-01-17 17:30","current_status":"OK","fabrication_no":"000218400887","consumption_at_set_date_m3":1.225,"set_date":"2020-12-31","consumption_at_set_date_2_m3":1.225,"set_date_2":"2020-12-31","max_flow_since_datetime_m3h":0.666,"max_flow_datetime":"2021-01-07 20:05","consumption_at_history_1_m3":1.225,"history_1_date":"2020-12-31","consumption_at_history_2_m3":0.027,"history_2_date":"2020-11-30","consumption_at_history_3_m3":0,"history_3_date":"2020-10-31","consumption_at_history_4_m3":0,"history_4_date":"2020-09-30","consumption_at_history_5_m3":0,"history_5_date":"2020-08-31","consumption_at_history_6_m3":0,"history_6_date":"2020-07-31","consumption_at_history_7_m3":0,"history_7_date":"2020-06-30","consumption_at_history_8_m3":0,"history_8_date":"2020-05-31","consumption_at_history_9_m3":0,"history_9_date":"2020-04-30","consumption_at_history_10_m3":0,"history_10_date":"2020-03-31","consumption_at_history_11_m3":0,"history_11_date":"2020-02-29","consumption_at_history_12_m3":0,"history_12_date":"2020-01-31","timestamp":"1111-11-11T11:11:11Z"}
|Votchka;79787776;1.798000;OK;1.225000;2020-12-31;1111-11-11 11:11.11

369
src/meter_evo868.cc 100644
Wyświetl plik

@ -0,0 +1,369 @@
/*
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 <bits/stdc++.h>
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, MeterType::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;
}

Wyświetl plik

@ -41,6 +41,7 @@
X(em24, C1_bit, ElectricityMeter, EM24, EM24) \
X(emerlin868, T1_bit, WaterMeter, EMERLIN868, EMerlin868) \
X(ev200, T1_bit, WaterMeter, EV200, EV200) \
X(evo868, T1_bit, WaterMeter, EVO868, EVO868) \
X(fhkvdataiii,T1_bit, HeatCostAllocationMeter, FHKVDATAIII, FHKVDataIII) \
X(fhkvdataiv, T1_bit, HeatCostAllocationMeter, FHKVDATAIV, FHKVDataIV) \
X(hydrus, T1_bit, WaterMeter, HYDRUS, Hydrus) \
@ -111,6 +112,7 @@
X(EM24, MANUFACTURER_KAM, 0x02, 0x33) \
X(EMERLIN868,MANUFACTURER_ELR, 0x37, 0x11) \
X(EV200, MANUFACTURER_ELR, 0x07, 0x0d) \
X(EVO868, MANUFACTURER_MAD, 0x07, 0x50) \
X(FHKVDATAIII,MANUFACTURER_TCH, 0x80, 0x69) \
X(FHKVDATAIII,MANUFACTURER_TCH, 0x80, 0x94) \
X(FHKVDATAIV,MANUFACTURER_TCH, 0x08, 0x69) \

Wyświetl plik

@ -37,6 +37,7 @@ void test_ids();
void test_kdf();
void test_periods();
void test_devices();
void test_months();
int main(int argc, char **argv)
{
@ -62,6 +63,7 @@ int main(int argc, char **argv)
test_ids();
test_kdf();
test_periods();
test_months();
return 0;
}
@ -644,3 +646,26 @@ void test_devices()
}
void test_months()
{
struct tm date;
date.tm_year = 2020-1900;
date.tm_mon = 12-1;
date.tm_mday = 31;
string s = strdate(&date);
struct tm d;
d = date;
addMonths(&d, -10);
string os = strdate(&d);
if (s != "2020-12-31" ||
os != "2020-02-29")
{
printf("ERROR! Expected 2020-12-31 - 10 months should be 2020-02-29\n"
"But got %s - 11 = %s\n", s.c_str(), os.c_str());
}
}

Wyświetl plik

@ -1180,6 +1180,61 @@ string strdatetimesec(struct tm *datetime)
return string(buf);
}
bool is_leap_year(int year)
{
if (year % 4 != 0) return false;
if (year % 400 == 0) return true;
if (year % 100 == 0) return false;
return true;
}
int days_in_months[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int get_days_in_month(int year, int month)
{
assert(month >= 0);
assert(month < 12);
int days = days_in_months[month];
if (month == 1 && is_leap_year(year))
{
// Handle february in a leap year.
days += 1;
}
return days;
}
void addMonths(struct tm *date, int months)
{
bool is_last_day_in_month = date->tm_mday == get_days_in_month(date->tm_year, date->tm_mon);
int year = date->tm_year + months / 12;
int month = date->tm_mon + months % 12;
if (month > 11)
{
year += 1;
month -= 12;
}
int day;
if (is_last_day_in_month)
{
day = get_days_in_month(year, month); // Last day of month maps to last day of result month
}
else
{
day = std::min(date->tm_mday, get_days_in_month(year, month));
}
date->tm_year = year;
date->tm_mon = month;
date->tm_mday = day;
}
AccessCheck checkIfExistsAndSameGroup(string device)
{
struct stat sb;

Wyświetl plik

@ -55,6 +55,7 @@ std::string strdate(struct tm *date);
std::string strdatetime(struct tm *date);
// Return for example: 2010-03-21 15:22:03
std::string strdatetimesec(struct tm *date);
void addMonths(struct tm* date, int m);
bool stringFoundCaseIgnored(std::string haystack, std::string needle);

Wyświetl plik

@ -220,6 +220,11 @@ Received telegram from: 62626262
type: Heat meter (0x43)
ver: 0x45
driver: compact5
Received telegram from: 79787776
manufacturer: (MAD) Maddalena, Italy (0x3424)
type: Water meter (0x07)
ver: 0x50
driver: evo868
EOF
RES=$($PROG --logfile=$LOGFILE --t1 simulations/simulation_t1.txt 2>&1)

Wyświetl plik

@ -20,6 +20,7 @@ METERS="MyWarmWater supercom587 12345678 NOKEY
Room fhkvdataiii 11776622 NOKEY
Rooom fhkvdataiv 14542076 FCF41938F63432975B52505F547FCEDF
HeatMeter eurisii 88018801 NOKEY
Votchka evo868 79787776 NOKEY
Smokeo lansensm 00010204 NOKEY
Tempoo lansenth 00010203 NOKEY
Dooro lansendw 00010205 NOKEY