Added the bfw240radio driver.

pull/631/head
Fredrik Öhrström 2022-09-28 19:38:12 +02:00
rodzic 5e3ba5cb6f
commit 9c39e911c7
6 zmienionych plików z 160 dodań i 239 usunięć

Wyświetl plik

@ -1,4 +1,6 @@
Added the bfw 240 radio heat cost allocator.
Kajetan Krykwiński added support for the Apator E-ITN heat cost allocator. Thanks Kajetan!
Improved wmbusmeters to gracefully handle bad telegrams with multiple difvif entries

Wyświetl plik

@ -463,6 +463,7 @@ Sontex 868 (sontex868)
Techem FHKV data II/III (fhkvdataiii)
Siemens WHE542 (whe5x)
BMeters Hydroclima RFM (hydroclima)
BFW 240 (bfw240radio)
Supported heat meters:
Heat meter Techem Compact V / Compact Ve (compact5) (non-standard protocol)

Wyświetl plik

@ -0,0 +1,157 @@
/*
Copyright (C) 2021-2022 Fredrik Öhrström (gpl-3.0-or-later)
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"meters_common_implementation.h"
namespace
{
struct Driver : public virtual MeterCommonImplementation
{
Driver(MeterInfo &mi, DriverInfo &di);
void processContent(Telegram *t);
double current_hca_;
double prev_hca_;
double historic_hca_[18];
string device_date_;
};
static bool ok = registerDriver([](DriverInfo&di)
{
di.setName("bfw240radio");
di.setDefaultFields("name,id,current_hca,prev_hca,timestamp");
di.addLinkMode(LinkMode::T1);
di.setMeterType(MeterType::HeatCostAllocationMeter);
di.addDetection(MANUFACTURER_BFW,0x08, 0x02);
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
});
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
{
addPrint("current", Quantity::HCA,
[&](Unit u){ return convert(current_hca_, Unit::HCA, u);},
"Energy consumption so far in this billing period.",
PrintProperty::FIELD | PrintProperty::JSON);
addPrint("prev", Quantity::HCA,
[&](Unit u){ return convert(prev_hca_, Unit::HCA, u); },
"Energy consumption at end of previous billing period.",
PrintProperty::FIELD | PrintProperty::JSON);
for (int i=0; i<18; ++i)
{
string info = tostrprintf("prev_%02d", i+1);
string about = tostrprintf("Energy consumption %d months ago.", i+1);
addPrint(info, Quantity::HCA,
[this,i](Unit u){ return convert(historic_hca_[i], Unit::HCA, u);},
about, PrintProperty::JSON);
}
addPrint("device_date", Quantity::Text,
[&](){ return device_date_; },
"Device date when telegram was sent.",
PrintProperty::JSON);
}
int getHistoric(int n, vector<uchar> &content)
{
assert(n >= 0 && n < 18);
assert(content.size() == 40);
int offset = (n*12)/8;
int remainder = (n*12)%8;
uchar lo, hi;
if (remainder == 0)
{
lo = content[36-offset];
hi = 0x0f & content[36-1-offset];
}
else
{
assert(remainder == 4);
lo = content[36-1-offset];
hi = (0xf0 & content[36-offset]) >> 4;
}
return hi*256 + lo;
}
/*
date of telegram reception--------------------------------------------------------------------------------|
|
18 historic monthly values (newest to the right, byte-reordering for 2.,4.,6.,...-oldest month)----| |
| |
???------------------------| | |
| | |
current consumption---| | | |
| | | |
prev. cons.---vvvv vvvv vvvv vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv vvvvvv
feb: 2F2F6F1F 0144 0100 1470 000 000 000 000 000 000 000 000 000 000 000 000 000 000 370 09B 441 0AC 260221
mar: 2F2F481F 0144 0100 1470 000 000 000 000 000 000 000 000 000 000 000 000 000 037 9B0 144 AC0 100 040321
apr: 2F2F871F 0144 013C 1470 000 000 000 000 000 000 000 000 000 000 000 000 370 09B 441 0AC 001 13C 030421
*/
void Driver::processContent(Telegram *t)
{
vector<uchar> content;
t->extractPayload(&content);
if (content.size() < 40) return;
device_date_ = tostrprintf("20%02x-%02x-%02x", content[39], content[39-1], content[39-2]);
current_hca_ = content[6]*256 + content[7];
prev_hca_ = content[4]*256 + content[5];
for (int i=0; i<18; ++i)
{
historic_hca_[i] = getHistoric(i, content);
}
}
}
// Test: bfw bfw240radio 00707788 NOKEY
// telegram=|3644D7088877700002087ADBC000002F2F9E1F03C10388152A00000000000000000000000000000204000404000EE2020AC1321D280221|
// {"media":"heat cost allocation","meter":"bfw240radio","name":"bfw","id":"00707788","current_hca":904,"prev_hca":961,"prev_01_hca":541,"prev_02_hca":961,"prev_03_hca":522,"prev_04_hca":226,"prev_05_hca":14,"prev_06_hca":4,"prev_07_hca":4,"prev_08_hca":4,"prev_09_hca":2,"prev_10_hca":0,"prev_11_hca":0,"prev_12_hca":0,"prev_13_hca":0,"prev_14_hca":0,"prev_15_hca":0,"prev_16_hca":0,"prev_17_hca":0,"prev_18_hca":0,"device_date":"2021-02-28","timestamp":"1111-11-11T11:11:11Z"}
// |bfw;00707788;904;961;1111-11-11 11:11.11
// telegram=|3644D7088877700002087A8BC000002F2F011F03C1038D152A0000000000000000000000000200040400040E00E20A23C11D238D010321|
// {"media":"heat cost allocation","meter":"bfw240radio","name":"bfw","id":"00707788","current_hca":909,"prev_hca":961,"prev_01_hca":909,"prev_02_hca":541,"prev_03_hca":961,"prev_04_hca":522,"prev_05_hca":226,"prev_06_hca":14,"prev_07_hca":4,"prev_08_hca":4,"prev_09_hca":4,"prev_10_hca":2,"prev_11_hca":0,"prev_12_hca":0,"prev_13_hca":0,"prev_14_hca":0,"prev_15_hca":0,"prev_16_hca":0,"prev_17_hca":0,"prev_18_hca":0,"device_date":"2021-03-01","timestamp":"1111-11-11T11:11:11Z"}
// |bfw;00707788;909;961;1111-11-11 11:11.11
// Test: bfww bfw240radio 00707076 NOKEY
// telegram=|3644D7087670700002087A9CC000002F2F6E1F000000000B36000000000000000000000000000000000000000000000000000000260221|
// {"media":"heat cost allocation","meter":"bfw240radio","name":"bfww","id":"00707076","current_hca":0,"prev_hca":0,"prev_01_hca":0,"prev_02_hca":0,"prev_03_hca":0,"prev_04_hca":0,"prev_05_hca":0,"prev_06_hca":0,"prev_07_hca":0,"prev_08_hca":0,"prev_09_hca":0,"prev_10_hca":0,"prev_11_hca":0,"prev_12_hca":0,"prev_13_hca":0,"prev_14_hca":0,"prev_15_hca":0,"prev_16_hca":0,"prev_17_hca":0,"prev_18_hca":0,"device_date":"2021-02-26","timestamp":"1111-11-11T11:11:11Z"}
// |bfww;00707076;0;0;1111-11-11 11:11.11
// telegram=|3644D7087670700002087A27C000002F2F011F000000000B36000000000000000000000000000000000000000000000000000000010321|
// {"media":"heat cost allocation","meter":"bfw240radio","name":"bfww","id":"00707076","current_hca":0,"prev_hca":0,"prev_01_hca":0,"prev_02_hca":0,"prev_03_hca":0,"prev_04_hca":0,"prev_05_hca":0,"prev_06_hca":0,"prev_07_hca":0,"prev_08_hca":0,"prev_09_hca":0,"prev_10_hca":0,"prev_11_hca":0,"prev_12_hca":0,"prev_13_hca":0,"prev_14_hca":0,"prev_15_hca":0,"prev_16_hca":0,"prev_17_hca":0,"prev_18_hca":0,"device_date":"2021-03-01","timestamp":"1111-11-11T11:11:11Z"}
// |bfww;00707076;0;0;1111-11-11 11:11.11
// Test: bfwww bfw240radio 00707447 NOKEY
// telegram=|3644D7084774700002087A80C000002F2F6F1F01440100147000000000000000000000000000000000000000000037009B4410AC260221|
// {"media":"heat cost allocation","meter":"bfw240radio","name":"bfwww","id":"00707447","current_hca":256,"prev_hca":324,"prev_01_hca":172,"prev_02_hca":324,"prev_03_hca":155,"prev_04_hca":55,"prev_05_hca":0,"prev_06_hca":0,"prev_07_hca":0,"prev_08_hca":0,"prev_09_hca":0,"prev_10_hca":0,"prev_11_hca":0,"prev_12_hca":0,"prev_13_hca":0,"prev_14_hca":0,"prev_15_hca":0,"prev_16_hca":0,"prev_17_hca":0,"prev_18_hca":0,"device_date":"2021-02-26","timestamp":"1111-11-11T11:11:11Z"}
// |bfwww;00707447;256;324;1111-11-11 11:11.11
// telegram=|3644D7084774700002087AE1C000002F2F481F0144010014700000000000000000000000000000000000000000379B0144AC0100040321|
// {"media":"heat cost allocation","meter":"bfw240radio","name":"bfwww","id":"00707447","current_hca":256,"prev_hca":324,"prev_01_hca":256,"prev_02_hca":172,"prev_03_hca":324,"prev_04_hca":155,"prev_05_hca":55,"prev_06_hca":0,"prev_07_hca":0,"prev_08_hca":0,"prev_09_hca":0,"prev_10_hca":0,"prev_11_hca":0,"prev_12_hca":0,"prev_13_hca":0,"prev_14_hca":0,"prev_15_hca":0,"prev_16_hca":0,"prev_17_hca":0,"prev_18_hca":0,"device_date":"2021-03-04","timestamp":"1111-11-11T11:11:11Z"}
// |bfwww;00707447;256;324;1111-11-11 11:11.11

Wyświetl plik

@ -1,237 +0,0 @@
/*
Copyright (C) 2021 Fredrik Öhrström (gpl-3.0-or-later)
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"units.h"
#include"util.h"
#include <ctime>
struct MeterBFW240RADIO : public virtual MeterCommonImplementation
{
MeterBFW240RADIO(MeterInfo &mi);
double currentPeriodEnergyConsumption(Unit u);
string currentPeriodDate();
double previousPeriodEnergyConsumption(Unit u);
string previousPeriodDate();
double currentRoomTemperature(Unit u);
double currentRadiatorTemperature(Unit u);
private:
void processContent(Telegram *t);
string leadingZeroString(int num);
double curr_energy_hca_ {};
string curr_energy_hca_date {};
double prev_energy_hca_ {};
string prev_energy_hca_date {};
double temp_room_ {};
double temp_radiator_ {};
};
shared_ptr<Meter> createBFW240Radio(MeterInfo &mi)
{
return shared_ptr<Meter>(new MeterBFW240RADIO(mi));
}
MeterBFW240RADIO::MeterBFW240RADIO(MeterInfo &mi) :
MeterCommonImplementation(mi, "bfw240radio")
{
setMeterType(MeterType::HeatCostAllocationMeter);
// media 0x80 T telegrams
addLinkMode(LinkMode::T1);
addPrint("current", Quantity::HCA,
[&](Unit u){ return currentPeriodEnergyConsumption(u); },
"Energy consumption so far in this billing period.",
PrintProperty::FIELD | PrintProperty::JSON);
/* addPrint("current_date", Quantity::Text,
[&](){ return currentPeriodDate(); },
"Date of current billing period.",
PrintProperty::FIELD | PrintProperty::JSON);
*/
addPrint("previous", Quantity::HCA,
[&](Unit u){ return previousPeriodEnergyConsumption(u); },
"Energy consumption in previous billing period.",
PrintProperty::FIELD | PrintProperty::JSON);
/*
addPrint("previous_date", Quantity::Text,
[&](){ return previousPeriodDate(); },
"Date of last billing period.",
PrintProperty::FIELD | PrintProperty::JSON);
addPrint("temp_room", Quantity::Temperature,
[&](Unit u){ return currentRoomTemperature(u); },
"Current room temperature.",
PrintProperty::FIELD | PrintProperty::JSON);
addPrint("temp_radiator", Quantity::Temperature,
[&](Unit u){ return currentRadiatorTemperature(u); },
"Current radiator temperature.",
PrintProperty::FIELD | PrintProperty::JSON);
*/
}
double MeterBFW240RADIO::currentPeriodEnergyConsumption(Unit u)
{
return curr_energy_hca_;
}
string MeterBFW240RADIO::currentPeriodDate()
{
return curr_energy_hca_date;
}
double MeterBFW240RADIO::previousPeriodEnergyConsumption(Unit u)
{
return prev_energy_hca_;
}
string MeterBFW240RADIO::previousPeriodDate()
{
return prev_energy_hca_date;
}
double MeterBFW240RADIO::currentRoomTemperature(Unit u)
{
assertQuantity(u, Quantity::Temperature);
return convert(temp_room_, Unit::C, u);
}
double MeterBFW240RADIO::currentRadiatorTemperature(Unit u)
{
assertQuantity(u, Quantity::Temperature);
return convert(temp_radiator_, Unit::C, u);
}
void MeterBFW240RADIO::processContent(Telegram *t)
{
// Unfortunately, the Techem FHKV data ii/iii is mostly a proprieatary protocol
// simple wrapped inside a wmbus telegram since the ci-field is 0xa0.
// Which means that the entire payload is manufacturer specific.
vector<uchar> content;
t->extractPayload(&content);
debugPayload("THE PAYLOAD", content);
if (content.size() < 2) return;
int offset = 0;
if (content[0] == 0x2f && content[0] == 0x2f) offset = 2;
// Consumption
// Previous Consumption
uchar prev_hi = content[offset+2];
uchar prev_lo = content[offset+3];
double prev = (256.0*prev_hi+prev_lo);
prev_energy_hca_ = prev;
string prevs;
strprintf(prevs, "%02x%02x", prev_lo, prev_hi);
/*
// Previous Date
uchar date_prev_lo = content[1];
uchar date_prev_hi = content[2];
int date_prev = (256.0*date_prev_hi+date_prev_lo);
int day_prev = (date_prev >> 0) & 0x1F;
int month_prev = (date_prev >> 5) & 0x0F;
int year_prev = (date_prev >> 9) & 0x3F;
prev_energy_hca_date = std::to_string((year_prev + 2000)) + "-" + leadingZeroString(month_prev) + "-" + leadingZeroString(day_prev) + "T02:00:00Z";
*/
//t->addMoreExplanation(offset, " last date of previous billing period (%s)", prev_energy_hca_date);
// Current Consumption
uchar curr_hi = content[offset+4];
uchar curr_lo = content[offset+5];
double curr = (256.0*curr_hi+curr_lo);
curr_energy_hca_ = curr;
string currs;
strprintf(currs, "%02x%02x", curr_lo, curr_hi);
/*
// Current Date
uchar date_curr_lo = content[5];
uchar date_curr_hi = content[6];
int date_curr = (256.0*date_curr_hi+date_curr_lo);
time_t now = time(0);
tm *ltm = localtime(&now);
int year_curr = 1900 + ltm->tm_year;
int day_curr = (date_curr >> 4) & 0x1F;
if (day_curr <= 0) day_curr = 1;
int month_curr = (date_curr >> 9) & 0x0F;
if (month_curr <= 0) month_curr = 12;
curr_energy_hca_date = to_string(year_curr) + "-" + leadingZeroString(month_curr) + "-" + leadingZeroString(day_curr) + "T02:00:00Z";
// t->addMoreExplanation(offset, " last date of current billing period (%s)", curr_energy_hca_date);
// Temperature
uchar room_tlo;
uchar room_thi;
uchar radiator_tlo;
uchar radiator_thi;
if(t->dll_version == 0x94)
{
room_tlo = content[10];
room_thi = content[11];
radiator_tlo = content[12];
radiator_thi = content[13];
} else
{
room_tlo = content[9];
room_thi = content[10];
radiator_tlo = content[11];
radiator_thi = content[12];
}
// Room Temperature
double room_t = (256.0*room_thi+room_tlo)/100;
temp_room_ = room_t;
string room_ts;
strprintf(room_ts, "%02x%02x", room_tlo, room_thi);
// t->addMoreExplanation(offset, " current room temparature (%f °C)", room_ts);
// Radiator Temperature
double radiator_t = (256.0*radiator_thi+radiator_tlo)/100;
temp_radiator_ = radiator_t;
string radiator_ts;
strprintf(radiator_ts, "%02x%02x", radiator_tlo, radiator_thi);
// t->addMoreExplanation(offset, " current radiator temparature (%f °C)", radiator_ts);
*/
}
string MeterBFW240RADIO::leadingZeroString(int num) {
string new_num = (num < 10 ? "0": "") + std::to_string(num);
return new_num;
}

Wyświetl plik

@ -27,7 +27,6 @@
// meter driver, manufacturer, media, version
//
#define METER_DETECTION \
X(BFW240RADIO, MANUFACTURER_BFW,0x08, 0x02) \
X(CCx01, MANUFACTURER_GSS, 0x02, 0x01) \
X(COMPACT5, MANUFACTURER_TCH, 0x04, 0x45) \
X(COMPACT5, MANUFACTURER_TCH, 0xc3, 0x45) \

Wyświetl plik

@ -60,7 +60,6 @@ LIST_OF_METER_TYPES
#define LIST_OF_METERS \
X(auto, 0, AutoMeter, AUTO, Auto) \
X(unknown, 0, UnknownMeter, UNKNOWN, Unknown) \
X(bfw240radio,T1_bit, HeatCostAllocationMeter, BFW240RADIO, BFW240Radio) \
X(compact5, T1_bit, HeatMeter, COMPACT5, Compact5) \
X(dme_07, T1_bit, WaterMeter, DME_07, DME_07) \
X(ebzwmbe, T1_bit, ElectricityMeter, EBZWMBE, EBZWMBE) \