Refactor hydrocalm3 driver to new format.

pull/708/head
Fredrik Öhrström 2022-11-26 20:05:06 +01:00
rodzic 614e47de0b
commit 8498fdf222
9 zmienionych plików z 192 dodań i 202 usunięć

Wyświetl plik

@ -9,6 +9,9 @@ using the calculation feature.
E.g. --calculate_total_gj=total_kwh
ATTENTION! The hydrocalm3 driver has been refactored to the new driver format.
The field device_date_time has been renamed to device_datetime.
ATTENTION! The fhkvdataiv driver has been refactored to the new driver format.
The field consumption_at_set_date_17 was wrong and has been renamed to field consumption_at_set_date_8.

Wyświetl plik

@ -294,8 +294,8 @@ telegram=|6044B8059430040001037A1D005085E2B670BCF1A5C87E0C1A51DA18924EF984613DA2
# Test Hydrocal M3 heat/cooling meter
telegram=|8E44B409747372710B0D7A798080052F2F_0C0E59600100046D1D36B9290C13679947000C0E000000000C13590000000C13000000000C13000000000A5A18020A5E11020F823D06003D06003D06003D0600140600620500480400E402001601000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002F2F|
{"media":"heat/cooling load","meter":"hydrocalm3","name":"HeatCool","id":"71727374","total_heating_kwh":4460.833333,"total_cooling_kwh":0,"device_date_time":"2021-09-25 22:29","total_heating_m3":479.967,"total_cooling_m3":0.059,"c1_volume_m3":0,"c2_volume_m3":0,"supply_temperature_c":21.8,"return_temperature_c":21.1,"timestamp":"1111-11-11T11:11:11Z"}
|HeatCool;71727374;4460.833333;0.000000;1111-11-11 11:11.11
{"media":"heat/cooling load","meter":"hydrocalm3","name":"HeatCool","id":"71727374","status": "TPL_MFCT_80","total_heating_kwh":4460.833333,"total_cooling_kwh":0,"device_datetime":"2021-09-25 22:29","total_heating_m3":479.967,"total_cooling_m3":0.059,"c1_volume_m3":0,"c2_volume_m3":0,"supply_temperature_c":21.8,"return_temperature_c":21.1,"timestamp":"1111-11-11T11:11:11Z"}
|HeatCool;71727374;4460.833333;0;1111-11-11 11:11.11
# Test Weptech Munia temperature hygrometer
telegram=|2E44B05C82340100021B7A460000002F2F0A6601020AFB1A570602FD971D00002F2F2F2F2F2F2F2F2F2F2F2F2F2F2F|

Wyświetl plik

@ -0,0 +1,157 @@
/*
Copyright (C) 2018-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);
};
static bool ok = registerDriver([](DriverInfo&di)
{
di.setName("hydrocalm3");
di.setDefaultFields("name,id,total_heating_kwh,total_cooling_kwh,timestamp");
di.setMeterType(MeterType::HeatMeter);
di.addLinkMode(LinkMode::T1);
di.addDetection(MANUFACTURER_BMT, 0x0d, 0x0b);
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
});
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
{
addStringField(
"status",
"Meter status from tpl status field.",
PrintProperty::JSON | PrintProperty::IMPORTANT |
PrintProperty::STATUS | PrintProperty::JOIN_TPL_STATUS);
addNumericFieldWithExtractor(
"total_heating",
"The total heating energy consumption recorded by this meter.",
PrintProperty::JSON,
Quantity::Energy,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::EnergyMJ)
.set(IndexNr(1))
);
addNumericFieldWithExtractor(
"device",
"The date time when the recording was made.",
PrintProperty::JSON,
Quantity::PointInTime,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::DateTime)
);
addNumericFieldWithExtractor(
"total_cooling",
"The total cooling energy consumption recorded by this meter.",
PrintProperty::JSON,
Quantity::Energy,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::EnergyMJ)
.set(IndexNr(2))
);
addNumericFieldWithExtractor(
"total_heating",
"Total heating volume of media.",
PrintProperty::JSON,
Quantity::Volume,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::Volume)
.set(IndexNr(1))
);
addNumericFieldWithExtractor(
"total_cooling",
"Total cooling volume of media.",
PrintProperty::JSON,
Quantity::Volume,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::Volume)
.set(IndexNr(2))
);
addNumericFieldWithExtractor(
"c1_volume",
"Supply c1 volume.",
PrintProperty::JSON,
Quantity::Volume,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::Volume)
.set(IndexNr(3))
);
addNumericFieldWithExtractor(
"c2_volume",
"Return c2 volume.",
PrintProperty::JSON,
Quantity::Volume,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::Volume)
.set(IndexNr(4))
);
addNumericFieldWithExtractor(
"supply_temperature",
"The supply t1 pipe temperature.",
PrintProperty::JSON,
Quantity::Temperature,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::FlowTemperature)
.set(IndexNr(1))
);
addNumericFieldWithExtractor(
"return_temperature",
"The return t2 pipe temperature.",
PrintProperty::JSON,
Quantity::Temperature,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::ReturnTemperature)
);
}
}
// Test:HeatCool hydrocalm3 71727374 NOKEY
// telegram=|8E44B409747372710B0D7A798080052F2F_0C0E59600100046D1D36B9290C13679947000C0E000000000C13590000000C13000000000C13000000000A5A18020A5E11020F823D06003D06003D06003D0600140600620500480400E402001601000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002F2F|
// {"media":"heat/cooling load","meter":"hydrocalm3","name":"HeatCool","id":"71727374","status": "TPL_MFCT_80","total_heating_kwh":4460.833333,"total_cooling_kwh":0,"device_datetime":"2021-09-25 22:29","total_heating_m3":479.967,"total_cooling_m3":0.059,"c1_volume_m3":0,"c2_volume_m3":0,"supply_temperature_c":21.8,"return_temperature_c":21.1,"timestamp":"1111-11-11T11:11:11Z"}
// |HeatCool;71727374;4460.833333;0;1111-11-11 11:11.11

Wyświetl plik

@ -28,7 +28,6 @@
//
#define METER_DETECTION \
X(CCx01, MANUFACTURER_GSS, 0x02, 0x01) \
X(HYDROCALM3,MANUFACTURER_BMT, 0x0d, 0x0b) \
X(HYDRODIGIT,MANUFACTURER_BMT, 0x06, 0x13) \
X(HYDRODIGIT,MANUFACTURER_BMT, 0x07, 0x13) \
X(HYDRODIGIT,MANUFACTURER_BMT, 0x07, 0x15) \

Wyświetl plik

@ -1,185 +0,0 @@
/*
Copyright (C) 2018-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"meters.h"
#include"meters_common_implementation.h"
#include"dvparser.h"
#include"wmbus.h"
#include"wmbus_utils.h"
#include"util.h"
#define INFO_CODE_VOLTAGE_INTERRUPTED 1
#define INFO_CODE_LOW_BATTERY_LEVEL 2
#define INFO_CODE_EXTERNAL_ALARM 4
#define INFO_CODE_SENSOR_T1_ABOVE_MEASURING_RANGE 8
#define INFO_CODE_SENSOR_T2_ABOVE_MEASURING_RANGE 16
#define INFO_CODE_SENSOR_T1_BELOW_MEASURING_RANGE 32
#define INFO_CODE_SENSOR_T2_BELOW_MEASURING_RANGE 64
#define INFO_CODE_TEMP_DIFF_WRONG_POLARITY 128
struct MeterHydrocalM3 : public virtual MeterCommonImplementation {
MeterHydrocalM3(MeterInfo &mi);
private:
void processContent(Telegram *t);
double total_heating_energy_kwh_ {};
double total_heating_volume_m3_ {};
double total_cooling_energy_kwh_ {};
double total_cooling_volume_m3_ {};
double t1_temperature_c_ { 127 };
double t2_temperature_c_ { 127 };
double c1_volume_m3_ {};
double c2_volume_m3_ {};
string device_date_time_ {};
};
MeterHydrocalM3::MeterHydrocalM3(MeterInfo &mi) :
MeterCommonImplementation(mi, "hydrocalm3")
{
setMeterType(MeterType::HeatMeter);
setExpectedELLSecurityMode(ELLSecurityMode::AES_CTR);
addLinkMode(LinkMode::T1);
addPrint("total_heating", Quantity::Energy,
[&](Unit u){ assertQuantity(u, Quantity::Energy); return convert(total_heating_energy_kwh_, Unit::KWH, u); },
"The total heating energy consumption recorded by this meter.",
PrintProperty::FIELD | PrintProperty::JSON);
addPrint("total_cooling", Quantity::Energy,
[&](Unit u){ assertQuantity(u, Quantity::Energy); return convert(total_cooling_energy_kwh_, Unit::KWH, u); },
"The total cooling energy consumption recorded by this meter.",
PrintProperty::FIELD | PrintProperty::JSON);
addPrint("device_date_time", Quantity::Text,
[&](){ return device_date_time_; },
"Date when total energy consumption was recorded.",
PrintProperty::JSON);
addPrint("total_heating", Quantity::Volume,
[&](Unit u){ assertQuantity(u, Quantity::Volume); return convert(total_heating_volume_m3_, Unit::M3, u); },
"Total heating volume of media.",
PrintProperty::JSON);
addPrint("total_cooling", Quantity::Volume,
[&](Unit u){ assertQuantity(u, Quantity::Volume); return convert(total_cooling_volume_m3_, Unit::M3, u); },
"Total cooling volume of media.",
PrintProperty::JSON);
addPrint("c1_volume", Quantity::Volume,
[&](Unit u){ assertQuantity(u, Quantity::Volume); return convert(c1_volume_m3_, Unit::M3, u); },
"Supply c1 volume.",
PrintProperty::JSON);
addPrint("c2_volume", Quantity::Volume,
[&](Unit u){ assertQuantity(u, Quantity::Volume); return convert(c2_volume_m3_, Unit::M3, u); },
"Return c2 volume.",
PrintProperty::JSON);
addPrint("supply_temperature", Quantity::Temperature,
[&](Unit u){ return convert(t1_temperature_c_, Unit::C, u); },
"The supply t1 pipe temperature.",
PrintProperty::JSON);
addPrint("return_temperature", Quantity::Temperature,
[&](Unit u){ return convert(t2_temperature_c_, Unit::C, u); },
"The return t2 pipe temperature.",
PrintProperty::JSON);
}
shared_ptr<Meter> createHydrocalM3(MeterInfo &mi) {
return shared_ptr<Meter>(new MeterHydrocalM3(mi));
}
void MeterHydrocalM3::processContent(Telegram *t)
{
int offset;
string key;
if (findKey(MeasurementType::Instantaneous, VIFRange::DateTime, 0, 0, &key, &t->dv_entries))
{
struct tm datetime;
extractDVdate(&t->dv_entries, key, &offset, &datetime);
device_date_time_ = strdatetime(&datetime);
t->addMoreExplanation(offset, " device date time (%s)", device_date_time_.c_str());
}
// The meter either sends the total energy consumed as kWh or as MJ.
// First look for kwh
if (findKeyWithNr(MeasurementType::Instantaneous, VIFRange::EnergyWh, 0, 0, 1, &key, &t->dv_entries))
{
extractDVdouble(&t->dv_entries, key, &offset, &total_heating_energy_kwh_);
t->addMoreExplanation(offset, " total heating energy consumption (%f kWh)", total_heating_energy_kwh_);
}
// Then look for mj.
if (findKeyWithNr(MeasurementType::Instantaneous, VIFRange::EnergyMJ, 0, 0, 1, &key, &t->dv_entries))
{
double mj;
extractDVdouble(&t->dv_entries, key, &offset, &mj);
total_heating_energy_kwh_ = convert(mj, Unit::MJ, Unit::KWH);
t->addMoreExplanation(offset, " total heating energy consumption (%f MJ = %f kWh)", mj, total_heating_energy_kwh_);
}
if (findKeyWithNr(MeasurementType::Instantaneous, VIFRange::Volume, 0, 0, 1, &key, &t->dv_entries))
{
extractDVdouble(&t->dv_entries, key, &offset, &total_heating_volume_m3_);
t->addMoreExplanation(offset, " total heating volume (%f m3)", total_heating_volume_m3_);
}
// Now look for cooling energy, which uses the same DIFVIF but follows the first set of data.
if (findKeyWithNr(MeasurementType::Instantaneous, VIFRange::EnergyWh, 0, 0, 2, &key, &t->dv_entries))
{
extractDVdouble(&t->dv_entries, key, &offset, &total_cooling_energy_kwh_);
t->addMoreExplanation(offset, " total cooling energy consumption (%f kWh)", total_cooling_energy_kwh_);
}
// Then look for mj.
if (findKeyWithNr(MeasurementType::Instantaneous, VIFRange::EnergyMJ, 0, 0, 2, &key, &t->dv_entries))
{
double mj;
extractDVdouble(&t->dv_entries, key, &offset, &mj);
total_cooling_energy_kwh_ = convert(mj, Unit::MJ, Unit::KWH);
t->addMoreExplanation(offset, " total cooling energy consumption (%f MJ = %f kWh)", mj, total_cooling_energy_kwh_);
}
if (findKeyWithNr(MeasurementType::Instantaneous, VIFRange::Volume, 0, 0, 2, &key, &t->dv_entries))
{
extractDVdouble(&t->dv_entries, key, &offset, &total_cooling_volume_m3_);
t->addMoreExplanation(offset, " total cooling volume (%f m3)", total_cooling_volume_m3_);
}
if (findKeyWithNr(MeasurementType::Instantaneous, VIFRange::Volume, 0, 0, 3, &key, &t->dv_entries))
{
extractDVdouble(&t->dv_entries, key, &offset, &c1_volume_m3_);
t->addMoreExplanation(offset, " volume C1 (%f m3)", c1_volume_m3_);
}
if (findKeyWithNr(MeasurementType::Instantaneous, VIFRange::Volume, 0, 0, 4, &key, &t->dv_entries))
{
extractDVdouble(&t->dv_entries, key, &offset, &c2_volume_m3_);
t->addMoreExplanation(offset, " volume C2 (%f m3)", c2_volume_m3_);
}
if(findKey(MeasurementType::Instantaneous, VIFRange::FlowTemperature, 0, 0, &key, &t->dv_entries)) {
extractDVdouble(&t->dv_entries, key, &offset, &t1_temperature_c_);
t->addMoreExplanation(offset, " supply temperature T1 (%f °C)", t1_temperature_c_);
}
if(findKey(MeasurementType::Instantaneous, VIFRange::ReturnTemperature, 0, 0, &key, &t->dv_entries)) {
extractDVdouble(&t->dv_entries, key, &offset, &t2_temperature_c_);
t->addMoreExplanation(offset, " return temperature T2 (%f °C)", t2_temperature_c_);
}
}

Wyświetl plik

@ -2429,13 +2429,19 @@ bool FieldInfo::extractNumeric(Meter *m, Telegram *t, DVEntry *dve)
{
struct tm datetime;
dve->extractDate(&datetime);
extracted_double_value = mktime(&datetime);
// TODO Figure out why I have to use a temporary time_t offset here instead
// of just subtracting datetime.tm_gmtoff, which gives the wrong result.
time_t offset = datetime.tm_gmtoff;
time_t tmp = mktime(&datetime)-offset;
extracted_double_value = tmp;
}
else if (matcher_.vif_range == VIFRange::Date)
{
struct tm date;
dve->extractDate(&date);
extracted_double_value = mktime(&date);
//time_t offset = date.tm_gmtoff;
time_t tmp = mktime(&date);
extracted_double_value = tmp;
}
else if (matcher_.vif_range != VIFRange::Any &&
matcher_.vif_range != VIFRange::AnyVolumeVIF &&
@ -2469,7 +2475,11 @@ static string add_tpl_status(string existing_status, Meter *m, Telegram *t)
if (existing_status != "OK")
{
// Join the statuses.
existing_status += " "+status;
if (existing_status != "")
{
existing_status += " ";
}
existing_status += status;
}
else
{

Wyświetl plik

@ -62,7 +62,6 @@ LIST_OF_METER_TYPES
X(auto, 0, AutoMeter, AUTO, Auto) \
X(unknown, 0, UnknownMeter, UNKNOWN, Unknown) \
X(gransystems,T1_bit, ElectricityMeter, CCx01, CCx01) \
X(hydrocalm3, T1_bit, HeatMeter, HYDROCALM3, HydrocalM3) \
X(hydrodigit, T1_bit, WaterMeter, HYDRODIGIT, Hydrodigit) \
X(multical302,C1_bit|T1_bit, HeatMeter, MULTICAL302, Multical302) \
X(multical403,C1_bit, HeatMeter, MULTICAL403, Multical403) \

Wyświetl plik

@ -1382,7 +1382,7 @@ string strdatetime(double v)
struct tm datetime;
time_t t = v;
localtime_r(&t, &datetime);
return strdate(&datetime);
return strdatetime(&datetime);
}
string strdatetimesec(struct tm *datetime)
@ -1397,7 +1397,7 @@ string strdatetimesec(double v)
struct tm datetime;
time_t t = v;
localtime_r(&t, &datetime);
return strdate(&datetime);
return strdatetimesec(&datetime);
}
bool is_leap_year(int year)

Wyświetl plik

@ -4999,13 +4999,13 @@ string decodeTPLStatusByteWithLookup(uchar sts, map<int,string> *vendor_lookup)
else
{
// We could not translate, just print the bits.
t += tostrprintf("UNKNOWN_%02X ", sts & 0xe0);
t += tostrprintf("TPL_MFCT_%02X ", sts & 0xe0);
}
while (t.size() > 0 && t.back() == ' ') t.pop_back();
}
if (t == "OK") return s;
if (s == "OK") return t;
if (t == "OK" || t == "") return s;
if (s == "OK" || s == "") return t;
return s+" "+t;
}
@ -5020,8 +5020,8 @@ string decodeTPLStatusByteNoMfct(uchar sts)
t = tostrprintf("UNKNOWN_%02X", sts & 0xe0);
}
if (t == "OK") return s;
if (s == "OK") return t;
if (t == "OK" || t == "") return s;
if (s == "OK" || s == "") return t;
return s+" "+t;
}
@ -5034,11 +5034,18 @@ string decodeTPLStatusByteWithMfct(uchar sts, Translate::Lookup &lookup)
if ((sts & 0xe0) != 0)
{
// Vendor specific bits are set, lets translate them.
t = lookup.translate(sts & 0xe0);
if (lookup.hasLookups())
{
t = lookup.translate(sts & 0xe0);
}
else
{
t = decodeTPLStatusByteWithLookup(sts & 0xe0, NULL);
}
}
if (t == "OK") return s;
if (s == "OK") return t;
if (t == "OK" || t == "") return s;
if (s == "OK" || s == "") return t;
return s+" "+t;
}