kopia lustrzana https://github.com/weetmuts/wmbusmeters
Rewrite remaining drivers using ancient code for printing values.
rodzic
d7d1cda149
commit
952489c7e0
5
CHANGES
5
CHANGES
|
@ -1,3 +1,8 @@
|
|||
|
||||
ATTENTION! The hydrus driver has been rewritten from scratch! current_date is now meter_datetime
|
||||
Several fields were printed with value 0 even if the telegram did not contain the actual zero,
|
||||
such fields are no longer printed.
|
||||
|
||||
ATTENTION! The microclima driver has been refactored. The field device_date_time has been renamed
|
||||
to meter_datetime. Also two fields related to tariffs do not occur in the actual telegrams so these
|
||||
fields have been removed. The fields output has therefore been drastically changed. Use --selectfields=
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
telegram=|6644242381818181640E7246564656A51170071F0050052F2F|15257A616F14139172137DAE3A0C000000008C2013917213000B3B0000000B26784601025AF5000266EF00046D1B08B7214C1338861200CC101300000000CC201338861200426C9F2C42EC7EBF2C|
|
||||
{"media":"water","meter":"hydrus","name":"Vatten","id":"56465646","total_m3":114.35283,"total_tariff1_m3":0,"total_tariff2_m3":137.291,"max_flow_m3h":0,"flow_temperature_c":24.5,"external_temperature_c":23.9,"current_date":"2021-01-23 08:27","total_at_date_m3":128.638,"total_tariff1_at_date_m3":0,"total_tariff2_at_date_m3":128.638,"at_date":"2020-12-31 00:00","actuality_duration_s":0,"operating_time_h":14678,"remaining_battery_life_y":0,"status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
||||
{"media":"water","meter":"hydrus","name":"Vatten","id":"56465646","operating_time_h":14678,"meter_datetime":"2021-01-23 08:27","flow_temperature_c":24.5,"external_temperature_c":23.9,"status":"OK","total_tariff2_m3":137.291,"total_tariff1_at_date_m3":0,"total_tariff2_at_date_m3":128.638,"flow_m3h":0,"total_at_date_m3":128.638,"at_date":"2020-12-31","timestamp":"1111-11-11T11:11:11Z"}
|
||||
|
|
|
@ -6,12 +6,12 @@ telegram=|1944304C72242421D401A2|013D4013DD8B46A4999C1293E582CC|
|
|||
# Test new version of IZAR
|
||||
|
||||
telegram=|2944A511780729662366A20118001378D3B3DB8CEDD77731F25832AAF3DA8CADF9774EA673172E8C61F2|
|
||||
{"media":"water","meter":"izar","name":"IzarWater2","id":"66236629","prefix":"","serial_number":"000000","total_m3":16.76,"last_month_total_m3":11.84,"last_month_measure_date":"2019-11-30","remaining_battery_life_y":12,"current_alarms":"no_alarm","previous_alarms":"no_alarm","transmit_period_s":8,"manufacture_year":"0","timestamp":"1111-11-11T11:11:11Z"}
|
||||
{"media":"water","meter":"izar","name":"IzarWater2","id":"66236629","total_m3":16.76,"last_month_total_m3":11.84,"last_month_measure_date":"2019-11-30","remaining_battery_life_y":12,"current_alarms":"no_alarm","previous_alarms":"no_alarm","transmit_period_s":8,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
|
||||
# Yet another version of IZAR
|
||||
|
||||
telegram=|1944A511780779194820A1|21170013355F8EDB2D03C6912B1E37
|
||||
{"media":"water","meter":"izar","name":"IzarWater3","id":"20481979","prefix":"","serial_number":"000000","total_m3":4.366,"last_month_total_m3":0,"last_month_measure_date":"2020-12-31","remaining_battery_life_y":11.5,"current_alarms":"no_alarm","previous_alarms":"no_alarm","transmit_period_s":8,"manufacture_year":"0","timestamp":"1111-11-11T11:11:11Z"}
|
||||
{"media":"water","meter":"izar","name":"IzarWater3","id":"20481979","total_m3":4.366,"last_month_total_m3":0,"last_month_measure_date":"2020-12-31","remaining_battery_life_y":11.5,"current_alarms":"no_alarm","previous_alarms":"no_alarm","transmit_period_s":8,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
|
||||
# And another izar, with a mfct specific tpl ci field a3.
|
||||
|
||||
|
@ -25,4 +25,4 @@ telegram=|1944304CDEFFE420CC01A2|63120013258F907B0AFF12529AC33B|
|
|||
# Yet another version of IZAR
|
||||
|
||||
telegram=19442423860775035048A251520015BEB6B2E1ED623A18FC74A5
|
||||
{"media":"water","meter":"izar","name":"IzarWater6","id":"48500375","prefix":"","serial_number":"000000","total_m3":521.602,"last_month_total_m3":519.147,"last_month_measure_date":"2021-11-15","remaining_battery_life_y":9,"current_alarms":"no_alarm","previous_alarms":"leakage","transmit_period_s":8,"manufacture_year":"0","timestamp":"1111-11-11T11:11:11Z"}
|
||||
{"media":"water","meter":"izar","name":"IzarWater6","id":"48500375","total_m3":521.602,"last_month_total_m3":519.147,"last_month_measure_date":"2021-11-15","remaining_battery_life_y":9,"current_alarms":"no_alarm","previous_alarms":"leakage","transmit_period_s":8,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
|
|
|
@ -101,11 +101,11 @@ telegram=|2e44333003020100071b7a634820252f2f0265840842658308820165950802fb1aae01
|
|||
# Test Lansen door window telegram
|
||||
|
||||
telegram=|2e44333005020100071d7ab54800002f2f02fd1b110002fd971d01000efd3a2200000000008e40fd3a000000000000|
|
||||
{"media":"reserved","meter":"lansendw","name":"Dooro","id":"00010205","status":"CLOSED","a_counter":22,"b_counter":0,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
{"media":"reserved","meter":"lansendw","name":"Dooro","id":"00010205","status":"CLOSED","a_counter":22,"b_counter":0,"error_flags": "ERROR_FLAGS_1 PERMANENT_ERROR UNKNOWN_40","timestamp":"1111-11-11T11:11:11Z"}
|
||||
|Dooro;00010205;CLOSED;1111-11-11 11:11.11
|
||||
|
||||
telegram=|2e44333005020100071d7ab66800002f2f02fd1b550002fd971d01000efd3a2300000000008e40fd3a000000000000|
|
||||
{"media":"reserved","meter":"lansendw","name":"Dooro","id":"00010205","status":"OPEN","a_counter":23,"b_counter":0,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
{"media":"reserved","meter":"lansendw","name":"Dooro","id":"00010205","status":"OPEN","a_counter":23,"b_counter":0,"error_flags": "ERROR_FLAGS_1 PERMANENT_ERROR UNKNOWN_60","timestamp":"1111-11-11T11:11:11Z"}
|
||||
|Dooro;00010205;OPEN;1111-11-11 11:11.11
|
||||
|
||||
# Test Lansen pulse counter
|
||||
|
@ -122,17 +122,17 @@ telegram=|5744b40988227711101b7ab20800000265a00842658f088201659f08226589081265a0
|
|||
|
||||
# Test Hydrus water meter telegram
|
||||
telegram=|4E44A5116464646470077AED004005_2F2F01FD08300C13741100007C1300000000FC101300000000FC201300000000726C00000B3B00000002FD748713025A6800C4016D3B177F2ACC011300020000|
|
||||
{"media":"water","meter":"hydrus","name":"HydrusWater","id":"64646464","total_m3":1.174,"total_tariff1_m3":0,"total_tariff2_m3":0,"max_flow_m3h":0,"flow_temperature_c":10.4,"external_temperature_c":0,"current_date":"","total_at_date_m3":0,"total_tariff1_at_date_m3":0,"total_tariff2_at_date_m3":0,"at_date":"2000-00-00 00:00","actuality_duration_s":0,"operating_time_h":0,"remaining_battery_life_y":13.686516,"status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
||||
|HydrusWater;64646464;1.174;0;OK;1111-11-11 11:11.11
|
||||
{"at_datetime": "2019-10-31 23:59","flow_m3h": 0,"flow_temperature_c": 10.4,"id": "64646464","media": "water","meter": "hydrus","name": "HydrusWater","remaining_battery_life_y": 13.686797,"status": "OK","timestamp": "1111-11-11T11:11:11Z","total_at_date_m3": 0.2,"total_m3": 1.174}
|
||||
|HydrusWater;64646464;1.174;0.2;OK;1111-11-11 11:11.11
|
||||
|
||||
# Test Hydrus new version water meter telegram
|
||||
telegram=|3E44A5116565656570067AFB0030052F2F_0C13503400000DFD110A383731303134423032410B3B00000002FD74DC15C4016D3B178D29CC0113313400002F2F|
|
||||
{"media":"warm water","meter":"hydrus","name":"HydrusVater","id":"65656565","total_m3":3.45,"total_tariff1_m3":0,"total_tariff2_m3":0,"max_flow_m3h":0,"flow_temperature_c":127,"external_temperature_c":0,"current_date":"","total_at_date_m3":3.431,"total_tariff1_at_date_m3":0,"total_tariff2_at_date_m3":0,"at_date":"2020-09-13 23:59","actuality_duration_s":0,"operating_time_h":0,"remaining_battery_life_y":15.321013,"status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
||||
{"at_datetime": "2020-09-13 23:59","customer": "A20B410178","flow_m3h": 0,"id": "65656565","media": "warm water","meter": "hydrus","name": "HydrusVater","remaining_battery_life_y": 15.321328,"status": "OK","timestamp": "1111-11-11T11:11:11Z","total_at_date_m3": 3.431,"total_m3": 3.45}
|
||||
|HydrusVater;65656565;3.45;3.431;OK;1111-11-11 11:11.11
|
||||
|
||||
# Test Hydrus with default AES encryption
|
||||
telegram=||6644242328001081640E7266567464A51170071F0050052C411A08674048DD6BA82A0DF79FFD401309179A893A1BE3CE8EDC50C2A45CD7AFEC3B4CE765820BE8056C124A17416C3722985FFFF7FCEB7094901AB3A16294B511B9A740C9F9911352B42A72FB3B0C|
|
||||
{"media":"water","meter":"hydrus","name":"HydrusAES","id":"64745666","total_m3":137.291,"total_tariff1_m3":0,"total_tariff2_m3":137.291,"max_flow_m3h":0,"flow_temperature_c":24.5,"external_temperature_c":23.9,"current_date":"2021-01-23 08:27","total_at_date_m3":128.638,"total_tariff1_at_date_m3":0,"total_tariff2_at_date_m3":128.638,"at_date":"2020-12-31 00:00","actuality_duration_s":6673,"operating_time_h":14678,"remaining_battery_life_y":0,"status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
||||
{"actuality_duration_s": 6673,"at_date": "2020-12-31","external_temperature_c": 23.9,"flow_m3h": 0,"flow_temperature_c": 24.5,"id": "64745666","media": "water","meter": "hydrus","meter_datetime": "2021-01-23 08:27","name": "HydrusAES","operating_time_h": 14678,"status": "OK","timestamp": "1111-11-11T11:11:11Z","total_at_date_m3": 128.638,"total_m3": 137.291,"total_tariff1_at_date_m3": 0,"total_tariff1_m3": 0,"total_tariff2_at_date_m3": 128.638,"total_tariff2_m3": 137.291}
|
||||
|HydrusAES;64745666;137.291;128.638;OK;1111-11-11 11:11.11
|
||||
|
||||
# Test BMeters HydroDigit water telegram
|
||||
|
@ -270,18 +270,18 @@ telegram=|1E44A511909192937B077A9F0010052F2F_04130347030002FD1700002F2F2F|
|
|||
|
||||
# Test IZAR RS 868 water meter
|
||||
telegram=|1E4424238B07797389607A8F00107D_041312170100426CBF23441344100100|
|
||||
{"media":"water","meter":"hydrus","name":"HydrusIzarRS","id":"60897379","total_m3":71.442,"total_tariff1_m3":0,"total_tariff2_m3":0,"max_flow_m3h":0,"flow_temperature_c":127,"external_temperature_c":0,"current_date":"","total_at_date_m3":69.7,"total_tariff1_at_date_m3":0,"total_tariff2_at_date_m3":0,"at_date":"2021-03-31 00:00","actuality_duration_s":0,"operating_time_h":0,"remaining_battery_life_y":0,"status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
||||
{"at_date": "2021-03-31","id": "60897379","media": "water","meter": "hydrus","name": "HydrusIzarRS","status": "OK","timestamp": "1111-11-11T11:11:11Z","total_at_date_m3": 69.7,"total_m3": 71.442}
|
||||
|HydrusIzarRS;60897379;71.442;69.7;OK;1111-11-11 11:11.11
|
||||
|
||||
# Test IZAR RS 868 water meter warm
|
||||
telegram=|1E4424238B06204790607A2A0010D8_0413DDC00000426CBF23441382BB0000|
|
||||
{"media":"warm water","meter":"hydrus","name":"HydrusIzarRSWarm","id":"60904720","total_m3":49.373,"total_tariff1_m3":0,"total_tariff2_m3":0,"max_flow_m3h":0,"flow_temperature_c":127,"external_temperature_c":0,"current_date":"","total_at_date_m3":48.002,"total_tariff1_at_date_m3":0,"total_tariff2_at_date_m3":0,"at_date":"2021-03-31 00:00","actuality_duration_s":0,"operating_time_h":0,"remaining_battery_life_y":0,"status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
||||
{"at_date": "2021-03-31","id": "60904720","media": "warm water","meter": "hydrus","name": "HydrusIzarRSWarm","status": "OK","timestamp": "1111-11-11T11:11:11Z","total_at_date_m3": 48.002,"total_m3": 49.373}
|
||||
|HydrusIzarRSWarm;60904720;49.373;48.002;OK;1111-11-11 11:11.11
|
||||
|
||||
# Test another Diehl branded meter with the izar protocol
|
||||
telegram=|19442423850798160018A2410100133EBBD44081053F243A82A3|
|
||||
{"media":"water","meter":"izar","name":"IzarWater4","id":"18001698","prefix":"","serial_number":"000000","total_m3":835.689,"last_month_total_m3":820.329,"last_month_measure_date":"2021-09-01","remaining_battery_life_y":0.5,"current_alarms":"no_alarm","previous_alarms":"no_alarm","transmit_period_s":8,"manufacture_year":"0","timestamp":"1111-11-11T11:11:11Z"}
|
||||
|IzarWater4;18001698;;000000;835.689;820.329;2021-09-01;0.5;no_alarm;no_alarm;8;0;1111-11-11 11:11.11
|
||||
{"media":"water","meter":"izar","name":"IzarWater4","id":"18001698","total_m3":835.689,"last_month_total_m3":820.329,"last_month_measure_date":"2021-09-01","remaining_battery_life_y":0.5,"current_alarms":"no_alarm","previous_alarms":"no_alarm","transmit_period_s":8,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
|IzarWater4;18001698;null;null;835.689;820.329;2021-09-01;0.5;no_alarm;no_alarm;8;null;1111-11-11 11:11.11
|
||||
|
||||
# Test Aventies Water Meter
|
||||
telegram=76442104710007612507727100076121042507B5006005E2E95A3C2A1279A5415E6732679B43369FD5FDDDD783EEEBB48236D34E7C94AF0A18A5FDA5F7D64111EB42D4D891622139F2952F9D12A20088DFA4CF8123871123EE1F6C1DCEA414879DDB4E05E508F1826D7EFBA6964DF804C9261EA23BBF03
|
||||
|
|
|
@ -17,17 +17,15 @@
|
|||
|
||||
#include"meters_common_implementation.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
struct MeterApator08 : public virtual MeterCommonImplementation
|
||||
namespace
|
||||
{
|
||||
MeterApator08(MeterInfo &mi, DriverInfo &di);
|
||||
struct Driver : public virtual MeterCommonImplementation
|
||||
{
|
||||
Driver(MeterInfo &mi, DriverInfo &di);
|
||||
|
||||
private:
|
||||
|
||||
void processContent(Telegram *t);
|
||||
|
||||
double total_water_consumption_m3_ {};
|
||||
};
|
||||
|
||||
static bool ok = registerDriver([](DriverInfo&di)
|
||||
|
@ -37,25 +35,20 @@ static bool ok = registerDriver([](DriverInfo&di)
|
|||
di.setMeterType(MeterType::WaterMeter);
|
||||
di.addLinkMode(LinkMode::T1);
|
||||
di.addDetection(0x8614/*APT?*/, 0x03, 0x03);
|
||||
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new MeterApator08(mi, di)); });
|
||||
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
|
||||
});
|
||||
|
||||
MeterApator08::MeterApator08(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addNumericField(
|
||||
"total",
|
||||
Quantity::Volume,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"The total water consumption recorded by this meter.",
|
||||
SET_FUNC(total_water_consumption_m3_, Unit::M3),
|
||||
GET_FUNC(total_water_consumption_m3_, Unit::M3));
|
||||
"The total water consumption recorded by this meter.");
|
||||
}
|
||||
|
||||
void MeterApator08::processContent(Telegram *t)
|
||||
void Driver::processContent(Telegram *t)
|
||||
{
|
||||
// Unfortunately, the at-wmbus-08 is mostly a proprietary protocol
|
||||
// simple wrapped inside a wmbus telegram. Naughty!
|
||||
|
||||
// The telegram says gas (0x03) but it is a water meter.... so fix this.
|
||||
t->dll_type = 0x07;
|
||||
|
||||
|
@ -72,12 +65,16 @@ void MeterApator08::processContent(Telegram *t)
|
|||
string key;
|
||||
if(findKey(MeasurementType::Instantaneous, VIFRange::Volume, 0, 0, &key, &vendor_values))
|
||||
{
|
||||
extractDVdouble(&vendor_values, key, &offset, &total_water_consumption_m3_);
|
||||
double total_water_consumption_m3 {};
|
||||
extractDVdouble(&vendor_values, key, &offset, &total_water_consumption_m3);
|
||||
// Now divide with 3! Is this the same for all apator08 meters? Time will tell.
|
||||
total_water_consumption_m3_ /= 3.0;
|
||||
total_water_consumption_m3 /= 3.0;
|
||||
|
||||
total = "*** 10|"+total+" total consumption (%f m3)";
|
||||
t->addSpecialExplanation(offset, 4, KindOfData::CONTENT, Understanding::FULL, total.c_str(), total_water_consumption_m3_);
|
||||
t->addSpecialExplanation(offset, 4, KindOfData::CONTENT, Understanding::FULL, total.c_str(), total_water_consumption_m3);
|
||||
|
||||
setNumericValue("total", Unit::M3, total_water_consumption_m3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,8 +26,6 @@ namespace
|
|||
void processContent(Telegram *t);
|
||||
void processExtras(string miExtras);
|
||||
int registerSize(int c);
|
||||
|
||||
double total_water_consumption_m3_ {};
|
||||
};
|
||||
|
||||
static bool ok = registerDriver([](DriverInfo&di)
|
||||
|
@ -47,10 +45,11 @@ namespace
|
|||
{
|
||||
processExtras(mi.extras);
|
||||
|
||||
addPrint("total", Quantity::Volume,
|
||||
[&](Unit u){ return convert(total_water_consumption_m3_, Unit::M3, u); },
|
||||
"The total water consumption recorded by this meter.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericField(
|
||||
"total",
|
||||
Quantity::Volume,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"The total water consumption recorded by this meter.");
|
||||
}
|
||||
|
||||
void Driver::processContent(Telegram *t)
|
||||
|
@ -105,9 +104,12 @@ namespace
|
|||
strprintf(&total, "%02x%02x%02x%02x", content[i+0], content[i+1], content[i+2], content[i+3]);
|
||||
int offset = i-1+t->header_size;
|
||||
vendor_values["0413"] = {offset, DVEntry(offset, DifVifKey("0413"), MeasurementType::Instantaneous, 0x13, {}, 0, 0, 0, total) };
|
||||
extractDVdouble(&vendor_values, "0413", &offset, &total_water_consumption_m3_);
|
||||
double total_water_consumption_m3 {};
|
||||
extractDVdouble(&vendor_values, "0413", &offset, &total_water_consumption_m3);
|
||||
total = "*** 10-"+total+" total consumption (%f m3)";
|
||||
t->addSpecialExplanation(offset, 4, KindOfData::CONTENT, Understanding::FULL, total.c_str(), total_water_consumption_m3_);
|
||||
t->addSpecialExplanation(offset, 4, KindOfData::CONTENT, Understanding::FULL, total.c_str(), total_water_consumption_m3);
|
||||
|
||||
setNumericValue("total", Unit::M3, total_water_consumption_m3);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -26,8 +26,6 @@ namespace
|
|||
Driver(MeterInfo &mi, DriverInfo &di);
|
||||
|
||||
void processContent(Telegram *t);
|
||||
|
||||
double total_water_consumption_m3_ {};
|
||||
};
|
||||
|
||||
static bool ok = registerDriver([](DriverInfo&di)
|
||||
|
@ -41,10 +39,11 @@ namespace
|
|||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addPrint("total", Quantity::Volume,
|
||||
[&](Unit u){ return convert(total_water_consumption_m3_, Unit::M3, u); },
|
||||
"The total water consumption recorded by this meter.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericField(
|
||||
"total",
|
||||
Quantity::Volume,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"The total water consumption recorded by this meter.");
|
||||
}
|
||||
|
||||
void Driver::processContent(Telegram *t)
|
||||
|
@ -69,9 +68,11 @@ namespace
|
|||
extractDVdouble(&vendor_values, "0413", &offset, &tmp);
|
||||
// Single tick seems to be 1/3 of a m3. Divide by 3 and keep a single decimal.
|
||||
// This will report consumption as 100.0 100.3 100.7 101.0 etc.
|
||||
total_water_consumption_m3_ = 0.1*std::round(10000.0*tmp/3.0);
|
||||
double total_water_consumption_m3 = 0.1*std::round(10000.0*tmp/3.0);
|
||||
total = "*** "+total+" total consumption (%f m3)";
|
||||
t->addSpecialExplanation(offset, 4, KindOfData::CONTENT, Understanding::FULL, total.c_str(), total_water_consumption_m3_);
|
||||
t->addSpecialExplanation(offset, 4, KindOfData::CONTENT, Understanding::FULL, total.c_str(), total_water_consumption_m3);
|
||||
|
||||
setNumericValue("total", Unit::M3, total_water_consumption_m3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2022 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
Copyright (C) 2022-2023 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
Copyright (C) 2022 Kajetan Krykwiński (gpl-3.0-or-later)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
@ -26,14 +26,6 @@ namespace
|
|||
|
||||
void processContent(Telegram *t);
|
||||
string dateToString(uchar date_lo, uchar date_hi);
|
||||
|
||||
double prev_energy_hca_ {};
|
||||
double curr_energy_hca_ {};
|
||||
string curr_energy_hca_date_ {};
|
||||
string season_start_date_ {};
|
||||
string esb_date_ {};
|
||||
double temp_room_avg_ {};
|
||||
double temp_room_prev_avg_ {};
|
||||
};
|
||||
|
||||
static bool ok = registerDriver([](DriverInfo&di)
|
||||
|
@ -51,40 +43,37 @@ namespace
|
|||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addPrint("current", Quantity::HCA,
|
||||
[&](Unit u){ return convert(curr_energy_hca_, Unit::HCA, u);},
|
||||
"Energy consumption so far in this billing period.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericField("current",
|
||||
Quantity::HCA,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"Energy consumption so far in this billing period.");
|
||||
|
||||
addPrint("previous", Quantity::HCA,
|
||||
[&](Unit u){ return convert(prev_energy_hca_, Unit::HCA, u); },
|
||||
"Energy consumption in previous billing period.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericField("previous",
|
||||
Quantity::HCA,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"Energy consumption in previous billing period.");
|
||||
|
||||
addPrint("current_date", Quantity::Text,
|
||||
[&](){ return curr_energy_hca_date_; },
|
||||
addStringField("current_date",
|
||||
"Current date, as reported by meter.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
|
||||
addPrint("season_start_date", Quantity::Text,
|
||||
[&](){ return season_start_date_; },
|
||||
addStringField("season_start_date",
|
||||
"Season start date.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
|
||||
addPrint("esb_date", Quantity::Text,
|
||||
[&](){ return esb_date_; },
|
||||
addStringField("esb_date",
|
||||
"Electronic seal protection break date.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
|
||||
addPrint("temp_room_avg", Quantity::Temperature,
|
||||
[&](Unit u){ return convert(temp_room_avg_, Unit::C, u); },
|
||||
"Average room temperature in current season.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericField("temp_room_avg",
|
||||
Quantity::Temperature,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"Average room temperature in current season.");
|
||||
|
||||
addPrint("temp_room_prev_avg", Quantity::Temperature,
|
||||
[&](Unit u){ return convert(temp_room_prev_avg_, Unit::C, u); },
|
||||
"Average room temperature in previous season.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericField("temp_room_prev_avg",
|
||||
Quantity::Temperature,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"Average room temperature in previous season.");
|
||||
}
|
||||
|
||||
void Driver::processContent(Telegram *t)
|
||||
|
@ -123,38 +112,44 @@ namespace
|
|||
// Note: NOT byte swapped. Accidentally? works via dateToString conversion.
|
||||
uchar season_start_date_lo = content[1];
|
||||
uchar season_start_date_hi = content[0];
|
||||
season_start_date_ = dateToString(season_start_date_lo, season_start_date_hi);
|
||||
string season_start_date = dateToString(season_start_date_lo, season_start_date_hi);
|
||||
setStringValue("season_start_date", season_start_date);
|
||||
|
||||
// Previous season total allocation
|
||||
uchar prev_lo = content[4];
|
||||
uchar prev_hi = content[5];
|
||||
prev_energy_hca_ = (256.0*prev_hi+prev_lo);
|
||||
double previous_hca = (256.0*prev_hi+prev_lo);
|
||||
setNumericValue("previous", Unit::HCA, previous_hca);
|
||||
|
||||
// Electronic seal break date
|
||||
uchar esb_date_lo = content[6];
|
||||
uchar esb_date_hi = content[7];
|
||||
esb_date_ = dateToString(esb_date_lo, esb_date_hi);
|
||||
string esb_date = dateToString(esb_date_lo, esb_date_hi);
|
||||
setStringValue("esb_date", esb_date);
|
||||
|
||||
// Current season allocation
|
||||
uchar curr_lo = content[8];
|
||||
uchar curr_hi = content[9];
|
||||
curr_energy_hca_ = (256.0*curr_hi+curr_lo);
|
||||
double current_hca = (256.0*curr_hi+curr_lo);
|
||||
setNumericValue("current", Unit::HCA, current_hca);
|
||||
|
||||
// Current date reported by meter
|
||||
uchar date_curr_lo = content[10];
|
||||
uchar date_curr_hi = content[11];
|
||||
curr_energy_hca_date_ = dateToString(date_curr_lo, date_curr_hi);
|
||||
string current_date = dateToString(date_curr_lo, date_curr_hi);
|
||||
setStringValue("current_date", current_date);
|
||||
|
||||
// Previous season average temperature
|
||||
double temp_room_prev_avg_frac = content[12];
|
||||
double temp_room_prev_avg_deg = content[13];
|
||||
temp_room_prev_avg_ = temp_room_prev_avg_deg + temp_room_prev_avg_frac/256.0;
|
||||
double temp_room_prev_avg = temp_room_prev_avg_deg + temp_room_prev_avg_frac/256.0;
|
||||
setNumericValue("temp_room_prev_avg", Unit::C, temp_room_prev_avg);
|
||||
|
||||
// Current season average temperature
|
||||
double temp_room_avg_frac = content[14];
|
||||
double temp_room_avg_deg = content[15];
|
||||
temp_room_avg_ = temp_room_avg_deg + temp_room_avg_frac/256.0;
|
||||
|
||||
double temp_room_avg = temp_room_avg_deg + temp_room_avg_frac/256.0;
|
||||
setNumericValue("temp_room_avg", Unit::C, temp_room_avg);
|
||||
}
|
||||
|
||||
string Driver::dateToString(uchar date_lo, uchar date_hi) {
|
||||
|
|
|
@ -24,11 +24,6 @@ namespace
|
|||
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)
|
||||
|
@ -44,28 +39,28 @@ namespace
|
|||
|
||||
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.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericField("current",
|
||||
Quantity::HCA,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"Energy consumption so far in this billing period.");
|
||||
|
||||
addPrint("prev", Quantity::HCA,
|
||||
[&](Unit u){ return convert(prev_hca_, Unit::HCA, u); },
|
||||
"Energy consumption at end of previous billing period.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericField("prev",
|
||||
Quantity::HCA,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"Energy consumption at end of previous billing period.");
|
||||
|
||||
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, DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericField(info,
|
||||
Quantity::HCA,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
about);
|
||||
}
|
||||
|
||||
addPrint("device_date", Quantity::Text,
|
||||
[&](){ return device_date_; },
|
||||
addStringField("device_date",
|
||||
"Device date when telegram was sent.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
}
|
||||
|
@ -116,28 +111,31 @@ namespace
|
|||
|
||||
if (content.size() < 40) return;
|
||||
|
||||
current_hca_ = content[6]*256 + content[7];
|
||||
double current_hca = content[6]*256 + content[7];
|
||||
setNumericValue("current", Unit::HCA, current_hca);
|
||||
|
||||
string msg = tostrprintf("*** %02X%02X \"current_hca\":%g", content[6], content[7], current_hca_);
|
||||
string msg = tostrprintf("*** %02X%02X \"current_hca\":%g", content[6], content[7], current_hca);
|
||||
t->addSpecialExplanation(6+t->header_size, 2, KindOfData::CONTENT, Understanding::FULL, msg.c_str());
|
||||
|
||||
prev_hca_ = content[4]*256 + content[5];
|
||||
double prev_hca = content[4]*256 + content[5];
|
||||
setNumericValue("prev", Unit::HCA, prev_hca);
|
||||
|
||||
msg = tostrprintf("*** %02X%02X \"prev_hca\":%g", content[4], content[5], prev_hca_);
|
||||
msg = tostrprintf("*** %02X%02X \"prev_hca\":%g", content[4], content[5], prev_hca);
|
||||
t->addSpecialExplanation(4+t->header_size, 2, KindOfData::CONTENT, Understanding::FULL, msg.c_str());
|
||||
|
||||
device_date_ = tostrprintf("20%02x-%02x-%02x", content[39], content[39-1], content[39-2]);
|
||||
string device_date = tostrprintf("20%02x-%02x-%02x", content[39], content[39-1], content[39-2]);
|
||||
setStringValue("device_date", device_date);
|
||||
|
||||
msg = tostrprintf("*** %02X%02X%02X \"device_date\":\"%s\"", content[39-2], content[39-1], content[39],
|
||||
device_date_.c_str());
|
||||
device_date.c_str());
|
||||
t->addSpecialExplanation(39-2+t->header_size, 3, KindOfData::CONTENT, Understanding::FULL, msg.c_str());
|
||||
|
||||
|
||||
|
||||
|
||||
for (int i=0; i<18; ++i)
|
||||
{
|
||||
historic_hca_[i] = getHistoric(i, content);
|
||||
string info = tostrprintf("prev_%02d", i+1);
|
||||
double historic_hca = getHistoric(i, content);
|
||||
setNumericValue(info, Unit::HCA, historic_hca);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,10 +24,6 @@ namespace
|
|||
Driver(MeterInfo &mi, DriverInfo &di);
|
||||
|
||||
void processContent(Telegram *t);
|
||||
|
||||
double total_energy_kwh_ {};
|
||||
double curr_energy_kwh_ {};
|
||||
double prev_energy_kwh_ {};
|
||||
};
|
||||
|
||||
static bool ok = registerDriver([](DriverInfo&di)
|
||||
|
@ -48,20 +44,20 @@ namespace
|
|||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addPrint("total", Quantity::Energy,
|
||||
[&](Unit u){ return convert(total_energy_kwh_, Unit::KWH, u); },
|
||||
"The total energy consumption recorded by this meter.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericField("total",
|
||||
Quantity::Energy,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"The total energy consumption recorded by this meter.");
|
||||
|
||||
addPrint("current", Quantity::Energy,
|
||||
[&](Unit u){ return convert(curr_energy_kwh_, Unit::KWH, u); },
|
||||
"Energy consumption so far in this billing period.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericField("current",
|
||||
Quantity::Energy,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"Energy consumption so far in this billing period.");
|
||||
|
||||
addPrint("previous", Quantity::Energy,
|
||||
[&](Unit u){ return convert(prev_energy_kwh_, Unit::KWH, u); },
|
||||
"Energy consumption in previous billing period.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericField("previous",
|
||||
Quantity::Energy,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"Energy consumption in previous billing period.");
|
||||
}
|
||||
|
||||
void Driver::processContent(Telegram *t)
|
||||
|
@ -101,9 +97,14 @@ namespace
|
|||
t->explanations.push_back(ce);
|
||||
t->addMoreExplanation(offset, " energy used in current billing period (%f KWH)", curr);
|
||||
|
||||
total_energy_kwh_ = prev+curr;
|
||||
curr_energy_kwh_ = curr;
|
||||
prev_energy_kwh_ = prev;
|
||||
double total_energy_kwh = prev+curr;
|
||||
setNumericValue("total", Unit::KWH, total_energy_kwh);
|
||||
|
||||
double curr_energy_kwh = curr;
|
||||
setNumericValue("current", Unit::KWH, curr_energy_kwh);
|
||||
|
||||
double prev_energy_kwh = prev;
|
||||
setNumericValue("previous", Unit::KWH, prev_energy_kwh);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,23 +23,10 @@ namespace
|
|||
{
|
||||
Driver(MeterInfo &mi, DriverInfo &di);
|
||||
|
||||
private:
|
||||
|
||||
void processContent(Telegram *t);
|
||||
|
||||
double currentPeriodEnergyConsumption(Unit u);
|
||||
string currentPeriodDate();
|
||||
double previousPeriodEnergyConsumption(Unit u);
|
||||
string previousPeriodDate();
|
||||
double currentRoomTemperature(Unit u);
|
||||
double currentRadiatorTemperature(Unit u);
|
||||
|
||||
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_ {};
|
||||
};
|
||||
|
||||
static bool ok = registerDriver([](DriverInfo&di)
|
||||
|
@ -56,72 +43,38 @@ namespace
|
|||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addPrint("current", Quantity::HCA,
|
||||
[&](Unit u){ return currentPeriodEnergyConsumption(u); },
|
||||
"Energy consumption so far in this billing period.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericField("current",
|
||||
Quantity::HCA,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"Energy consumption so far in this billing period.");
|
||||
|
||||
addPrint("current_date", Quantity::Text,
|
||||
[&](){ return currentPeriodDate(); },
|
||||
addStringField("current_date",
|
||||
"Date of current billing period.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
|
||||
addPrint("previous", Quantity::HCA,
|
||||
[&](Unit u){ return previousPeriodEnergyConsumption(u); },
|
||||
"Energy consumption in previous billing period.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericField("previous",
|
||||
Quantity::HCA,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"Energy consumption in previous billing period.");
|
||||
|
||||
addPrint("previous_date", Quantity::Text,
|
||||
[&](){ return previousPeriodDate(); },
|
||||
addStringField("previous_date",
|
||||
"Date of last billing period.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
|
||||
addPrint("temp_room", Quantity::Temperature,
|
||||
[&](Unit u){ return currentRoomTemperature(u); },
|
||||
"Current room temperature.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericField("temp_room",
|
||||
Quantity::Temperature,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"Current room temperature.");
|
||||
|
||||
addPrint("temp_radiator", Quantity::Temperature,
|
||||
[&](Unit u){ return currentRadiatorTemperature(u); },
|
||||
"Current radiator temperature.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
}
|
||||
|
||||
double Driver::currentPeriodEnergyConsumption(Unit u)
|
||||
{
|
||||
return curr_energy_hca_;
|
||||
}
|
||||
|
||||
string Driver::currentPeriodDate()
|
||||
{
|
||||
return curr_energy_hca_date;
|
||||
}
|
||||
|
||||
double Driver::previousPeriodEnergyConsumption(Unit u)
|
||||
{
|
||||
return prev_energy_hca_;
|
||||
}
|
||||
|
||||
string Driver::previousPeriodDate()
|
||||
{
|
||||
return prev_energy_hca_date;
|
||||
}
|
||||
|
||||
double Driver::currentRoomTemperature(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Temperature);
|
||||
return convert(temp_room_, Unit::C, u);
|
||||
}
|
||||
|
||||
double Driver::currentRadiatorTemperature(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Temperature);
|
||||
return convert(temp_radiator_, Unit::C, u);
|
||||
addNumericField("temp_radiator",
|
||||
Quantity::Temperature,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"Current radiator temperature.");
|
||||
}
|
||||
|
||||
void Driver::processContent(Telegram *t)
|
||||
{
|
||||
// Unfortunately, the Techem FHKV data ii/iii is mostly a proprieatary protocol
|
||||
// The Techem FHKV data ii/iii is mostly a proprietary protocol
|
||||
// simple wrapped inside a wmbus telegram since the ci-field is 0xa0.
|
||||
// Which means that the entire payload is manufacturer specific.
|
||||
|
||||
|
@ -135,18 +88,10 @@ namespace
|
|||
debugPayload("(fhkvdataiii) not enough data", content);
|
||||
return;
|
||||
}
|
||||
// Consumption
|
||||
// Previous Consumption
|
||||
uchar prev_lo = content[3];
|
||||
uchar prev_hi = content[4];
|
||||
double prev = (256.0*prev_hi+prev_lo);
|
||||
prev_energy_hca_ = prev;
|
||||
|
||||
string prevs;
|
||||
strprintf(&prevs, "%02x%02x", prev_lo, prev_hi);
|
||||
//t->addMoreExplanation(14, " energy used in previous billing period (%f HCA)", prevs);
|
||||
|
||||
// Previous Date
|
||||
// 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);
|
||||
|
@ -154,21 +99,36 @@ namespace
|
|||
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);
|
||||
string previous_date =
|
||||
std::to_string((year_prev + 2000)) + "-" +
|
||||
leadingZeroString(month_prev) + "-" +
|
||||
leadingZeroString(day_prev) + "T02:00:00Z";
|
||||
|
||||
// Current Consumption
|
||||
uchar curr_lo = content[7];
|
||||
uchar curr_hi = content[8];
|
||||
double curr = (256.0*curr_hi+curr_lo);
|
||||
curr_energy_hca_ = curr;
|
||||
setStringValue("previous_date", previous_date);
|
||||
|
||||
string currs;
|
||||
strprintf(&currs, "%02x%02x", curr_lo, curr_hi);
|
||||
//t->addMoreExplanation(offset, " energy used in current billing period (%f HCA)", currs);
|
||||
string bytes = tostrprintf("%02x%02x", content[1], content[2]);
|
||||
string info = "*** "+bytes+" previous_date = %s";
|
||||
|
||||
t->addSpecialExplanation(t->header_size+1, 2, KindOfData::CONTENT, Understanding::FULL,
|
||||
info.c_str(), previous_date.c_str());
|
||||
|
||||
// Previous Consumption //////////////////////////////////////////////////////////
|
||||
|
||||
uchar prev_lo = content[3];
|
||||
uchar prev_hi = content[4];
|
||||
double prev = (256.0*prev_hi+prev_lo);
|
||||
double previous_hca = prev;
|
||||
setNumericValue("previous", Unit::HCA, previous_hca);
|
||||
|
||||
bytes = tostrprintf("%02x%02x", content[3], content[4]);
|
||||
info = "*** "+bytes+" previous_hca = %.17g";
|
||||
|
||||
t->addSpecialExplanation(t->header_size+3, 2, KindOfData::CONTENT, Understanding::FULL,
|
||||
info.c_str(), previous_hca);
|
||||
|
||||
// Current Date //////////////////////////////////////////////////////////////////
|
||||
|
||||
// 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);
|
||||
|
@ -181,44 +141,73 @@ namespace
|
|||
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);
|
||||
string current_date =
|
||||
to_string(year_curr) + "-" +
|
||||
leadingZeroString(month_curr) + "-" +
|
||||
leadingZeroString(day_curr) + "T02:00:00Z";
|
||||
|
||||
setStringValue("current_date", current_date);
|
||||
|
||||
bytes = tostrprintf("%02x%02x", content[5], content[6]);
|
||||
info = "*** "+bytes+" current_date = %s";
|
||||
|
||||
t->addSpecialExplanation(t->header_size+5, 2, KindOfData::CONTENT, Understanding::FULL,
|
||||
info.c_str(), current_date.c_str());
|
||||
|
||||
// Current Consumption ///////////////////////////////////////////////////////////
|
||||
|
||||
uchar curr_lo = content[7];
|
||||
uchar curr_hi = content[8];
|
||||
double current_hca = (256.0*curr_hi+curr_lo);
|
||||
|
||||
setNumericValue("current", Unit::HCA, current_hca);
|
||||
|
||||
bytes = tostrprintf("%02x%02x", content[7], content[8]);
|
||||
info = "*** "+bytes+" current_hca = %.17g";
|
||||
|
||||
t->addSpecialExplanation(t->header_size+7, 2, KindOfData::CONTENT, Understanding::FULL,
|
||||
info.c_str(), current_hca);
|
||||
|
||||
// Temperatures //////////////////////////////////////////////
|
||||
|
||||
// Temperature
|
||||
uchar room_tlo;
|
||||
uchar room_thi;
|
||||
uchar radiator_tlo;
|
||||
uchar radiator_thi;
|
||||
int offset = 9;
|
||||
|
||||
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];
|
||||
offset= 10;
|
||||
}
|
||||
|
||||
// Room Temperature
|
||||
double room_t = (256.0*room_thi+room_tlo)/100;
|
||||
temp_room_ = room_t;
|
||||
room_tlo = content[offset];
|
||||
room_thi = content[offset+1];
|
||||
radiator_tlo = content[offset+2];
|
||||
radiator_thi = content[offset+3];
|
||||
|
||||
string room_ts;
|
||||
strprintf(&room_ts, "%02x%02x", room_tlo, room_thi);
|
||||
// t->addMoreExplanation(offset, " current room temparature (%f °C)", room_ts);
|
||||
// Room Temperature ////////////////////////////////////////////////////////////////
|
||||
|
||||
// Radiator Temperature
|
||||
double radiator_t = (256.0*radiator_thi+radiator_tlo)/100;
|
||||
temp_radiator_ = radiator_t;
|
||||
double temp_room_c = (256.0*room_thi+room_tlo)/100;
|
||||
setNumericValue("temp_room", Unit::C, temp_room_c);
|
||||
|
||||
string radiator_ts;
|
||||
strprintf(&radiator_ts, "%02x%02x", radiator_tlo, radiator_thi);
|
||||
// t->addMoreExplanation(offset, " current radiator temparature (%f °C)", radiator_ts);
|
||||
bytes = tostrprintf("%02x%02x", content[offset], content[offset+1]);
|
||||
info = "*** "+bytes+" temp_room_c = %.17g";
|
||||
|
||||
t->addSpecialExplanation(t->header_size+offset, 2, KindOfData::CONTENT, Understanding::FULL,
|
||||
info.c_str(), temp_room_c);
|
||||
|
||||
// Radiator Temperature ////////////////////////////////////////////////////////////
|
||||
|
||||
double temp_radiator_c = (256.0*radiator_thi+radiator_tlo)/100;
|
||||
setNumericValue("temp_radiator", Unit::C, temp_radiator_c);
|
||||
|
||||
bytes = tostrprintf("%02x%02x", content[offset+2], content[offset+3]);
|
||||
info = "*** "+bytes+" temp_radiator_c = %.17g";
|
||||
|
||||
t->addSpecialExplanation(t->header_size+offset+2, 2, KindOfData::CONTENT, Understanding::FULL,
|
||||
info.c_str(), temp_radiator_c);
|
||||
}
|
||||
|
||||
string Driver::leadingZeroString(int num) {
|
||||
|
|
|
@ -25,11 +25,6 @@ namespace
|
|||
void processContent(Telegram *t);
|
||||
void decodeRF_RKN0(Telegram *t);
|
||||
void decodeRF_RKN9(Telegram *t);
|
||||
|
||||
double average_ambient_temperature_ {};
|
||||
double max_ambient_temperature_ {};
|
||||
double average_ambient_temperature_last_month_ {};
|
||||
double average_heater_temperature_last_month_ {};
|
||||
};
|
||||
|
||||
static bool ok = registerDriver([](DriverInfo&di)
|
||||
|
@ -39,6 +34,7 @@ namespace
|
|||
di.setMeterType(MeterType::HeatCostAllocationMeter);
|
||||
di.addLinkMode(LinkMode::T1);
|
||||
di.addDetection(MANUFACTURER_BMP, 0x08, 0x53);
|
||||
di.usesProcessContent();
|
||||
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
|
||||
});
|
||||
|
||||
|
@ -55,29 +51,25 @@ namespace
|
|||
.set(VIFRange::HeatCostAllocation)
|
||||
);
|
||||
|
||||
addPrint("average_ambient_temperature",
|
||||
addNumericField("average_ambient_temperature",
|
||||
Quantity::Temperature,
|
||||
GET_FUNC(average_ambient_temperature_, Unit::C),
|
||||
"Average ambient temperature since this beginning of this month.",
|
||||
DEFAULT_PRINT_PROPERTIES );
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"Average ambient temperature since this beginning of this month.");
|
||||
|
||||
addPrint("max_ambient_temperature",
|
||||
addNumericField("max_ambient_temperature",
|
||||
Quantity::Temperature,
|
||||
GET_FUNC(max_ambient_temperature_, Unit::C),
|
||||
"Max ambient temperature since the beginning of this month.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"Max ambient temperature since the beginning of this month.");
|
||||
|
||||
addPrint("average_ambient_temperature_last_month",
|
||||
addNumericField("average_ambient_temperature_last_month",
|
||||
Quantity::Temperature,
|
||||
GET_FUNC(average_ambient_temperature_last_month_, Unit::C),
|
||||
"Average ambient temperature last month.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"Average ambient temperature last month.");
|
||||
|
||||
addPrint("average_heater_temperature_last_month",
|
||||
addNumericField("average_heater_temperature_last_month",
|
||||
Quantity::Temperature,
|
||||
GET_FUNC(average_heater_temperature_last_month_, Unit::C),
|
||||
"Average heater temperature last month.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"Average heater temperature last month.");
|
||||
}
|
||||
|
||||
double toTemperature(uchar hi, uchar lo)
|
||||
|
@ -149,14 +141,16 @@ namespace
|
|||
i+=2;
|
||||
|
||||
if (i+1 >= len) return;
|
||||
average_ambient_temperature_ = toTemperature(bytes[i+1], bytes[i]);
|
||||
double average_ambient_temperature_c = toTemperature(bytes[i+1], bytes[i]);
|
||||
setNumericValue("average_ambient_temperature", Unit::C, average_ambient_temperature_c);
|
||||
info = renderJsonOnlyDefaultUnit("average_ambient_temperature", Quantity::Temperature);
|
||||
t->addSpecialExplanation(i+offset, 2, KindOfData::CONTENT, Understanding::FULL,
|
||||
"*** %02X%02X (%s)", bytes[i], bytes[i+1], info.c_str());
|
||||
i+=2;
|
||||
|
||||
if (i+1 >= len) return;
|
||||
max_ambient_temperature_ = toTemperature(bytes[i+1], bytes[i]);
|
||||
double max_ambient_temperature_c = toTemperature(bytes[i+1], bytes[i]);
|
||||
setNumericValue("max_ambient_temperature", Unit::C, max_ambient_temperature_c);
|
||||
info = renderJsonOnlyDefaultUnit("max_ambient_temperature", Quantity::Temperature);
|
||||
t->addSpecialExplanation(i+offset, 2, KindOfData::CONTENT, Understanding::FULL,
|
||||
"*** %02X%02X (%s)", bytes[i], bytes[i+1], info.c_str());
|
||||
|
@ -175,14 +169,18 @@ namespace
|
|||
i+=2;
|
||||
|
||||
if (i+1 >= len) return;
|
||||
average_ambient_temperature_last_month_ = toTemperature(bytes[i+1], bytes[i]);
|
||||
double average_ambient_temperature_last_month_c = toTemperature(bytes[i+1], bytes[i]);
|
||||
setNumericValue("average_ambient_temperature_last_month", Unit::C,
|
||||
average_ambient_temperature_last_month_c);
|
||||
info = renderJsonOnlyDefaultUnit("average_ambient_temperature_last_month", Quantity::Temperature);
|
||||
t->addSpecialExplanation(i+offset, 2, KindOfData::CONTENT, Understanding::FULL,
|
||||
"*** %02X%02X (%s)", bytes[i], bytes[i+1], info.c_str());
|
||||
i+=2;
|
||||
|
||||
if (i+1 >= len) return;
|
||||
average_heater_temperature_last_month_ = toTemperature(bytes[i+1], bytes[i]);
|
||||
double average_heater_temperature_last_month_c = toTemperature(bytes[i+1], bytes[i]);
|
||||
setNumericValue("average_heater_temperature_last_month", Unit::C,
|
||||
average_heater_temperature_last_month_c);
|
||||
info = renderJsonOnlyDefaultUnit("average_heater_temperature_last_month", Quantity::Temperature);
|
||||
t->addSpecialExplanation(i+offset, 2, KindOfData::CONTENT, Understanding::FULL,
|
||||
"*** %02X%02X (%s)", bytes[i], bytes[i+1], info.c_str());
|
||||
|
@ -252,5 +250,5 @@ namespace
|
|||
// Test: HCAA hydroclima 74393723 NOKEY
|
||||
// Comment:
|
||||
// telegram=|2D44B009233739743308780F9D1300023ED97AEC7BC5908A32C15D8A32C126915AC15AC126912691269187912689|
|
||||
// {"media":"heat cost allocation","meter":"hydroclima","name":"HCAA","id":"74393723","average_ambient_temperature_c":0,"max_ambient_temperature_c":0,"average_ambient_temperature_last_month_c":0,"average_heater_temperature_last_month_c":0,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |HCAA;74393723;null;0;1111-11-11 11:11.11
|
||||
// {"media":"heat cost allocation","meter":"hydroclima","name":"HCAA","id":"74393723","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |HCAA;74393723;null;null;1111-11-11 11:11.11
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2019-2022 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
Copyright (C) 2019-2023 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
Copyright (C) 2021 Vincent Privat (gpl-3.0-or-later)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
@ -17,47 +17,12 @@
|
|||
*/
|
||||
|
||||
#include"meters_common_implementation.h"
|
||||
#include"manufacturer_specificities.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
struct Driver : public virtual MeterCommonImplementation
|
||||
{
|
||||
Driver(MeterInfo &mi, DriverInfo &di);
|
||||
|
||||
void processContent(Telegram *t);
|
||||
|
||||
private:
|
||||
|
||||
// Total water counted through the meter
|
||||
double totalWaterConsumption(Unit u);
|
||||
double totalWaterConsumptionTariff1(Unit u);
|
||||
double totalWaterConsumptionTariff2(Unit u);
|
||||
double totalWaterConsumptionAtDate(Unit u);
|
||||
double totalWaterConsumptionTariff1AtDate(Unit u);
|
||||
double totalWaterConsumptionTariff2AtDate(Unit u);
|
||||
double maxFlow(Unit u);
|
||||
bool hasMaxFlow();
|
||||
double flowTemperature(Unit u);
|
||||
double externalTemperature(Unit u);
|
||||
|
||||
double total_water_consumption_m3_ {};
|
||||
double total_water_consumption_tariff1_m3_ {};
|
||||
double total_water_consumption_tariff2_m3_ {};
|
||||
string current_date_;
|
||||
double total_water_consumption_at_date_m3_ {};
|
||||
double total_water_consumption_tariff1_at_date_m3_ {};
|
||||
double total_water_consumption_tariff2_at_date_m3_ {};
|
||||
string at_date_;
|
||||
double max_flow_m3h_ {};
|
||||
double flow_temperature_c_ { 127 };
|
||||
double external_temperature_c_ {};
|
||||
uint32_t actuality_duration_s_ {};
|
||||
double operating_time_h_ {};
|
||||
double remaining_battery_life_year_ {};
|
||||
string status_; // TPL STS
|
||||
|
||||
Translate::Lookup error_codes_;
|
||||
};
|
||||
|
||||
static bool ok = registerDriver([](DriverInfo&di)
|
||||
|
@ -79,528 +44,148 @@ namespace
|
|||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
error_codes_ =
|
||||
Translate::Lookup(
|
||||
{
|
||||
{
|
||||
{
|
||||
"TPL_FLAGS",
|
||||
Translate::Type::IndexToString,
|
||||
AlwaysTrigger, MaskBits(0xe0),
|
||||
"OK",
|
||||
{
|
||||
{ 0x20, "AIR_IN_PIPE" },
|
||||
{ 0x40, "WOOT_0x40" },
|
||||
{ 0x60, "MEASUREMENT_ERROR" },
|
||||
{ 0x80, "LEAKAGE_OR_NO_USAGE" },
|
||||
{ 0xa0, "REVERSE_FLOW" },
|
||||
{ 0xc0, "LOW_TEMPERATURE" },
|
||||
{ 0xe0, "AIR_IN_PIPE" } }
|
||||
}
|
||||
},
|
||||
});
|
||||
addOptionalCommonFields("operating_time_h,actuality_duration_s,meter_datetime,customer");
|
||||
addOptionalFlowRelatedFields("flow_temperature_c,external_temperature_c");
|
||||
|
||||
addPrint("total", Quantity::Volume,
|
||||
[&](Unit u){ return totalWaterConsumption(u); },
|
||||
addStringField(
|
||||
"status",
|
||||
"Status of meter.",
|
||||
PrintProperty::STATUS | PrintProperty::INCLUDE_TPL_STATUS);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"total",
|
||||
"The total water consumption recorded by this meter.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
Quantity::Volume,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Volume)
|
||||
);
|
||||
|
||||
addPrint("total_tariff1", Quantity::Volume,
|
||||
[&](Unit u){ return totalWaterConsumptionTariff1(u); },
|
||||
"The total water consumption recorded by this meter at tariff 1.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericFieldWithExtractor(
|
||||
"total_tariff{tariff_counter}",
|
||||
"The total water consumption recorded on tariff # by this meter.",
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
Quantity::Volume,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Volume)
|
||||
.set(TariffNr(1),TariffNr(2))
|
||||
);
|
||||
|
||||
addPrint("total_tariff2", Quantity::Volume,
|
||||
[&](Unit u){ return totalWaterConsumptionTariff2(u); },
|
||||
"The total water consumption recorded by this meter at tariff 2.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericFieldWithExtractor(
|
||||
"total_tariff{tariff_counter}_at_date",
|
||||
"The total water consumption recorded on tariff # by this meter at billing date.",
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
Quantity::Volume,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Volume)
|
||||
.set(TariffNr(1),TariffNr(2))
|
||||
.set(StorageNr(1))
|
||||
);
|
||||
|
||||
addPrint("max_flow", Quantity::Flow,
|
||||
[&](Unit u){ return maxFlow(u); },
|
||||
"The maximum flow recorded during previous period.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericFieldWithExtractor(
|
||||
"flow",
|
||||
"The current water flow.",
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
Quantity::Flow,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::VolumeFlow));
|
||||
|
||||
addPrint("flow_temperature", Quantity::Temperature,
|
||||
[&](Unit u){ return flowTemperature(u); },
|
||||
"The water temperature.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
|
||||
addPrint("external_temperature", Quantity::Temperature,
|
||||
[&](Unit u){ return externalTemperature(u); },
|
||||
"The external temperature.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
|
||||
addPrint("current_date", Quantity::Text,
|
||||
[&](){ return current_date_; },
|
||||
"Current date of measurement.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
|
||||
addPrint("total_at_date", Quantity::Volume,
|
||||
[&](Unit u){ return totalWaterConsumptionAtDate(u); },
|
||||
addNumericFieldWithExtractor(
|
||||
"total_at_date",
|
||||
"The total water consumption recorded at date.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
Quantity::Volume,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Volume)
|
||||
.set(StorageNr(1))
|
||||
);
|
||||
|
||||
addPrint("total_tariff1_at_date", Quantity::Volume,
|
||||
[&](Unit u){ return totalWaterConsumptionTariff1AtDate(u); },
|
||||
"The total water consumption recorded at tariff 1 at date.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericFieldWithExtractor(
|
||||
"at",
|
||||
"The last billing period date.",
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
Quantity::PointInTime,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Date)
|
||||
.set(StorageNr(1)),
|
||||
Unit::DateLT
|
||||
);
|
||||
|
||||
addPrint("total_tariff2_at_date", Quantity::Volume,
|
||||
[&](Unit u){ return totalWaterConsumptionTariff2AtDate(u); },
|
||||
"The total water consumption recorded at tariff 2 at date.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericFieldWithExtractor(
|
||||
"total_at_date",
|
||||
"Fix this! The total water consumption recorded at last day. Perhaps?",
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
Quantity::Volume,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Volume)
|
||||
.set(StorageNr(3))
|
||||
);
|
||||
|
||||
addPrint("at_date", Quantity::Text,
|
||||
[&](){ return at_date_; },
|
||||
"Date when total water consumption was recorded.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericFieldWithExtractor(
|
||||
"at",
|
||||
"Fix this! The last billing period date last day. Perhaps?",
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
Quantity::PointInTime,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::DateTime)
|
||||
.set(StorageNr(3))
|
||||
);
|
||||
|
||||
addPrint("actuality_duration", Quantity::Time, Unit::Second,
|
||||
[&](Unit u){ return convert(actuality_duration_s_, Unit::Second, u); },
|
||||
"Elapsed time between measurement and transmission",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
|
||||
addPrint("operating_time", Quantity::Time, Unit::Hour,
|
||||
[&](Unit u){ return convert(operating_time_h_, Unit::Hour, u); },
|
||||
"How long the meter is operating",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
|
||||
addPrint("remaining_battery_life", Quantity::Time, Unit::Year,
|
||||
[&](Unit u){ return convert(remaining_battery_life_year_, Unit::Year, u); },
|
||||
"How many more years the battery is expected to last",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
|
||||
addPrint("status", Quantity::Text,
|
||||
[&](){ return status_; },
|
||||
"The status is OK or some error condition.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
}
|
||||
|
||||
void Driver::processContent(Telegram *t)
|
||||
{
|
||||
// There are two distinctintly different Hydrus telegrams.
|
||||
// Unfortunately there seems to be no markings on the
|
||||
// physical meter that tells us which version it is.
|
||||
//
|
||||
// Fortunately the mfct,media,version bits does distinguish
|
||||
// the meters!
|
||||
//
|
||||
// This driver currently only decodes parts of both telegrams.
|
||||
// Eventually we should either split this driver into two.
|
||||
// Or make more generic capabilities in drivers to switch
|
||||
// between different telegrams formats, that are similar
|
||||
// but not quite.
|
||||
|
||||
/*
|
||||
New style telegram:
|
||||
(hydrus) 11: 0C dif (8 digit BCD Instantaneous value)
|
||||
(hydrus) 12: 13 vif (Volume l)
|
||||
(hydrus) 13: * 50340000 total consumption (3.450000 m3)
|
||||
(hydrus) 17: 0D dif (variable length Instantaneous value)
|
||||
(hydrus) 18: FD vif (Second extension of VIF-codes)
|
||||
(hydrus) 19: 11 vife (Customer)
|
||||
(hydrus) 1a: 0A varlen=10
|
||||
(hydrus) 1b: 38373130313442303241
|
||||
(hydrus) 25: 0B dif (6 digit BCD Instantaneous value)
|
||||
(hydrus) 26: 3B vif (Volume flow l/h)
|
||||
(hydrus) 27: * 000000 max flow (0.000000 m3/h)
|
||||
(hydrus) 2a: 02 dif (16 Bit Integer/Binary Instantaneous value)
|
||||
(hydrus) 2b: FD vif (Second extension of VIF-codes)
|
||||
(hydrus) 2c: 74 vife (Reserved)
|
||||
(hydrus) 2d: * DC15 battery life days (5596)
|
||||
(hydrus) 2f: C4 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
|
||||
(hydrus) 30: 01 dife (subunit=0 tariff=0 storagenr=3)
|
||||
(hydrus) 31: 6D vif (Date and time type)
|
||||
(hydrus) 32: * 3B178D29 at date (2020-09-13 23:59)
|
||||
(hydrus) 36: CC dif (8 digit BCD Instantaneous value storagenr=1)
|
||||
(hydrus) 37: 01 dife (subunit=0 tariff=0 storagenr=3)
|
||||
(hydrus) 38: 13 vif (Volume l)
|
||||
(hydrus) 39: * 31340000 total consumption at date (3.431000 m3)
|
||||
(hydrus) 3d: 2F skip
|
||||
(hydrus) 3e: 2F skip
|
||||
|
||||
{"media":"warm water","meter":"hydrus","name":"Vatten","id":"65656565","total_m3":3.45,"max_flow_m3h":0,"flow_temperature_c":127,"total_at_date_m3":3.431,"at_date":"2020-09-13 23:59","battery_life_days":"5596","status":"OK","timestamp":"2020-09-27T08:54:42Z"}
|
||||
|
||||
Old style telegram
|
||||
|
||||
(hydrus) 11: 01 dif (8 Bit Integer/Binary Instantaneous value)
|
||||
(hydrus) 12: FD vif (Second extension of VIF-codes)
|
||||
(hydrus) 13: 08 vife (Access Number (transmission count))
|
||||
(hydrus) 14: 30
|
||||
(hydrus) 15: 0C dif (8 digit BCD Instantaneous value)
|
||||
(hydrus) 16: 13 vif (Volume l)
|
||||
(hydrus) 17: * 74110000 total consumption (1.174000 m3)
|
||||
(hydrus) 1b: 7C dif (8 digit BCD Value during error state storagenr=1)
|
||||
(hydrus) 1c: 13 vif (Volume l)
|
||||
(hydrus) 1d: 00000000
|
||||
(hydrus) 21: FC dif (8 digit BCD Value during error state storagenr=1)
|
||||
(hydrus) 22: 10 dife (subunit=0 tariff=1 storagenr=1)
|
||||
(hydrus) 23: 13 vif (Volume l)
|
||||
(hydrus) 24: 00000000
|
||||
(hydrus) 28: FC dif (8 digit BCD Value during error state storagenr=1)
|
||||
(hydrus) 29: 20 dife (subunit=0 tariff=2 storagenr=1)
|
||||
(hydrus) 2a: 13 vif (Volume l)
|
||||
(hydrus) 2b: 00000000
|
||||
(hydrus) 2f: 72 dif (16 Bit Integer/Binary Value during error state storagenr=1)
|
||||
(hydrus) 30: 6C vif (Date type G)
|
||||
(hydrus) 31: 0000
|
||||
(hydrus) 33: 0B dif (6 digit BCD Instantaneous value)
|
||||
(hydrus) 34: 3B vif (Volume flow l/h)
|
||||
(hydrus) 35: * 000000 max flow (0.000000 m3/h)
|
||||
(hydrus) 38: 02 dif (16 Bit Integer/Binary Instantaneous value)
|
||||
(hydrus) 39: FD vif (Second extension of VIF-codes)
|
||||
(hydrus) 3a: 74 vife (Reserved)
|
||||
(hydrus) 3b: * 8713 battery life days (4999)
|
||||
(hydrus) 3d: 02 dif (16 Bit Integer/Binary Instantaneous value)
|
||||
(hydrus) 3e: 5A vif (Flow temperature 10⁻¹ °C)
|
||||
(hydrus) 3f: * 6800 flow temperature (10.400000 °C)
|
||||
(hydrus) 41: C4 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
|
||||
(hydrus) 42: 01 dife (subunit=0 tariff=0 storagenr=3)
|
||||
(hydrus) 43: 6D vif (Date and time type)
|
||||
(hydrus) 44: * 3B177F2A at date (2019-10-31 23:59)
|
||||
(hydrus) 48: CC dif (8 digit BCD Instantaneous value storagenr=1)
|
||||
(hydrus) 49: 01 dife (subunit=0 tariff=0 storagenr=3)
|
||||
(hydrus) 4a: 13 vif (Volume l)
|
||||
(hydrus) 4b: * 00020000 total consumption at date (0.200000 m3)
|
||||
|
||||
{"media":"water","meter":"hydrus","name":"Votten","id":"64646464","total_m3":1.174,"max_flow_m3h":0,"flow_temperature_c":10.4,"total_at_date_m3":0.2,"at_date":"2019-10-31 23:59","battery_life_days":"4999","status":"OK","timestamp":"2020-09-27T08:54:42Z"}
|
||||
|
||||
*/
|
||||
/*
|
||||
Yet another version telegram:
|
||||
(hydrus) 0f: 2f2f decrypt check bytes
|
||||
(hydrus) 11: 01 dif (8 Bit Integer/Binary Instantaneous value)
|
||||
(hydrus) 12: FD vif (Second extension of VIF-codes)
|
||||
(hydrus) 13: 08 vife (Access Number (transmission count))
|
||||
(hydrus) 14: 88
|
||||
(hydrus) 15: 0C dif (8 digit BCD Instantaneous value)
|
||||
(hydrus) 16: 13 vif (Volume l)
|
||||
(hydrus) 17: * 45911600 total consumption (169.145000 m3)
|
||||
(hydrus) 1b: 8C dif (8 digit BCD Instantaneous value)
|
||||
(hydrus) 1c: 10 dife (subunit=0 tariff=1 storagenr=0)
|
||||
(hydrus) 1d: 05 vif (Energy 10² Wh)
|
||||
(hydrus) 1e: 77900200
|
||||
(hydrus) 22: 0B dif (6 digit BCD Instantaneous value)
|
||||
(hydrus) 23: 3B vif (Volume flow l/h)
|
||||
(hydrus) 24: * 000000 max flow (0.000000 m3/h)
|
||||
(hydrus) 27: 02 dif (16 Bit Integer/Binary Instantaneous value)
|
||||
(hydrus) 28: 5A vif (Flow temperature 10⁻¹ °C)
|
||||
(hydrus) 29: * DD00 flow temperature (22.100000 °C)
|
||||
(hydrus) 2b: 02 dif (16 Bit Integer/Binary Instantaneous value)
|
||||
(hydrus) 2c: FD vif (Second extension of VIF-codes)
|
||||
(hydrus) 2d: 17 vife (Error flags (binary))
|
||||
(hydrus) 2e: 0000
|
||||
(hydrus) 30: 0A dif (4 digit BCD Instantaneous value)
|
||||
(hydrus) 31: A6 vif (Operating time hours)
|
||||
(hydrus) 32: 18 vife (?)
|
||||
(hydrus) 33: 0000
|
||||
(hydrus) 35: 0B dif (6 digit BCD Instantaneous value)
|
||||
(hydrus) 36: 26 vif (Operating time hours)
|
||||
(hydrus) 37: 255802
|
||||
(hydrus) 3a: 2B dif (6 digit BCD Minimum value)
|
||||
(hydrus) 3b: 3B vif (Volume flow l/h)
|
||||
(hydrus) 3c: 000000
|
||||
(hydrus) 3f: C4 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
|
||||
(hydrus) 40: 01 dife (subunit=0 tariff=0 storagenr=3)
|
||||
(hydrus) 41: 6D vif (Date and time type)
|
||||
(hydrus) 42: * 3B179F2A at date (2020-10-31 23:59)
|
||||
(hydrus) 46: CC dif (8 digit BCD Instantaneous value storagenr=1)
|
||||
(hydrus) 47: 01 dife (subunit=0 tariff=0 storagenr=3)
|
||||
(hydrus) 48: 13 vif (Volume l)
|
||||
(hydrus) 49: * 14811600 total consumption at date (168.114000 m3)
|
||||
(hydrus) 4d: 02 dif (16 Bit Integer/Binary Instantaneous value)
|
||||
(hydrus) 4e: 66 vif (External temperature 10⁻¹ °C)
|
||||
(hydrus) 4f: D400
|
||||
(hydrus) 51: 2F skip
|
||||
(hydrus) 52: 2F skip
|
||||
(hydrus) 53: 2F skip
|
||||
(hydrus) 54: 2F skip
|
||||
(hydrus) 55: 2F skip
|
||||
(hydrus) 56: 2F skip
|
||||
(hydrus) 57: 2F skip
|
||||
(hydrus) 58: 2F skip
|
||||
(hydrus) 59: 2F skip
|
||||
(hydrus) 5a: 2F skip
|
||||
(hydrus) 5b: 2F skip
|
||||
(hydrus) 5c: 2F skip
|
||||
(hydrus) 5d: 2F skip
|
||||
(hydrus) 5e: 2F skip
|
||||
*/
|
||||
|
||||
/* Another one for #216 / #223
|
||||
|
||||
(hydrus) 00: 66 length (102 bytes)
|
||||
(hydrus) 01: 44 dll-c (from meter SND_NR)
|
||||
(hydrus) 02: 2423 dll-mfct (HYD)
|
||||
(hydrus) 04: 28001081 dll-id (81100028)
|
||||
(hydrus) 08: 64 dll-version
|
||||
(hydrus) 09: 0e dll-type (Bus/System component)
|
||||
(hydrus) 0a: 72 tpl-ci-field (EN 13757-3 Application Layer (long tplh))
|
||||
(hydrus) 0b: 66567464 tpl-id (64745666)
|
||||
(hydrus) 0f: a511 tpl-mfct (DME)
|
||||
(hydrus) 11: 70 tpl-version
|
||||
(hydrus) 12: 07 tpl-type (Water meter)
|
||||
(hydrus) 13: 1f tpl-acc-field
|
||||
(hydrus) 14: 00 tpl-sts-field
|
||||
(hydrus) 15: 5005 tpl-cfg 0550 (AES_CBC_IV nb=5 cntn=0 ra=0 hc=0 )
|
||||
(hydrus) 17: 2f2f decrypt check bytes
|
||||
(hydrus) 19: 03 dif (24 Bit Integer/Binary Instantaneous value)
|
||||
(hydrus) 1a: 74 vif (Actuality duration seconds)
|
||||
(hydrus) 1b: * 111A00 actuality duration (-0.000029 s)
|
||||
(hydrus) 1e: 0C dif (8 digit BCD Instantaneous value)
|
||||
(hydrus) 1f: 13 vif (Volume l)
|
||||
(hydrus) 20: * 91721300 total consumption (137.291000 m3)
|
||||
(hydrus) 24: 8C dif (8 digit BCD Instantaneous value)
|
||||
(hydrus) 25: 10 dife (subunit=0 tariff=1 storagenr=0)
|
||||
(hydrus) 26: 13 vif (Volume l)
|
||||
(hydrus) 27: * 00000000 total consumption at tariff 1 (0.000000 m3)
|
||||
(hydrus) 2b: 8C dif (8 digit BCD Instantaneous value)
|
||||
(hydrus) 2c: 20 dife (subunit=0 tariff=2 storagenr=0)
|
||||
(hydrus) 2d: 13 vif (Volume l)
|
||||
(hydrus) 2e: * 91721300 total consumption at tariff 2 (137.291000 m3)
|
||||
(hydrus) 32: 0B dif (6 digit BCD Instantaneous value)
|
||||
(hydrus) 33: 3B vif (Volume flow l/h)
|
||||
(hydrus) 34: * 000000 max flow (0.000000 m3/h)
|
||||
(hydrus) 37: 0B dif (6 digit BCD Instantaneous value)
|
||||
(hydrus) 38: 26 vif (Operating time hours)
|
||||
(hydrus) 39: * 784601 operating time (14678.000000 h)
|
||||
(hydrus) 3c: 02 dif (16 Bit Integer/Binary Instantaneous value)
|
||||
(hydrus) 3d: 5A vif (Flow temperature 10⁻¹ °C)
|
||||
(hydrus) 3e: * F500 flow temperature (24.500000 °C)
|
||||
(hydrus) 40: 02 dif (16 Bit Integer/Binary Instantaneous value)
|
||||
(hydrus) 41: 66 vif (External temperature 10⁻¹ °C)
|
||||
(hydrus) 42: * EF00 external temperature (23.900000 °C)
|
||||
(hydrus) 44: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
(hydrus) 45: 6D vif (Date and time type)
|
||||
(hydrus) 46: * 1B08B721 current date (2021-01-23 08:27)
|
||||
(hydrus) 4a: 4C dif (8 digit BCD Instantaneous value storagenr=1)
|
||||
(hydrus) 4b: 13 vif (Volume l)
|
||||
(hydrus) 4c: * 38861200 total consumption at date (128.638000 m3)
|
||||
(hydrus) 50: CC dif (8 digit BCD Instantaneous value storagenr=1)
|
||||
(hydrus) 51: 10 dife (subunit=0 tariff=1 storagenr=1)
|
||||
(hydrus) 52: 13 vif (Volume l)
|
||||
(hydrus) 53: * 00000000 total consumption at tariff 1 at date (0.000000 m3)
|
||||
(hydrus) 57: CC dif (8 digit BCD Instantaneous value storagenr=1)
|
||||
(hydrus) 58: 20 dife (subunit=0 tariff=2 storagenr=1)
|
||||
(hydrus) 59: 13 vif (Volume l)
|
||||
(hydrus) 5a: * 38861200 total consumption at tariff 1 at date (128.638000 m3)
|
||||
(hydrus) 5e: 42 dif (16 Bit Integer/Binary Instantaneous value storagenr=1)
|
||||
(hydrus) 5f: 6C vif (Date type G)
|
||||
(hydrus) 60: * 9F2C at date (2020-12-31 00:00)
|
||||
(hydrus) 62: 42 dif (16 Bit Integer/Binary Instantaneous value storagenr=1)
|
||||
(hydrus) 63: EC vif (Date type G)
|
||||
(hydrus) 64: 7E vife (additive correction constant: unit of VIF * 10^-1)
|
||||
(hydrus) 65: BF2C
|
||||
*/
|
||||
|
||||
/* And another two more that seem to be branded IZAR RS 868 */
|
||||
/*
|
||||
(hydrus) 00: 1e length (30 bytes)
|
||||
(hydrus) 01: 44 dll-c (from meter SND_NR)
|
||||
(hydrus) 02: 2423 dll-mfct (HYD)
|
||||
(hydrus) 04: 79738960 dll-id (60897379)
|
||||
(hydrus) 08: 8b dll-version
|
||||
(hydrus) 09: 07 dll-type (Water meter)
|
||||
(hydrus) 0a: 7a tpl-ci-field (EN 13757-3 Application Layer (short tplh))
|
||||
(hydrus) 0b: 8f tpl-acc-field
|
||||
(hydrus) 0c: 00 tpl-sts-field (OK)
|
||||
(hydrus) 0d: 107d tpl-cfg 7d10 (accessibility synchronous SPECIFIC_16_31 )
|
||||
(hydrus) 0f: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
(hydrus) 10: 13 vif (Volume l)
|
||||
(hydrus) 11: * 12170100 total consumption (71.442000 m3)
|
||||
(hydrus) 15: 42 dif (16 Bit Integer/Binary Instantaneous value storagenr=1)
|
||||
(hydrus) 16: 6C vif (Date type G)
|
||||
(hydrus) 17: * BF23 at date (2021-03-31 00:00)
|
||||
(hydrus) 19: 44 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
|
||||
(hydrus) 1a: 13 vif (Volume l)
|
||||
(hydrus) 1b: * 44100100 total consumption at date (69.700000 m3)
|
||||
|
||||
(hydrus) 01: 44 dll-c (from meter SND_NR)
|
||||
(hydrus) 02: 2423 dll-mfct (HYD)
|
||||
(hydrus) 04: 20479060 dll-id (60904720)
|
||||
(hydrus) 08: 8b dll-version
|
||||
(hydrus) 09: 06 dll-type (Warm Water (30°C-90°C) meter)
|
||||
(hydrus) 0a: 7a tpl-ci-field (EN 13757-3 Application Layer (short tplh))
|
||||
(hydrus) 0b: 2a tpl-acc-field
|
||||
(hydrus) 0c: 00 tpl-sts-field (OK)
|
||||
(hydrus) 0d: 10d8 tpl-cfg d810 (bidirectional accessibility SPECIFIC_16_31 )
|
||||
(hydrus) 0f: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
(hydrus) 10: 13 vif (Volume l)
|
||||
(hydrus) 11: * DDC00000 total consumption (49.373000 m3)
|
||||
(hydrus) 15: 42 dif (16 Bit Integer/Binary Instantaneous value storagenr=1)
|
||||
(hydrus) 16: 6C vif (Date type G)
|
||||
(hydrus) 17: * BF23 at date (2021-03-31 00:00)
|
||||
(hydrus) 19: 44 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
|
||||
(hydrus) 1a: 13 vif (Volume l)
|
||||
(hydrus) 1b: * 82BB0000 total consumption at date (48.002000 m3)
|
||||
*/
|
||||
|
||||
int offset;
|
||||
string key;
|
||||
struct tm datetime;
|
||||
|
||||
// Container 0 : current / total
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::Volume, 0, 0, &key, &t->dv_entries)) {
|
||||
extractDVdouble(&t->dv_entries, key, &offset, &total_water_consumption_m3_);
|
||||
t->addMoreExplanation(offset, " total consumption (%f m3)", total_water_consumption_m3_);
|
||||
}
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::Volume, 0, 1, &key, &t->dv_entries)) {
|
||||
extractDVdouble(&t->dv_entries, key, &offset, &total_water_consumption_tariff1_m3_);
|
||||
t->addMoreExplanation(offset, " total consumption at tariff 1 (%f m3)", total_water_consumption_tariff1_m3_);
|
||||
}
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::Volume, 0, 2, &key, &t->dv_entries)) {
|
||||
extractDVdouble(&t->dv_entries, key, &offset, &total_water_consumption_tariff2_m3_);
|
||||
t->addMoreExplanation(offset, " total consumption at tariff 2 (%f m3)", total_water_consumption_tariff2_m3_);
|
||||
}
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::VolumeFlow, 0, 0, &key, &t->dv_entries)) {
|
||||
extractDVdouble(&t->dv_entries, key, &offset, &max_flow_m3h_);
|
||||
t->addMoreExplanation(offset, " max flow (%f m3/h)", max_flow_m3h_);
|
||||
}
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::FlowTemperature, 0, 0, &key, &t->dv_entries)) {
|
||||
extractDVdouble(&t->dv_entries, key, &offset, &flow_temperature_c_);
|
||||
t->addMoreExplanation(offset, " flow temperature (%f °C)", flow_temperature_c_);
|
||||
}
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::ExternalTemperature, 0, 0, &key, &t->dv_entries)) {
|
||||
extractDVdouble(&t->dv_entries, key, &offset, &external_temperature_c_);
|
||||
t->addMoreExplanation(offset, " external temperature (%f °C)", external_temperature_c_);
|
||||
}
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::DateTime, 0, 0, &key, &t->dv_entries)) {
|
||||
extractDVdate(&t->dv_entries, key, &offset, &datetime);
|
||||
current_date_ = strdatetime(&datetime);
|
||||
t->addMoreExplanation(offset, " current date (%s)", current_date_.c_str());
|
||||
}
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::ActualityDuration, 0, 0, &key, &t->dv_entries)) {
|
||||
extractDVuint24(&t->dv_entries, key, &offset, &actuality_duration_s_);
|
||||
t->addMoreExplanation(offset, " actuality duration (%f s)", actuality_duration_s_);
|
||||
}
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::OperatingTime, 0, 0, &key, &t->dv_entries)) {
|
||||
extractDVdouble(&t->dv_entries, key, &offset, &operating_time_h_);
|
||||
t->addMoreExplanation(offset, " operating time (%f h)", operating_time_h_);
|
||||
}
|
||||
|
||||
// Container 1/3 : past/future records
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::Volume, 1, 0, &key, &t->dv_entries)
|
||||
|| findKey(MeasurementType::Instantaneous, VIFRange::Volume, 3, 0, &key, &t->dv_entries)) {
|
||||
extractDVdouble(&t->dv_entries, key, &offset, &total_water_consumption_at_date_m3_);
|
||||
t->addMoreExplanation(offset, " total consumption at date (%f m3)", total_water_consumption_at_date_m3_);
|
||||
}
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::Volume, 1, 1, &key, &t->dv_entries)
|
||||
|| findKey(MeasurementType::Instantaneous, VIFRange::Volume, 3, 1, &key, &t->dv_entries)) {
|
||||
extractDVdouble(&t->dv_entries, key, &offset, &total_water_consumption_tariff1_at_date_m3_);
|
||||
t->addMoreExplanation(offset, " total consumption at tariff 1 at date (%f m3)", total_water_consumption_tariff1_at_date_m3_);
|
||||
}
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::Volume, 1, 2, &key, &t->dv_entries)
|
||||
|| findKey(MeasurementType::Instantaneous, VIFRange::Volume, 3, 2, &key, &t->dv_entries)) {
|
||||
extractDVdouble(&t->dv_entries, key, &offset, &total_water_consumption_tariff2_at_date_m3_);
|
||||
t->addMoreExplanation(offset, " total consumption at tariff 1 at date (%f m3)", total_water_consumption_tariff2_at_date_m3_);
|
||||
}
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::Date , 1, 0, &key, &t->dv_entries)
|
||||
|| findKey(MeasurementType::Instantaneous, VIFRange::DateTime, 3, 0, &key, &t->dv_entries)) {
|
||||
extractDVdate(&t->dv_entries, key, &offset, &datetime);
|
||||
at_date_ = strdatetime(&datetime);
|
||||
t->addMoreExplanation(offset, " at date (%s)", at_date_.c_str());
|
||||
}
|
||||
|
||||
// TODO: a date in the future is also transmitted with VIFE 7E in container 1
|
||||
|
||||
// custom
|
||||
|
||||
uint16_t days {};
|
||||
if (hasKey(&t->dv_entries, "02FD74") && extractDVuint16(&t->dv_entries, "02FD74", &offset, &days))
|
||||
{
|
||||
remaining_battery_life_year_ = ((double)days) / 365.25;
|
||||
t->addMoreExplanation(offset, " battery life (%d days %f years)", days, remaining_battery_life_year_);
|
||||
}
|
||||
|
||||
status_ = ::decodeTPLStatusByteWithMfct(t->tpl_sts, error_codes_);
|
||||
}
|
||||
|
||||
double Driver::totalWaterConsumption(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Volume);
|
||||
return convert(total_water_consumption_m3_, Unit::M3, u);
|
||||
}
|
||||
|
||||
double Driver::totalWaterConsumptionTariff1(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Volume);
|
||||
return convert(total_water_consumption_tariff1_m3_, Unit::M3, u);
|
||||
}
|
||||
|
||||
double Driver::totalWaterConsumptionTariff2(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Volume);
|
||||
return convert(total_water_consumption_tariff2_m3_, Unit::M3, u);
|
||||
}
|
||||
|
||||
double Driver::totalWaterConsumptionAtDate(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Volume);
|
||||
return convert(total_water_consumption_at_date_m3_, Unit::M3, u);
|
||||
}
|
||||
|
||||
double Driver::totalWaterConsumptionTariff1AtDate(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Volume);
|
||||
return convert(total_water_consumption_tariff1_at_date_m3_, Unit::M3, u);
|
||||
}
|
||||
|
||||
double Driver::totalWaterConsumptionTariff2AtDate(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Volume);
|
||||
return convert(total_water_consumption_tariff2_at_date_m3_, Unit::M3, u);
|
||||
}
|
||||
|
||||
double Driver::maxFlow(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Flow);
|
||||
return convert(max_flow_m3h_, Unit::M3H, u);
|
||||
}
|
||||
|
||||
bool Driver::hasMaxFlow()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
double Driver::flowTemperature(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Temperature);
|
||||
return convert(flow_temperature_c_, Unit::C, u);
|
||||
}
|
||||
|
||||
double Driver::externalTemperature(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Temperature);
|
||||
return convert(external_temperature_c_, Unit::C, u);
|
||||
addNumericFieldWithExtractor(
|
||||
"remaining_battery_life",
|
||||
"Remaining battery life in years.",
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
Quantity::Time,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::RemainingBattery),
|
||||
Unit::Year);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Test: HydrusWater hydrus 64646464 NOKEY
|
||||
// Comment:
|
||||
// telegram=|4E44A5116464646470077AED004005_2F2F01FD08300C13741100007C1300000000FC101300000000FC201300000000726C00000B3B00000002FD748713025A6800C4016D3B177F2ACC011300020000|
|
||||
// {"media":"water","meter":"hydrus","name":"HydrusWater","id":"64646464","total_m3":1.174,"total_tariff1_m3":0,"total_tariff2_m3":0,"max_flow_m3h":0,"flow_temperature_c":10.4,"external_temperature_c":0,"current_date":"","total_at_date_m3":0,"total_tariff1_at_date_m3":0,"total_tariff2_at_date_m3":0,"at_date":"2000-00-00 00:00","actuality_duration_s":0,"operating_time_h":0,"remaining_battery_life_y":13.686516,"status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |HydrusWater;64646464;1.174;0;OK;1111-11-11 11:11.11
|
||||
// {"media":"water","meter":"hydrus","name":"HydrusWater","id":"64646464","total_m3":1.174,"flow_m3h":0,"flow_temperature_c":10.4,"remaining_battery_life_y":13.686797,"status":"OK","at_datetime":"2019-10-31 23:59","total_at_date_m3": 0.2,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |HydrusWater;64646464;1.174;0.2;OK;1111-11-11 11:11.11
|
||||
|
||||
// Test: HydrusVater hydrus 65656565 NOKEY
|
||||
// Comment:
|
||||
// telegram=|3E44A5116565656570067AFB0030052F2F_0C13503400000DFD110A383731303134423032410B3B00000002FD74DC15C4016D3B178D29CC0113313400002F2F|
|
||||
// {"media":"warm water","meter":"hydrus","name":"HydrusVater","id":"65656565","total_m3":3.45,"total_tariff1_m3":0,"total_tariff2_m3":0,"max_flow_m3h":0,"flow_temperature_c":127,"external_temperature_c":0,"current_date":"","total_at_date_m3":3.431,"total_tariff1_at_date_m3":0,"total_tariff2_at_date_m3":0,"at_date":"2020-09-13 23:59","actuality_duration_s":0,"operating_time_h":0,"remaining_battery_life_y":15.321013,"status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// {"media":"warm water","meter":"hydrus","name":"HydrusVater","id":"65656565","flow_m3h":0,"customer": "A20B410178","total_m3":3.45,"total_at_date_m3":3.431,"remaining_battery_life_y":15.321328,"at_datetime":"2020-09-13 23:59","total_at_date_m3": 3.431,"status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |HydrusVater;65656565;3.45;3.431;OK;1111-11-11 11:11.11
|
||||
|
||||
// Test: HydrusAES hydrus 64745666 NOKEY
|
||||
// Comment:
|
||||
// telegram=||6644242328001081640E7266567464A51170071F0050052C411A08674048DD6BA82A0DF79FFD401309179A893A1BE3CE8EDC50C2A45CD7AFEC3B4CE765820BE8056C124A17416C3722985FFFF7FCEB7094901AB3A16294B511B9A740C9F9911352B42A72FB3B0C|
|
||||
// {"media":"water","meter":"hydrus","name":"HydrusAES","id":"64745666","total_m3":137.291,"total_tariff1_m3":0,"total_tariff2_m3":137.291,"max_flow_m3h":0,"flow_temperature_c":24.5,"external_temperature_c":23.9,"current_date":"2021-01-23 08:27","total_at_date_m3":128.638,"total_tariff1_at_date_m3":0,"total_tariff2_at_date_m3":128.638,"at_date":"2020-12-31 00:00","actuality_duration_s":6673,"operating_time_h":14678,"remaining_battery_life_y":0,"status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// {"media":"water","meter":"hydrus","name":"HydrusAES","id":"64745666","total_m3":137.291,"total_tariff1_m3":0,"total_tariff2_m3":137.291,"flow_m3h":0,"flow_temperature_c":24.5,"external_temperature_c":23.9,"total_at_date_m3":128.638,"total_tariff1_at_date_m3":0,"total_tariff2_at_date_m3":128.638,"at_date":"2020-12-31","actuality_duration_s":6673,"operating_time_h":14678,"meter_datetime": "2021-01-23 08:27","status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |HydrusAES;64745666;137.291;128.638;OK;1111-11-11 11:11.11
|
||||
|
||||
// Test: HydrusIzarRS hydrus 60897379 NOKEY
|
||||
// Comment: Hydrus IZAR RS 868 water meter
|
||||
// telegram=|1E4424238B07797389607A8F00107D_041312170100426CBF23441344100100|
|
||||
// {"media":"water","meter":"hydrus","name":"HydrusIzarRS","id":"60897379","total_m3":71.442,"at_date": "2021-03-31","total_at_date_m3":69.7,"status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |HydrusIzarRS;60897379;71.442;69.7;OK;1111-11-11 11:11.11
|
||||
|
||||
// Test: HydrusIzarRSWarm hydrus 60904720 NOKEY
|
||||
// Comment: Hydrus IZAR RS 868 water meter warm
|
||||
// telegram=|1E4424238B06204790607A2A0010D8_0413DDC00000426CBF23441382BB0000|
|
||||
// {"media":"warm water","meter":"hydrus","name":"HydrusIzarRSWarm","id":"60904720","total_m3":49.373,"total_at_date_m3":48.002,"at_date":"2021-03-31","status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |HydrusIzarRSWarm;60904720;49.373;48.002;OK;1111-11-11 11:11.11
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
Copyright (C) 2019 Jacek Tomasiak (gpl-3.0-or-later)
|
||||
Copyright (C) 2020-2022 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
Copyright (C) 2020-2023 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
Copyright (C) 2021 Vincent Privat (gpl-3.0-or-later)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
|
@ -23,7 +23,8 @@
|
|||
namespace
|
||||
{
|
||||
/** Contains all the booleans required to store the alarms of a PRIOS device. */
|
||||
typedef struct _izar_alarms {
|
||||
struct IzarAlarms
|
||||
{
|
||||
bool general_alarm;
|
||||
bool leakage_currently;
|
||||
bool leakage_previously;
|
||||
|
@ -36,7 +37,7 @@ namespace
|
|||
bool sensor_fraud_previously;
|
||||
bool mechanical_fraud_currently;
|
||||
bool mechanical_fraud_previously;
|
||||
} izar_alarms;
|
||||
};
|
||||
|
||||
struct Driver : public virtual MeterCommonImplementation
|
||||
{
|
||||
|
@ -46,27 +47,11 @@ namespace
|
|||
|
||||
private:
|
||||
|
||||
double totalWaterConsumption(Unit u);
|
||||
string serialNumber();
|
||||
double lastMonthTotalWaterConsumption(Unit u);
|
||||
string setH0Date();
|
||||
string currentAlarmsText();
|
||||
string previousAlarmsText();
|
||||
string currentAlarmsText(IzarAlarms &alarms);
|
||||
string previousAlarmsText(IzarAlarms &alarms);
|
||||
|
||||
vector<uchar> decodePrios(const vector<uchar> &origin, const vector<uchar> &payload, uint32_t key);
|
||||
|
||||
string prefix;
|
||||
uint32_t serial_number {0};
|
||||
double remaining_battery_life;
|
||||
uint16_t h0_year;
|
||||
uint8_t h0_month;
|
||||
uint8_t h0_day;
|
||||
double total_water_consumption_l_ {};
|
||||
double last_month_total_water_consumption_l_ {};
|
||||
uint32_t transmit_period_s_ {};
|
||||
uint16_t manufacture_year {0};
|
||||
izar_alarms alarms;
|
||||
|
||||
vector<uint32_t> keys;
|
||||
};
|
||||
|
||||
|
@ -89,6 +74,7 @@ namespace
|
|||
di.addDetection(MANUFACTURER_DME, 0x07, 0x78);
|
||||
di.addDetection(MANUFACTURER_DME, 0x06, 0x78);
|
||||
di.addDetection(MANUFACTURER_HYD, 0x07, 0x86);
|
||||
di.usesProcessContent();
|
||||
|
||||
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
|
||||
});
|
||||
|
@ -97,84 +83,52 @@ namespace
|
|||
{
|
||||
initializeDiehlDefaultKeySupport(meterKeys()->confidentiality_key, keys);
|
||||
|
||||
addPrint("prefix", Quantity::Text,
|
||||
[&](){ return prefix; },
|
||||
addStringField("prefix",
|
||||
"The alphanumeric prefix printed before serial number on device.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
|
||||
addPrint("serial_number", Quantity::Text,
|
||||
[&](){ return serialNumber(); },
|
||||
addStringField("serial_number",
|
||||
"The meter serial number.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
|
||||
addPrint("total", Quantity::Volume,
|
||||
[&](Unit u){ return totalWaterConsumption(u); },
|
||||
"The total water consumption recorded by this meter.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericField("total",
|
||||
Quantity::Volume,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"The total water consumption recorded by this meter.");
|
||||
|
||||
addPrint("last_month_total", Quantity::Volume,
|
||||
[&](Unit u){ return lastMonthTotalWaterConsumption(u); },
|
||||
"The total water consumption recorded by this meter around end of last month.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericField("last_month_total",
|
||||
Quantity::Volume,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"The total water consumption recorded by this meter around end of last month.");
|
||||
|
||||
addPrint("last_month_measure_date", Quantity::Text,
|
||||
[&](){ return setH0Date(); },
|
||||
addStringField("last_month_measure_date",
|
||||
"The date when the meter recorded the most recent billing value.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
|
||||
addPrint("remaining_battery_life", Quantity::Time, Unit::Year,
|
||||
[&](Unit u){ return convert(remaining_battery_life, Unit::Year, u); },
|
||||
addNumericField("remaining_battery_life",
|
||||
Quantity::Time,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"How many more years the battery is expected to last",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
|
||||
addPrint("current_alarms", Quantity::Text,
|
||||
[&](){ return currentAlarmsText(); },
|
||||
Unit::Year);
|
||||
addStringField("current_alarms",
|
||||
"Alarms currently reported by the meter.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
|
||||
addPrint("previous_alarms", Quantity::Text,
|
||||
[&](){ return previousAlarmsText(); },
|
||||
addStringField("previous_alarms",
|
||||
"Alarms previously reported by the meter.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
|
||||
addPrint("transmit_period", Quantity::Time, Unit::Second,
|
||||
[&](Unit u){ return convert(transmit_period_s_, Unit::Second, u); },
|
||||
addNumericField("transmit_period", Quantity::Time,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"The period at which the meter transmits its data.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
Unit::Second);
|
||||
|
||||
addPrint("manufacture_year", Quantity::Text,
|
||||
[&](){ return to_string(manufacture_year); },
|
||||
addStringField("manufacture_year",
|
||||
"The year during which the meter was manufactured.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
}
|
||||
|
||||
double Driver::totalWaterConsumption(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Volume);
|
||||
return convert(total_water_consumption_l_, Unit::L, u);
|
||||
}
|
||||
|
||||
double Driver::lastMonthTotalWaterConsumption(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Volume);
|
||||
return convert(last_month_total_water_consumption_l_, Unit::L, u);
|
||||
}
|
||||
|
||||
string Driver::setH0Date()
|
||||
{
|
||||
string date;
|
||||
strprintf(&date, "%d-%02d-%02d", h0_year, h0_month%99, h0_day%99);
|
||||
return date;
|
||||
}
|
||||
|
||||
string Driver::serialNumber()
|
||||
{
|
||||
string result;
|
||||
strprintf(&result, "%06d", serial_number);
|
||||
return result;
|
||||
}
|
||||
|
||||
string Driver::currentAlarmsText()
|
||||
string Driver::currentAlarmsText(IzarAlarms &alarms)
|
||||
{
|
||||
string s;
|
||||
if (alarms.leakage_currently) {
|
||||
|
@ -211,7 +165,7 @@ namespace
|
|||
return "no_alarm";
|
||||
}
|
||||
|
||||
string Driver::previousAlarmsText()
|
||||
string Driver::previousAlarmsText(IzarAlarms &alarms)
|
||||
{
|
||||
string s;
|
||||
if (alarms.leakage_previously) {
|
||||
|
@ -259,35 +213,50 @@ namespace
|
|||
string digits = to_string((origin[7] & 0x03) << 24 | origin[6] << 16 | origin[5] << 8 | origin[4]);
|
||||
// get the manufacture year
|
||||
uint8_t yy = atoi(digits.substr(0, 2).c_str());
|
||||
manufacture_year = yy > 70 ? (1900 + yy) : (2000 + yy); // Maybe to adjust in 2070, if this code stills lives :D
|
||||
int manufacture_year = yy > 70 ? (1900 + yy) : (2000 + yy); // Maybe to adjust in 2070, if this code stills lives :D
|
||||
setStringValue("manufacture_year", tostrprintf("%d", manufacture_year));
|
||||
|
||||
// get the serial number
|
||||
serial_number = atoi(digits.substr(2, digits.size()).c_str());
|
||||
uint32_t serial_number = atoi(digits.substr(2, digits.size()).c_str());
|
||||
setStringValue("serial_number", tostrprintf("%06d", serial_number));
|
||||
|
||||
// get letters
|
||||
uchar supplier_code = '@' + (((origin[9] & 0x0F) << 1) | (origin[8] >> 7));
|
||||
uchar meter_type = '@' + ((origin[8] & 0x7C) >> 2);
|
||||
uchar diameter = '@' + (((origin[8] & 0x03) << 3) | (origin[7] >> 5));
|
||||
// build the prefix
|
||||
strprintf(&prefix, "%c%02d%c%c", supplier_code, yy, meter_type, diameter);
|
||||
string prefix = tostrprintf("%c%02d%c%c", supplier_code, yy, meter_type, diameter);
|
||||
setStringValue("prefix", prefix);
|
||||
}
|
||||
|
||||
// get the remaining battery life (in year) and transmission period (in seconds)
|
||||
remaining_battery_life = (frame[12] & 0x1F) / 2.0;
|
||||
transmit_period_s_ = 1 << ((frame[11] & 0x0F) + 2);
|
||||
double remaining_battery_life = (frame[12] & 0x1F) / 2.0;
|
||||
setNumericValue("remaining_battery_life", Unit::Year, remaining_battery_life);
|
||||
|
||||
total_water_consumption_l_ = uint32FromBytes(decoded_content, 1, true);
|
||||
last_month_total_water_consumption_l_ = uint32FromBytes(decoded_content, 5, true);
|
||||
int transmit_period_s = 1 << ((frame[11] & 0x0F) + 2);
|
||||
setNumericValue("transmit_period", Unit::Second, transmit_period_s);
|
||||
|
||||
double total_water_consumption_l_ = uint32FromBytes(decoded_content, 1, true);
|
||||
setNumericValue("total", Unit::L, total_water_consumption_l_);
|
||||
|
||||
double last_month_total_water_consumption_l_ = uint32FromBytes(decoded_content, 5, true);
|
||||
setNumericValue("last_month_total", Unit::L, last_month_total_water_consumption_l_);
|
||||
|
||||
// get the date when the second measurement was taken
|
||||
h0_year = ((decoded_content[10] & 0xF0) >> 1) + ((decoded_content[9] & 0xE0) >> 5);
|
||||
uint16_t h0_year = ((decoded_content[10] & 0xF0) >> 1) + ((decoded_content[9] & 0xE0) >> 5);
|
||||
if (h0_year > 80) {
|
||||
h0_year += 1900;
|
||||
} else {
|
||||
h0_year += 2000;
|
||||
}
|
||||
h0_month = decoded_content[10] & 0xF;
|
||||
h0_day = decoded_content[9] & 0x1F;
|
||||
uint8_t h0_month = decoded_content[10] & 0xF;
|
||||
uint8_t h0_day = decoded_content[9] & 0x1F;
|
||||
|
||||
setStringValue("last_month_measure_date", tostrprintf("%d-%02d-%02d", h0_year, h0_month%99, h0_day%99));
|
||||
|
||||
// read the alarms:
|
||||
IzarAlarms alarms {};
|
||||
|
||||
alarms.general_alarm = frame[11] >> 7;
|
||||
alarms.leakage_currently = frame[12] >> 7;
|
||||
alarms.leakage_previously = frame[12] >> 6 & 0x1;
|
||||
|
@ -300,6 +269,9 @@ namespace
|
|||
alarms.sensor_fraud_previously = frame[13] >> 2 & 0x1;
|
||||
alarms.mechanical_fraud_currently = frame[13] >> 1 & 0x1;
|
||||
alarms.mechanical_fraud_previously = frame[13] & 0x1;
|
||||
|
||||
setStringValue("current_alarms", currentAlarmsText(alarms));
|
||||
setStringValue("previous_alarms", previousAlarmsText(alarms));
|
||||
}
|
||||
|
||||
vector<uchar> Driver::decodePrios(const vector<uchar> &origin, const vector<uchar> &frame, uint32_t key)
|
||||
|
@ -309,19 +281,19 @@ namespace
|
|||
}
|
||||
|
||||
// Test: IzarWater izar 21242472 NOKEY
|
||||
// telegram=|1944304C72242421D401A2|013D4013DD8B46A4999C1293E582CC|
|
||||
// telegram=|1944304C72242421D401A2_013D4013DD8B46A4999C1293E582CC|
|
||||
// {"media":"water","meter":"izar","name":"IzarWater","id":"21242472","prefix":"C19UA","serial_number":"145842","total_m3":3.488,"last_month_total_m3":3.486,"last_month_measure_date":"2019-09-30","remaining_battery_life_y":14.5,"current_alarms":"meter_blocked,underflow","previous_alarms":"no_alarm","transmit_period_s":8,"manufacture_year":"2019","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |IzarWater;21242472;C19UA;145842;3.488;3.486;2019-09-30;14.5;meter_blocked,underflow;no_alarm;8;2019;1111-11-11 11:11.11
|
||||
|
||||
// Test: IzarWater2 izar 66236629 NOKEY
|
||||
// telegram=|2944A511780729662366A20118001378D3B3DB8CEDD77731F25832AAF3DA8CADF9774EA673172E8C61F2|
|
||||
// {"media":"water","meter":"izar","name":"IzarWater2","id":"66236629","prefix":"","serial_number":"000000","total_m3":16.76,"last_month_total_m3":11.84,"last_month_measure_date":"2019-11-30","remaining_battery_life_y":12,"current_alarms":"no_alarm","previous_alarms":"no_alarm","transmit_period_s":8,"manufacture_year":"0","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |IzarWater2;66236629;;000000;16.76;11.84;2019-11-30;12;no_alarm;no_alarm;8;0;1111-11-11 11:11.11
|
||||
// {"media":"water","meter":"izar","name":"IzarWater2","id":"66236629","total_m3":16.76,"last_month_total_m3":11.84,"last_month_measure_date":"2019-11-30","remaining_battery_life_y":12,"current_alarms":"no_alarm","previous_alarms":"no_alarm","transmit_period_s":8,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |IzarWater2;66236629;null;null;16.76;11.84;2019-11-30;12;no_alarm;no_alarm;8;null;1111-11-11 11:11.11
|
||||
|
||||
// Test: IzarWater3 izar 20481979 NOKEY
|
||||
// telegram=|1944A511780779194820A1|21170013355F8EDB2D03C6912B1E37
|
||||
// {"media":"water","meter":"izar","name":"IzarWater3","id":"20481979","prefix":"","serial_number":"000000","total_m3":4.366,"last_month_total_m3":0,"last_month_measure_date":"2020-12-31","remaining_battery_life_y":11.5,"current_alarms":"no_alarm","previous_alarms":"no_alarm","transmit_period_s":8,"manufacture_year":"0","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |IzarWater3;20481979;;000000;4.366;0;2020-12-31;11.5;no_alarm;no_alarm;8;0;1111-11-11 11:11.11
|
||||
// telegram=|1944A511780779194820A1_21170013355F8EDB2D03C6912B1E37
|
||||
// {"media":"water","meter":"izar","name":"IzarWater3","id":"20481979","total_m3":4.366,"last_month_total_m3":0,"last_month_measure_date":"2020-12-31","remaining_battery_life_y":11.5,"current_alarms":"no_alarm","previous_alarms":"no_alarm","transmit_period_s":8,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |IzarWater3;20481979;null;null;4.366;0;2020-12-31;11.5;no_alarm;no_alarm;8;null;1111-11-11 11:11.11
|
||||
|
||||
// Test: IzarWater4 izar 2124589c NOKEY
|
||||
// Comment: With mfct specific tpl ci field a3.
|
||||
|
@ -331,11 +303,11 @@ namespace
|
|||
|
||||
// Test: IzarWater5 izar 20e4ffde NOKEY
|
||||
// Comment: Ensure non-regression on manufacture year parsing
|
||||
// telegram=|1944304CDEFFE420CC01A2|63120013258F907B0AFF12529AC33B|
|
||||
// telegram=|1944304CDEFFE420CC01A2_63120013258F907B0AFF12529AC33B|
|
||||
// {"media":"water","meter":"izar","name":"IzarWater5","id":"20e4ffde","prefix":"C15SA","serial_number":"007710","total_m3":159.832,"last_month_total_m3":157.76,"last_month_measure_date":"2021-02-01","remaining_battery_life_y":9,"current_alarms":"no_alarm","previous_alarms":"no_alarm","transmit_period_s":32,"manufacture_year":"2015","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |IzarWater5;20e4ffde;C15SA;007710;159.832;157.76;2021-02-01;9;no_alarm;no_alarm;32;2015;1111-11-11 11:11.11
|
||||
|
||||
// Test: IzarWater6 izar 48500375 NOKEY
|
||||
// telegram=|19442423860775035048A251520015BEB6B2E1ED623A18FC74A5|
|
||||
// {"media":"water","meter":"izar","name":"IzarWater6","id":"48500375","prefix":"","serial_number":"000000","total_m3":521.602,"last_month_total_m3":519.147,"last_month_measure_date":"2021-11-15","remaining_battery_life_y":9,"current_alarms":"no_alarm","previous_alarms":"leakage","transmit_period_s":8,"manufacture_year":"0","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |IzarWater6;48500375;;000000;521.602;519.147;2021-11-15;9;no_alarm;leakage;8;0;1111-11-11 11:11.11
|
||||
// {"media":"water","meter":"izar","name":"IzarWater6","id":"48500375","total_m3":521.602,"last_month_total_m3":519.147,"last_month_measure_date":"2021-11-15","remaining_battery_life_y":9,"current_alarms":"no_alarm","previous_alarms":"leakage","transmit_period_s":8,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |IzarWater6;48500375;null;null;521.602;519.147;2021-11-15;9;no_alarm;leakage;8;null;1111-11-11 11:11.11
|
||||
|
|
|
@ -17,22 +17,11 @@
|
|||
|
||||
#include"meters_common_implementation.h"
|
||||
|
||||
#define INFO_CODE_CLOSED 0x0011
|
||||
#define INFO_CODE_OPEN 0x0055
|
||||
|
||||
struct MeterLansenDW : public virtual MeterCommonImplementation
|
||||
namespace
|
||||
{
|
||||
MeterLansenDW(MeterInfo &mi, DriverInfo &di);
|
||||
|
||||
private:
|
||||
|
||||
void processContent(Telegram *t);
|
||||
|
||||
string status();
|
||||
|
||||
uint16_t info_codes_ {};
|
||||
double pulse_counter_a_ {};
|
||||
double pulse_counter_b_ {};
|
||||
struct Driver : public virtual MeterCommonImplementation
|
||||
{
|
||||
Driver(MeterInfo &mi, DriverInfo &di);
|
||||
};
|
||||
|
||||
static bool ok = registerDriver([](DriverInfo&di)
|
||||
|
@ -41,120 +30,70 @@ static bool ok = registerDriver([](DriverInfo&di)
|
|||
di.setDefaultFields("name,id,status,timestamp");
|
||||
di.setMeterType(MeterType::DoorWindowDetector);
|
||||
di.addLinkMode(LinkMode::T1);
|
||||
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new MeterLansenDW(mi, di)); });
|
||||
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
|
||||
di.addDetection(MANUFACTURER_LAS, 0x1d, 0x07);
|
||||
});
|
||||
|
||||
MeterLansenDW::MeterLansenDW(MeterInfo &mi, DriverInfo &di) :
|
||||
MeterCommonImplementation(mi, di)
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
setMeterType(MeterType::DoorWindowDetector);
|
||||
addStringFieldWithExtractorAndLookup(
|
||||
"status",
|
||||
"The state (OPEN/CLOSED) for the door/window.",
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::DigitalInput),
|
||||
Translate::Lookup()
|
||||
.add(Translate::Rule("INPUT_BITS", Translate::Type::IndexToString)
|
||||
.set(MaskBits(0xffff))
|
||||
.add(Translate::Map(0x11 ,"CLOSED", TestBit::Set))
|
||||
.add(Translate::Map(0x55 ,"OPEN", TestBit::Set))
|
||||
));
|
||||
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
addStringFieldWithExtractorAndLookup(
|
||||
"error_flags",
|
||||
"Error flags.",
|
||||
PrintProperty::STATUS | PrintProperty::INCLUDE_TPL_STATUS,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::ErrorFlags),
|
||||
Translate::Lookup()
|
||||
.add(Translate::Rule("ERROR_FLAGS", Translate::Type::BitToString)
|
||||
.set(MaskBits(0xffff))
|
||||
.set(DefaultMessage("OK"))
|
||||
));
|
||||
|
||||
addLinkMode(LinkMode::T1);
|
||||
|
||||
addPrint("status", Quantity::Text,
|
||||
[&](){ return status(); },
|
||||
"The current status: OPEN or CLOSED.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
|
||||
addPrint("a", Quantity::Counter,
|
||||
[&](Unit u) { assertQuantity(u, Quantity::Counter); return pulse_counter_a_; },
|
||||
addNumericFieldWithExtractor(
|
||||
"a",
|
||||
"How many times the door/window has been opened or closed.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
Quantity::Counter,
|
||||
VifScaling::None,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Dimensionless)
|
||||
);
|
||||
|
||||
addPrint("b", Quantity::Counter,
|
||||
[&](Unit u) { assertQuantity(u, Quantity::Counter); return pulse_counter_b_; },
|
||||
addNumericFieldWithExtractor(
|
||||
"b",
|
||||
"The current number of counted pulses from counter b.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
}
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
Quantity::Counter,
|
||||
VifScaling::None,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Dimensionless)
|
||||
.set(SubUnitNr(1))
|
||||
);
|
||||
|
||||
|
||||
shared_ptr<Meter> createLansenDW(MeterInfo &mi)
|
||||
{
|
||||
DriverInfo di;
|
||||
di.setName("lansendw");
|
||||
return shared_ptr<Meter>(new MeterLansenDW(mi, di));
|
||||
}
|
||||
|
||||
|
||||
void MeterLansenDW::processContent(Telegram *t)
|
||||
{
|
||||
/*
|
||||
(wmbus) 11: 02 dif (16 Bit Integer/Binary Instantaneous value)
|
||||
(wmbus) 12: FD vif (Second extension of VIF-codes)
|
||||
(wmbus) 13: 1B vife (Digital Input (binary))
|
||||
(wmbus) 14: 1100
|
||||
(wmbus) 16: 02 dif (16 Bit Integer/Binary Instantaneous value)
|
||||
(wmbus) 17: FD vif (Second extension of VIF-codes)
|
||||
(wmbus) 18: 97 vife (Error flags (binary))
|
||||
(wmbus) 19: 1D vife (Response delay time [bittimes])
|
||||
(wmbus) 1a: 0100
|
||||
(wmbus) 1c: 0E dif (12 digit BCD Instantaneous value)
|
||||
(wmbus) 1d: FD vif (Second extension of VIF-codes)
|
||||
(wmbus) 1e: 3A vife (Dimensionless / no VIF)
|
||||
(wmbus) 1f: 220000000000
|
||||
(wmbus) 25: 8E dif (12 digit BCD Instantaneous value)
|
||||
(wmbus) 26: 40 dife (subunit=1 tariff=0 storagenr=0)
|
||||
(wmbus) 27: FD vif (Second extension of VIF-codes)
|
||||
(wmbus) 28: 3A vife (Dimensionless / no VIF)
|
||||
(wmbus) 29: 000000000000
|
||||
*/
|
||||
|
||||
/*
|
||||
(wmbus) 11: 02 dif (16 Bit Integer/Binary Instantaneous value)
|
||||
(wmbus) 12: FD vif (Second extension of VIF-codes)
|
||||
(wmbus) 13: 1B vife (Digital Input (binary))
|
||||
(wmbus) 14: 5500
|
||||
(wmbus) 16: 02 dif (16 Bit Integer/Binary Instantaneous value)
|
||||
(wmbus) 17: FD vif (Second extension of VIF-codes)
|
||||
(wmbus) 18: 97 vife (Error flags (binary))
|
||||
(wmbus) 19: 1D vife (Response delay time [bittimes])
|
||||
(wmbus) 1a: 0100
|
||||
(wmbus) 1c: 0E dif (12 digit BCD Instantaneous value)
|
||||
(wmbus) 1d: FD vif (Second extension of VIF-codes)
|
||||
(wmbus) 1e: 3A vife (Dimensionless / no VIF)
|
||||
(wmbus) 1f: 230000000000
|
||||
(wmbus) 25: 8E dif (12 digit BCD Instantaneous value)
|
||||
(wmbus) 26: 40 dife (subunit=1 tariff=0 storagenr=0)
|
||||
(wmbus) 27: FD vif (Second extension of VIF-codes)
|
||||
(wmbus) 28: 3A vife (Dimensionless / no VIF)
|
||||
(wmbus) 29: 000000000000
|
||||
*/
|
||||
int offset;
|
||||
|
||||
if (extractDVuint16(&t->dv_entries, "02FD1B", &offset, &info_codes_))
|
||||
{
|
||||
t->addMoreExplanation(offset, renderJsonOnlyDefaultUnit("status", Quantity::Text));
|
||||
}
|
||||
|
||||
if (extractDVdouble(&t->dv_entries, "0EFD3A", &offset, &pulse_counter_a_, false))
|
||||
{
|
||||
t->addMoreExplanation(offset, renderJsonOnlyDefaultUnit("counter_a", Quantity::Counter));
|
||||
}
|
||||
|
||||
if (extractDVdouble(&t->dv_entries, "8E40FD3A", &offset, &pulse_counter_b_, false))
|
||||
{
|
||||
t->addMoreExplanation(offset, renderJsonOnlyDefaultUnit("counter_b", Quantity::Counter));
|
||||
}
|
||||
}
|
||||
|
||||
string MeterLansenDW::status()
|
||||
{
|
||||
string s;
|
||||
if (info_codes_ == INFO_CODE_OPEN)
|
||||
{
|
||||
s.append("OPEN");
|
||||
}
|
||||
else if (info_codes_ == INFO_CODE_CLOSED)
|
||||
{
|
||||
s.append("CLOSED");
|
||||
}
|
||||
else
|
||||
{
|
||||
s.append("ERR");
|
||||
}
|
||||
// Test: Dooro lansendw 00010205 NOKEY
|
||||
// telegram=|2e44333005020100071d7ab54800002f2f02fd1b110002fd971d01000efd3a2200000000008e40fd3a000000000000|
|
||||
// {"media":"reserved","meter":"lansendw","name":"Dooro","id":"00010205","status":"CLOSED","a_counter":22,"b_counter":0,"error_flags":"ERROR_FLAGS_1 PERMANENT_ERROR UNKNOWN_40","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |Dooro;00010205;CLOSED;1111-11-11 11:11.11
|
||||
|
||||
return s;
|
||||
}
|
||||
// telegram=|2e44333005020100071d7ab66800002f2f02fd1b550002fd971d01000efd3a2300000000008e40fd3a000000000000|
|
||||
// {"media":"reserved","meter":"lansendw","name":"Dooro","id":"00010205","status":"OPEN","a_counter":23,"b_counter":0,"error_flags":"ERROR_FLAGS_1 PERMANENT_ERROR UNKNOWN_60","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |Dooro;00010205;OPEN;1111-11-11 11:11.11
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2019-2022 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
Copyright (C) 2019-2023 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
|
||||
|
@ -24,20 +24,6 @@ namespace
|
|||
Driver(MeterInfo &mi, DriverInfo &di);
|
||||
|
||||
void processContent(Telegram *t);
|
||||
|
||||
private:
|
||||
|
||||
double totalWaterConsumption(Unit u);
|
||||
bool hasTotalWaterConsumption();
|
||||
double targetWaterConsumption(Unit u);
|
||||
bool hasTargetWaterConsumption();
|
||||
string currentDate();
|
||||
string previousDate();
|
||||
|
||||
double total_water_consumption_m3_ {};
|
||||
double target_water_consumption_m3_ {};
|
||||
string current_date_ {};
|
||||
string previous_date_ {};
|
||||
};
|
||||
|
||||
static bool ok = registerDriver([](DriverInfo&di)
|
||||
|
@ -54,23 +40,21 @@ namespace
|
|||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addPrint("total", Quantity::Volume,
|
||||
[&](Unit u){ return totalWaterConsumption(u); },
|
||||
"The total water consumption recorded by this meter.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericField("total",
|
||||
Quantity::Volume,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"The total water consumption recorded by this meter.");
|
||||
|
||||
addPrint("target", Quantity::Volume,
|
||||
[&](Unit u){ return targetWaterConsumption(u); },
|
||||
"The total water consumption recorded at the beginning of this month.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericField("target",
|
||||
Quantity::Volume,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"The total water consumption recorded at the beginning of this month.");
|
||||
|
||||
addPrint("current_date", Quantity::Text,
|
||||
[&](){ return currentDate(); },
|
||||
addStringField("current_date",
|
||||
"Date of current billing period.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
|
||||
addPrint("prev_date", Quantity::Text,
|
||||
[&](){ return previousDate(); },
|
||||
addStringField("prev_date",
|
||||
"Date of previous billing period.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
}
|
||||
|
@ -91,83 +75,31 @@ namespace
|
|||
uint prev_date_day = (prev_date >> 0) & 0x1F;
|
||||
uint prev_date_month = (prev_date >> 5) & 0x0F;
|
||||
uint prev_date_year = (prev_date >> 9) & 0x3F;
|
||||
strprintf(&previous_date_, "%d-%02d-%02dT02:00:00Z", prev_date_year+2000, prev_date_month, prev_date_day);
|
||||
|
||||
string prev_date_str;
|
||||
strprintf(&prev_date_str, "%04x", prev_date);
|
||||
uint offset = t->parsed.size() + 1;
|
||||
vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x6c, {}, 0, 0, 0, prev_date_str) };
|
||||
t->explanations.push_back(Explanation(offset, 1, prev_date_str, KindOfData::CONTENT, Understanding::FULL));
|
||||
t->addMoreExplanation(offset, " previous date (%s)", previous_date_.c_str());
|
||||
setStringValue("prev_date",
|
||||
tostrprintf("%d-%02d-%02dT02:00:00Z", prev_date_year+2000, prev_date_month, prev_date_day));
|
||||
|
||||
// Previous consumption
|
||||
uchar prev_lo = content[3];
|
||||
uchar prev_hi = content[4];
|
||||
double prev = (256.0*prev_hi+prev_lo)/10.0;
|
||||
|
||||
string prevs;
|
||||
strprintf(&prevs, "%02x%02x", prev_lo, prev_hi);
|
||||
offset = t->parsed.size()+3;
|
||||
vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, 0, 0, 0, prevs) };
|
||||
t->explanations.push_back(Explanation(offset, 2, prevs, KindOfData::CONTENT, Understanding::FULL));
|
||||
t->addMoreExplanation(offset, " prev consumption (%f m3)", prev);
|
||||
|
||||
// Current date
|
||||
uint16_t current_date = (content[6] << 8) | content[5];
|
||||
uint current_date_day = (current_date >> 4) & 0x1F;
|
||||
uint current_date_month = (current_date >> 9) & 0x0F;
|
||||
strprintf(¤t_date_, "%s-%02d-%02dT02:00:00Z", currentYear().c_str(), current_date_month, current_date_day);
|
||||
|
||||
string current_date_str;
|
||||
strprintf(¤t_date_str, "%04x", current_date);
|
||||
offset = t->parsed.size() + 5;
|
||||
vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x6c, {}, 0, 0, 0, current_date_str) };
|
||||
t->explanations.push_back(Explanation(offset, 1, current_date_str, KindOfData::CONTENT, Understanding::FULL));
|
||||
t->addMoreExplanation(offset, " current date (%s)", current_date_.c_str());
|
||||
setStringValue("current_date",
|
||||
tostrprintf("%s-%02d-%02dT02:00:00Z",
|
||||
currentYear().c_str(), current_date_month, current_date_day));
|
||||
|
||||
// Current consumption
|
||||
uchar curr_lo = content[7];
|
||||
uchar curr_hi = content[8];
|
||||
double curr = (256.0*curr_hi+curr_lo)/10.0;
|
||||
|
||||
string currs;
|
||||
strprintf(&currs, "%02x%02x", curr_lo, curr_hi);
|
||||
offset = t->parsed.size()+7;
|
||||
vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, 0, 0, 0, currs) };
|
||||
t->explanations.push_back(Explanation(offset, 2, currs, KindOfData::CONTENT, Understanding::FULL));
|
||||
t->addMoreExplanation(offset, " curr consumption (%f m3)", curr);
|
||||
|
||||
total_water_consumption_m3_ = prev+curr;
|
||||
target_water_consumption_m3_ = prev;
|
||||
}
|
||||
|
||||
double Driver::totalWaterConsumption(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Volume);
|
||||
return convert(total_water_consumption_m3_, Unit::M3, u);
|
||||
}
|
||||
|
||||
bool Driver::hasTotalWaterConsumption()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
double Driver::targetWaterConsumption(Unit u)
|
||||
{
|
||||
return target_water_consumption_m3_;
|
||||
}
|
||||
|
||||
bool Driver::hasTargetWaterConsumption()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
string Driver::currentDate() {
|
||||
return current_date_;
|
||||
}
|
||||
|
||||
string Driver::previousDate() {
|
||||
return previous_date_;
|
||||
double total_water_consumption_m3 = prev+curr;
|
||||
setNumericValue("total", Unit::M3, total_water_consumption_m3);
|
||||
double target_water_consumption_m3 = prev;
|
||||
setNumericValue("target", Unit::M3, target_water_consumption_m3);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2019-2022 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
Copyright (C) 2019-2023 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
|
||||
|
@ -24,14 +24,6 @@ namespace
|
|||
Driver(MeterInfo &mi, DriverInfo &di);
|
||||
|
||||
void processContent(Telegram *t);
|
||||
|
||||
private:
|
||||
|
||||
double totalWaterConsumption(Unit u);
|
||||
double targetWaterConsumption(Unit u);
|
||||
|
||||
double total_water_consumption_m3_ {};
|
||||
double target_water_consumption_m3_ {};
|
||||
};
|
||||
|
||||
static bool ok = registerDriver([](DriverInfo&di)
|
||||
|
@ -50,20 +42,20 @@ namespace
|
|||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addPrint("total", Quantity::Volume,
|
||||
[&](Unit u){ return totalWaterConsumption(u); },
|
||||
"The total water consumption recorded by this meter.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericField("total",
|
||||
Quantity::Volume,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"The total water consumption recorded by this meter.");
|
||||
|
||||
addPrint("target", Quantity::Volume,
|
||||
[&](Unit u){ return targetWaterConsumption(u); },
|
||||
"The total water consumption recorded at the beginning of this month.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
addNumericField("target",
|
||||
Quantity::Volume,
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
"The total water consumption recorded at the beginning of this month.");
|
||||
}
|
||||
|
||||
void Driver::processContent(Telegram *t)
|
||||
{
|
||||
// Unfortunately, the MK Radio 4 is mostly a proprieatary protocol
|
||||
// The MK Radio 4 is mostly a proprietary protocol
|
||||
// simple wrapped inside a wmbus telegram since the ci-field is 0xa2.
|
||||
// Which means that the entire payload is manufacturer specific.
|
||||
|
||||
|
@ -76,37 +68,32 @@ namespace
|
|||
uchar prev_hi = content[4];
|
||||
double prev = (256.0*prev_hi+prev_lo)/10.0;
|
||||
|
||||
/*
|
||||
string prevs;
|
||||
strprintf(&prevs, "%02x%02x", prev_lo, prev_hi);
|
||||
int offset = t->parsed.size()+3;
|
||||
vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, 0, 0, 0, prevs) };
|
||||
t->explanations.push_back(Explanation(offset, 2, prevs, KindOfData::CONTENT, Understanding::FULL));
|
||||
t->addMoreExplanation(offset, " prev consumption (%f m3)", prev);
|
||||
*/
|
||||
|
||||
uchar curr_lo = content[7];
|
||||
uchar curr_hi = content[8];
|
||||
double curr = (256.0*curr_hi+curr_lo)/10.0;
|
||||
|
||||
/*
|
||||
string currs;
|
||||
strprintf(&currs, "%02x%02x", curr_lo, curr_hi);
|
||||
offset = t->parsed.size()+7;
|
||||
vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, 0, 0, 0, currs) };
|
||||
t->explanations.push_back(Explanation(offset, 2, currs, KindOfData::CONTENT, Understanding::FULL));
|
||||
t->addMoreExplanation(offset, " curr consumption (%f m3)", curr);
|
||||
*/
|
||||
|
||||
total_water_consumption_m3_ = prev+curr;
|
||||
target_water_consumption_m3_ = prev;
|
||||
}
|
||||
|
||||
double Driver::totalWaterConsumption(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Volume);
|
||||
return convert(total_water_consumption_m3_, Unit::M3, u);
|
||||
}
|
||||
|
||||
double Driver::targetWaterConsumption(Unit u)
|
||||
{
|
||||
return target_water_consumption_m3_;
|
||||
double total_water_consumption_m3 = prev+curr;
|
||||
setNumericValue("total", Unit::M3, total_water_consumption_m3);
|
||||
double target_water_consumption_m3 = prev;
|
||||
setNumericValue("target", Unit::M3, target_water_consumption_m3);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2020-2022 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
Copyright (C) 2020-2023 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
|
||||
|
@ -24,23 +24,13 @@ namespace
|
|||
Driver(MeterInfo &mi, DriverInfo &di);
|
||||
|
||||
void processContent(Telegram *t);
|
||||
|
||||
private:
|
||||
|
||||
string status();
|
||||
bool smokeDetected();
|
||||
string previousDate();
|
||||
|
||||
uint16_t info_codes_ {};
|
||||
bool error_ {};
|
||||
string previous_date_ {};
|
||||
};
|
||||
|
||||
static bool ok = registerDriver([](DriverInfo&di)
|
||||
{
|
||||
di.setName("tsd2");
|
||||
di.setDefaultFields("name,id,status,prev_date,timestamp");
|
||||
di.setMeterType(MeterType::WaterMeter);
|
||||
di.setMeterType(MeterType::SmokeDetector);
|
||||
di.addLinkMode(LinkMode::T1);
|
||||
di.addDetection(MANUFACTURER_TCH, 0xf0, 0x76);
|
||||
|
||||
|
@ -49,88 +39,58 @@ namespace
|
|||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
setMeterType(MeterType::SmokeDetector);
|
||||
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
addLinkMode(LinkMode::T1);
|
||||
|
||||
addPrint("status", Quantity::Text,
|
||||
[&](){ return status(); },
|
||||
addStringField("status",
|
||||
"The current status: OK, SMOKE or ERROR.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
|
||||
addPrint("prev_date", Quantity::Text,
|
||||
[&](){ return previousDate(); },
|
||||
addStringField("prev_date",
|
||||
"Date of previous billing period.",
|
||||
DEFAULT_PRINT_PROPERTIES);
|
||||
}
|
||||
|
||||
#define INFO_CODE_SMOKE 0x0001
|
||||
|
||||
bool Driver::smokeDetected()
|
||||
{
|
||||
return info_codes_ & INFO_CODE_SMOKE;
|
||||
}
|
||||
#define INFO_CODE_SMOKE 0x01
|
||||
|
||||
void Driver::processContent(Telegram *t)
|
||||
{
|
||||
vector<uchar> data;
|
||||
t->extractPayload(&data);
|
||||
|
||||
if(data.empty())
|
||||
if(data.size() < 3)
|
||||
{
|
||||
error_ = true;
|
||||
setStringValue("status", "ERROR");
|
||||
return;
|
||||
}
|
||||
|
||||
info_codes_ = data[0];
|
||||
error_ = false;
|
||||
// Check CRC etc here....
|
||||
uint8_t status = data[0];
|
||||
if (status & INFO_CODE_SMOKE)
|
||||
{
|
||||
setStringValue("status", "SMOKE");
|
||||
}
|
||||
else
|
||||
{
|
||||
setStringValue("status", "OK");
|
||||
}
|
||||
|
||||
// Previous date
|
||||
uint16_t prev_date = (data[2] << 8) | data[1];
|
||||
uint prev_date_day = (prev_date >> 0) & 0x1F;
|
||||
uint prev_date_month = (prev_date >> 5) & 0x0F;
|
||||
uint prev_date_year = (prev_date >> 9) & 0x3F;
|
||||
strprintf(&previous_date_, "%d-%02d-%02dT02:00:00Z", prev_date_year+2000, prev_date_month, prev_date_day);
|
||||
|
||||
string prev_date_str;
|
||||
strprintf(&prev_date_str, "%04x", prev_date);
|
||||
uint offset = t->parsed.size() + 1;
|
||||
t->explanations.push_back(Explanation(offset, 1, prev_date_str, KindOfData::CONTENT, Understanding::FULL));
|
||||
t->addMoreExplanation(offset, " previous date (%s)", previous_date_.c_str());
|
||||
}
|
||||
|
||||
string Driver::status()
|
||||
{
|
||||
string s;
|
||||
bool smoke = info_codes_ & INFO_CODE_SMOKE;
|
||||
if (smoke)
|
||||
{
|
||||
s.append("SMOKE ");
|
||||
}
|
||||
|
||||
if (error_)
|
||||
{
|
||||
s.append("ERROR ");
|
||||
}
|
||||
|
||||
if (s.length() > 0)
|
||||
{
|
||||
s.pop_back();
|
||||
return s;
|
||||
}
|
||||
return "OK";
|
||||
}
|
||||
|
||||
string Driver::previousDate()
|
||||
{
|
||||
return previous_date_;
|
||||
setStringValue("prev_date",
|
||||
tostrprintf("%d-%02d-%02dT02:00:00Z", prev_date_year+2000, prev_date_month, prev_date_day));
|
||||
}
|
||||
}
|
||||
|
||||
// Test: Smokey tsd2 91633569 NOKEY
|
||||
|
||||
// telegram=|294468506935639176F0A0_019F|
|
||||
// {"media":"smoke detector","meter":"tsd2","name":"Smokey","id":"91633569","status":"ERROR","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |Smokey;91633569;ERROR;null;1111-11-11 11:11.11
|
||||
|
||||
// telegram=|294468506935639176F0A0_009F2782290060822900000401D6311AF93E1BF93E008DC3009ED4000FE500|
|
||||
// {"media":"smoke detector","meter":"tsd2","name":"Smokey","id":"91633569","status":"OK","prev_date":"2019-12-31T02:00:00Z","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |Smokey;91633569;OK;2019-12-31T02:00:00Z;1111-11-11 11:11.11
|
||||
|
||||
// telegram=|294468506935639176F0A0_019F2782290060822900000401D6311AF93E1BF93E008DC3009ED4000FE500|
|
||||
// {"media":"smoke detector","meter":"tsd2","name":"Smokey","id":"91633569","status":"SMOKE","prev_date":"2019-12-31T02:00:00Z","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |Smokey;91633569;SMOKE;2019-12-31T02:00:00Z;1111-11-11 11:11.11
|
||||
|
|
165
src/meters.cc
165
src/meters.cc
|
@ -302,70 +302,6 @@ void MeterCommonImplementation::setMfctTPLStatusBits(Translate::Lookup &lookup)
|
|||
mfct_tpl_status_bits_ = lookup;
|
||||
}
|
||||
|
||||
void MeterCommonImplementation::addPrint(string vname, Quantity vquantity,
|
||||
function<double(Unit)> getValueFunc, string help, PrintProperties pprops)
|
||||
{
|
||||
field_infos_.emplace_back(
|
||||
FieldInfo(field_infos_.size(),
|
||||
vname,
|
||||
vquantity,
|
||||
defaultUnitForQuantity(vquantity),
|
||||
VifScaling::Auto,
|
||||
FieldMatcher(),
|
||||
help,
|
||||
pprops,
|
||||
getValueFunc,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NoLookup, /* Lookup table */
|
||||
NULL /* Formula */
|
||||
));
|
||||
}
|
||||
|
||||
void MeterCommonImplementation::addPrint(string vname, Quantity vquantity, Unit unit,
|
||||
function<double(Unit)> getValueFunc, string help, PrintProperties pprops)
|
||||
{
|
||||
field_infos_.emplace_back(
|
||||
FieldInfo(field_infos_.size(),
|
||||
vname,
|
||||
vquantity,
|
||||
unit,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher(),
|
||||
help,
|
||||
pprops,
|
||||
getValueFunc,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NoLookup, /* Lookup table */
|
||||
NULL /* Formula */
|
||||
));
|
||||
}
|
||||
|
||||
void MeterCommonImplementation::addPrint(string vname, Quantity vquantity,
|
||||
function<string()> getValueFunc,
|
||||
string help, PrintProperties pprops)
|
||||
{
|
||||
field_infos_.emplace_back(
|
||||
FieldInfo(field_infos_.size(),
|
||||
vname,
|
||||
vquantity,
|
||||
defaultUnitForQuantity(vquantity),
|
||||
VifScaling::Auto,
|
||||
FieldMatcher(),
|
||||
help,
|
||||
pprops,
|
||||
NULL,
|
||||
getValueFunc,
|
||||
NULL,
|
||||
NULL,
|
||||
NoLookup, /* Lookup table */
|
||||
NULL /* Formula */
|
||||
));
|
||||
}
|
||||
|
||||
void MeterCommonImplementation::addNumericFieldWithExtractor(string vname,
|
||||
string help,
|
||||
PrintProperties print_properties,
|
||||
|
@ -469,31 +405,6 @@ void MeterCommonImplementation::addNumericFieldWithCalculatorAndMatcher(string v
|
|||
));
|
||||
}
|
||||
|
||||
void MeterCommonImplementation::addNumericField(
|
||||
string vname,
|
||||
Quantity vquantity,
|
||||
PrintProperties print_properties,
|
||||
string help,
|
||||
function<void(Unit,double)> setValueFunc,
|
||||
function<double(Unit)> getValueFunc)
|
||||
{
|
||||
field_infos_.emplace_back(
|
||||
FieldInfo(field_infos_.size(),
|
||||
vname,
|
||||
vquantity,
|
||||
defaultUnitForQuantity(vquantity),
|
||||
VifScaling::None,
|
||||
FieldMatcher(),
|
||||
help,
|
||||
print_properties,
|
||||
getValueFunc,
|
||||
NULL,
|
||||
setValueFunc,
|
||||
NULL,
|
||||
NoLookup, /* Lookup table */
|
||||
NULL /* Formula */
|
||||
));
|
||||
}
|
||||
|
||||
void MeterCommonImplementation::addNumericField(
|
||||
string vname,
|
||||
|
@ -543,39 +454,6 @@ void MeterCommonImplementation::addStringFieldWithExtractor(string vname,
|
|||
));
|
||||
}
|
||||
|
||||
void MeterCommonImplementation::addStringFieldWithExtractorAndLookup(
|
||||
string vname,
|
||||
Quantity vquantity,
|
||||
DifVifKey dif_vif_key,
|
||||
MeasurementType mt,
|
||||
VIFRange vi,
|
||||
StorageNr s,
|
||||
TariffNr t,
|
||||
IndexNr i,
|
||||
PrintProperties print_properties,
|
||||
string help,
|
||||
function<void(string)> setValueFunc,
|
||||
function<string()> getValueFunc,
|
||||
Translate::Lookup lookup)
|
||||
{
|
||||
field_infos_.emplace_back(
|
||||
FieldInfo(field_infos_.size(),
|
||||
vname,
|
||||
vquantity,
|
||||
defaultUnitForQuantity(vquantity),
|
||||
VifScaling::None,
|
||||
FieldMatcher::build().set(dif_vif_key).set(mt).set(vi).set(s).set(t).set(i),
|
||||
help,
|
||||
print_properties,
|
||||
NULL,
|
||||
getValueFunc,
|
||||
NULL,
|
||||
setValueFunc,
|
||||
lookup, /* Lookup table */
|
||||
NULL /* Formula */
|
||||
));
|
||||
}
|
||||
|
||||
void MeterCommonImplementation::addStringFieldWithExtractorAndLookup(string vname,
|
||||
string help,
|
||||
PrintProperties print_properties,
|
||||
|
@ -2566,6 +2444,35 @@ void MeterCommonImplementation::addOptionalCommonFields(string field_names)
|
|||
{
|
||||
set<string> fields = splitStringIntoSet(field_names, ',');
|
||||
|
||||
if (checkIf(fields, "actuality_duration_s"))
|
||||
{
|
||||
addNumericFieldWithExtractor(
|
||||
"actuality_duration",
|
||||
"Lapsed time between measurement and transmission.",
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
Quantity::Time,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::ActualityDuration),
|
||||
Unit::Second
|
||||
);
|
||||
}
|
||||
|
||||
if (checkIf(fields, "actuality_duration_h"))
|
||||
{
|
||||
addNumericFieldWithExtractor(
|
||||
"actuality_duration",
|
||||
"Lapsed time between measurement and transmission.",
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
Quantity::Time,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::ActualityDuration)
|
||||
);
|
||||
}
|
||||
|
||||
if (checkIf(fields, "fabrication_no"))
|
||||
{
|
||||
addStringFieldWithExtractor(
|
||||
|
@ -2805,6 +2712,20 @@ void MeterCommonImplementation::addOptionalFlowRelatedFields(string field_names)
|
|||
);
|
||||
}
|
||||
|
||||
if (checkIf(fields,"external_temperature_c"))
|
||||
{
|
||||
addNumericFieldWithExtractor(
|
||||
"external_temperature",
|
||||
"Temperature outside of meter.",
|
||||
DEFAULT_PRINT_PROPERTIES,
|
||||
Quantity::Temperature,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::ExternalTemperature)
|
||||
);
|
||||
}
|
||||
|
||||
if (checkIf(fields,"return_temperature_c"))
|
||||
{
|
||||
addNumericFieldWithExtractor(
|
||||
|
|
|
@ -182,6 +182,7 @@ private:
|
|||
vector<DriverDetect> detect_;
|
||||
vector<string> default_fields_;
|
||||
int force_mfct_index_ = -1; // Used for meters not declaring mfct specific data using the dif 0f.
|
||||
bool has_process_content_ = false; // Mark this driver as having mfct specific decoding.
|
||||
|
||||
public:
|
||||
DriverInfo() {};
|
||||
|
@ -193,6 +194,8 @@ public:
|
|||
void forceMfctIndex(int i) { force_mfct_index_ = i; }
|
||||
void setConstructor(function<shared_ptr<Meter>(MeterInfo&,DriverInfo&)> c) { constructor_ = c; }
|
||||
void addDetection(uint16_t mfct, uchar type, uchar ver) { detect_.push_back({ mfct, type, ver }); }
|
||||
void usesProcessContent() { has_process_content_ = true; }
|
||||
|
||||
vector<DriverDetect> &detect() { return detect_; }
|
||||
|
||||
DriverName name() { return name_; }
|
||||
|
@ -212,6 +215,7 @@ public:
|
|||
bool isValidMedia(uchar type);
|
||||
bool isCloseEnoughMedia(uchar type);
|
||||
int forceMfctIndex() { return force_mfct_index_; }
|
||||
bool hasProcessContentCode() { return has_process_content_; }
|
||||
};
|
||||
|
||||
bool registerDriver(function<void(DriverInfo&di)> setup);
|
||||
|
|
|
@ -100,28 +100,6 @@ protected:
|
|||
void addLinkMode(LinkMode lm);
|
||||
void setMfctTPLStatusBits(Translate::Lookup &lookup);
|
||||
|
||||
// Print with the default unit for this quantity.
|
||||
void addPrint(string vname, Quantity vquantity,
|
||||
function<double(Unit)> getValueFunc, string help, PrintProperties pprops);
|
||||
// Print with exactly this unit for this quantity.
|
||||
void addPrint(string vname, Quantity vquantity, Unit unit,
|
||||
function<double(Unit)> getValueFunc, string help, PrintProperties pprops);
|
||||
// Print the dimensionless Text quantity, no unit is needed.
|
||||
void addPrint(string vname, Quantity vquantity,
|
||||
function<std::string()> getValueFunc, string help, PrintProperties pprops);
|
||||
|
||||
#define SET_FUNC(varname,to_unit) {[=](Unit from_unit, double d){varname = convert(d, from_unit, to_unit);}}
|
||||
#define GET_FUNC(varname,from_unit) {[=](Unit to_unit){return convert(varname, from_unit, to_unit);}}
|
||||
#define LOOKUP_FIELD(DVKEY) NoDifVifKey,VifScaling::Auto,MeasurementType::Instantaneous,VIFRange::Any,AnyStorageNr,AnyTariffNr,IndexNr(1)
|
||||
#define FIND_FIELD(TYPE,INFO) NoDifVifKey,VifScaling::Auto,TYPE,INFO,StorageNr(0),TariffNr(0),IndexNr(1)
|
||||
#define FIND_FIELD_S(TYPE,INFO,STORAGE) NoDifVifKey,VifScaling::Auto,TYPE,INFO,STORAGE,TariffNr(0),IndexNr(1)
|
||||
#define FIND_FIELD_ST(TYPE,INFO,STORAGE,TARIFF) NoDifVifKey,VifScaling::Auto,,TYPE,INFO,STORAGE,TARIFF,IndexNr(1)
|
||||
#define FIND_FIELD_STI(TYPE,INFO,STORAGE,TARIFF,INDEX) NoDifVifKey,VifScaling::Auto,TYPE,INFO,STORAGE,TARIFF,INDEX
|
||||
|
||||
#define FIND_SFIELD(TYPE,INFO) NoDifVifKey,TYPE,INFO,StorageNr(0),TariffNr(0),IndexNr(1)
|
||||
#define FIND_SFIELD_S(TYPE,INFO,STORAGE) NoDifVifKey,TYPE,INFO,STORAGE,TariffNr(0),IndexNr(1)
|
||||
#define FIND_SFIELD_ST(TYPE,INFO,STORAGE,TARIFF) NoDifVifKey,TYPE,INFO,STORAGE,TARIFF,IndexNr(1)
|
||||
#define FIND_SFIELD_STI(TYPE,INFO,STORAGE,TARIFF,INDEX) NoDifVifKey,TYPE,INFO,STORAGE,TARIFF,INDEX
|
||||
|
||||
void addNumericFieldWithExtractor(
|
||||
string vname, // Name of value without unit, eg "total" "total_month{storagenr}"
|
||||
|
@ -149,14 +127,6 @@ protected:
|
|||
FieldMatcher matcher, // We can generate a calculated field per match.
|
||||
Unit display_unit = Unit::Unknown); // If specified use this unit for the json field instead instead of the default unit.
|
||||
|
||||
void addNumericField(
|
||||
string vname, // Name of value without unit, eg total
|
||||
Quantity vquantity, // Value belongs to this quantity.
|
||||
PrintProperties print_properties, // Should this be printed by default in fields,json and hr.
|
||||
string help,
|
||||
function<void(Unit,double)> setValueFunc, // Use the SET macro above.
|
||||
function<double(Unit)> getValueFunc); // Use the GET macro above.
|
||||
|
||||
void addNumericField(
|
||||
string vname, // Name of value without unit, eg total
|
||||
Quantity vquantity, // Value belongs to this quantity.
|
||||
|
@ -173,21 +143,6 @@ protected:
|
|||
PrintProperties print_properties,
|
||||
FieldMatcher matcher);
|
||||
|
||||
void addStringFieldWithExtractorAndLookup(
|
||||
string vname, // Name of value without unit, eg total
|
||||
Quantity vquantity, // Value belongs to this quantity.
|
||||
DifVifKey dif_vif_key, // You can hardocde a dif vif header here or use NoDifVifKey
|
||||
MeasurementType mt, // If not using a hardcoded key, search for mt,vi,s,t and i instead.
|
||||
VIFRange vi,
|
||||
StorageNr s,
|
||||
TariffNr t,
|
||||
IndexNr i,
|
||||
PrintProperties print_properties, // Should this be printed by default in fields,json and hr.
|
||||
string help,
|
||||
function<void(string)> setValueFunc, // Use the SET_STRING macro above.
|
||||
function<string()> getValueFunc, // Use the GET_STRING macro above.
|
||||
Translate::Lookup lookup); // Translate the bits/indexes.
|
||||
|
||||
void addStringFieldWithExtractorAndLookup(
|
||||
string vname,
|
||||
string help,
|
||||
|
|
Ładowanie…
Reference in New Issue