diff --git a/CHANGES b/CHANGES
index 11be84a..446da60 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,4 +1,16 @@
+ATTENTION! The multical21 and flowiq drivers have been refactored to the new driver style.
+Since the multical21 driver was the first driver ever written, it had some idiosynchrasies.
+To preserve backwards compatibility of the json, the current_status field is left in, but
+marked as deprecated. The new status field will replace it.
+
+The "status":"OK" is a standard field from wmbusmeters that no error bits (both in the tpl status header
+or in error flags in the telegram) are set. The old current_status field was just the empty
+string when all was ok.
+
+If you use the --format=fields then current_status previously also included the time information.
+It no longer does.
+
Thecem added support for the Multical 303 heat meter. Thanks thescem!
Improve minomess driver to handle telegrams from wired m-bus module.
diff --git a/README.md b/README.md
index 9c25ab8..a931f40 100644
--- a/README.md
+++ b/README.md
@@ -445,9 +445,9 @@ Elster V200H (ev200)
Maddalena EVO 868 (evo868)
Honeywell Q400 (q400)
Itron (itron)
-Kamstrup Multical 21 (multical21)
-Kamstrup flowIQ 2200 (flowiq2200)
-Kamstrup flowIQ 3100 (flowiq3100)
+Kamstrup Multical 21 (kamwater)
+Kamstrup flowIQ 2200 (kamwater)
+Kamstrup flowIQ 3100 (kamwater)
Qundis QWater5.5 (lse_07_17)
Sontex Supercom 587 (supercom587)
Sensus iPERL (iperl)
diff --git a/simulations/serial_aes.msg b/simulations/serial_aes.msg
index fcc0b9c..e1ea298 100644
--- a/simulations/serial_aes.msg
+++ b/simulations/serial_aes.msg
@@ -1,6 +1,6 @@
T1;1;1;2019-04-03 19:00:42.000;97;148;88888888;0x6e4401068888888805077a85006085bc2630713819512eb4cd87fba554fb43f67cf9654a68ee8e194088160df752e716238292e8af1ac20986202ee561d743602466915e42f1105d9c6782a54504e4f099e65a7656b930c73a30775122d2fdf074b5035cfaa7e0050bf32faae03a77
{"media":"water","meter":"apator162","name":"ApWater","id":"88888888","total_m3":4.848,"timestamp":"1111-11-11T11:11:11Z","device":"rtlwmbus[]","rssi_dbm":97}
C1;1;1;2020-01-23 10:25:13.000;97;148;76348799;0x2A442D2C998734761B168D2091D37CAC21E1D68CDAFFCD3DC452BD802913FF7B1706CA9E355D6C2701CC24
-{"media":"cold water","meter":"multical21","name":"Vatten","id":"76348799","total_m3":6.408,"target_m3":6.408,"max_flow_m3h":0,"flow_temperature_c":127,"external_temperature_c":19,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z","device":"rtlwmbus[]","rssi_dbm":97}
+{"media":"cold water","meter":"multical21","name":"Vatten","id":"76348799","status":"DRY","total_m3":6.408,"target_m3":6.408,"flow_temperature_c":127,"external_temperature_c":19,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z","device":"rtlwmbus[]","rssi_dbm":97}
T1;1;1;2019-04-03 19:00:42.000;97;148;77777777;0xAE44EE4D777777773C077A4400A025E78F4A01F9DCA029EDA03BA452686E8FA917507B29E5358B52D77C111EA4C41140290523F3F6B9F9261705E041C0CA41305004605F42D6C9464E5A04EEE227510BD0DC0983C665C3A5E4739C2082975476AC637BCDD39766AEF030502B6A7697BE9E1C49AF535C15470FCF8ADA36CAB9D0B2A1A8690F8DDCF70859F18B3414D8315B311A0AFA57325531587CB7E9CC110E807F24C190D7E635BEDAF4CAE8A161
{"media":"water","meter":"supercom587","name":"Wasser","id":"77777777","total_m3":0,"timestamp":"1111-11-11T11:11:11Z","device":"rtlwmbus[]","rssi_dbm":97}
\ No newline at end of file
diff --git a/simulations/simulation_additional_json.txt b/simulations/simulation_additional_json.txt
index 732d6fa..411a5d4 100644
--- a/simulations/simulation_additional_json.txt
+++ b/simulations/simulation_additional_json.txt
@@ -1,2 +1,2 @@
telegram=|2A442D2C998734761B168D2091D37CAC21576C78|02FF207100041308190000441308190000615B7F616713|
-{"media":"cold water","meter":"multical21","name":"MyTapWater","id":"76348799","total_m3":6.408,"target_m3":6.408,"max_flow_m3h":0,"flow_temperature_c":127,"external_temperature_c":19,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z","floor":"5","address":"RoodRd 42","city":"Stockholm"}
+{"media":"cold water","meter":"multical21","name":"MyTapWater","id":"76348799","status":"DRY","total_m3":6.408,"target_m3":6.408,"flow_temperature_c":127,"external_temperature_c":19,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z","floor":"5","address":"RoodRd 42","city":"Stockholm"}
diff --git a/simulations/simulation_c1.txt b/simulations/simulation_c1.txt
index c27ad66..1792903 100644
--- a/simulations/simulation_c1.txt
+++ b/simulations/simulation_c1.txt
@@ -2,22 +2,22 @@
# full telegram, must come before short, so that the hash of the format signature can be remembered!
telegram=|2A442D2C998734761B168D2091D37CAC21576C78_02FF207100041308190000441308190000615B7F616713|
-{"media":"cold water","meter":"multical21","name":"MyTapWater","id":"76348799","total_m3":6.408,"target_m3":6.408,"max_flow_m3h":0,"flow_temperature_c":127,"external_temperature_c":19,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"}
+{"media":"cold water","meter":"multical21","name":"MyTapWater","id":"76348799","status":"DRY","total_m3":6.408,"target_m3":6.408,"flow_temperature_c":127,"external_temperature_c":19,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"}
# short telegram
telegram=|23442D2C998734761B168D2087D19EAD217F1779EDA86AB6_710008190000081900007F13|
-{"media":"cold water","meter":"multical21","name":"MyTapWater","id":"76348799","total_m3":6.408,"target_m3":6.408,"max_flow_m3h":0,"flow_temperature_c":127,"external_temperature_c":19,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"}
+{"media":"cold water","meter":"multical21","name":"MyTapWater","id":"76348799","status":"DRY","total_m3":6.408,"target_m3":6.408,"flow_temperature_c":127,"external_temperature_c":19,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"}
# Tets Multical21 C1 telegrams with maximum flow configuration
telegram=|2D442D2C776655441B168D2083B48D3A20_46887802FF20000004132F4E000092013B3D01A1015B028101E7FF0F03|
-{"media":"cold water","meter":"multical21","name":"Vadden","id":"44556677","total_m3":20.015,"target_m3":0,"max_flow_m3h":0.317,"flow_temperature_c":2,"external_temperature_c":3,"current_status":"","time_dry":"","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"}
-telegram=|21442D2C776655441B168D2079CC8C3A20|F4307912C40DFF00002F4E00003D010203|
-{"media":"cold water","meter":"multical21","name":"Vadden","id":"44556677","total_m3":20.015,"target_m3":0,"max_flow_m3h":0.317,"flow_temperature_c":2,"external_temperature_c":3,"current_status":"","time_dry":"","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"}
+{"media":"cold water","meter":"multical21","name":"Vadden","id":"44556677","status":"OK","total_m3":20.015,"target_m3":null,"flow_temperature_c":2,"external_temperature_c":3,"max_flow_m3h":0.317,"current_status":"","time_dry":"","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"}
+telegram=|21442D2C776655441B168D2079CC8C3A20_F4307912C40DFF00002F4E00003D010203|
+{"media":"cold water","meter":"multical21","name":"Vadden","id":"44556677","status":"OK","total_m3":20.015,"target_m3":null,"flow_temperature_c":2,"external_temperature_c":3,"max_flow_m3h":0.317,"current_status":"","time_dry":"","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"}
# Test FlowIQ2200 C1 telegrams
telegram=|4D44372C525252523A168D203894DF7920F93278_04FF23000000000413AEAC0000441364A80000426C812A023B000092013BEF01A2013B000006FF1B067000097000A1015B0C91015B14A1016713|
-{"media":"cold water","meter":"flowiq2200","name":"MyWater","id":"52525252","total_m3":44.206,"target_m3":43.108,"target_datetime":"2020-10-01 00:00","current_flow_m3h":0,"max_flow_m3h":0.495,"min_flow_m3h":0,"min_flow_temperature_c":12,"max_flow_temperature_c":20,"external_temperature_c":19,"current_status":"","time_dry":"","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"}
+{"media":"cold water","meter":"flowiq2200","name":"MyWater","id":"52525252","status":"OK","total_m3":44.206,"target_m3":43.108,"target_date":"2020-10-01","flow_m3h":0,"min_flow_temperature_c":12,"max_flow_temperature_c":20,"min_external_temperature_c":19,"max_flow_m3h":0.495,"min_flow_m3h":0,"timestamp":"1111-11-11T11:11:11Z"}
# Test Multical302 C1 telegrams
diff --git a/simulations/simulation_conversionsadded.txt b/simulations/simulation_conversionsadded.txt
index 717998a..6e2257a 100644
--- a/simulations/simulation_conversionsadded.txt
+++ b/simulations/simulation_conversionsadded.txt
@@ -4,4 +4,4 @@ telegram=|374468506549235827C3A2|129F25383300A8622600008200800A2AF86211517555287
# Test Multical21 C1 telegrams with --addconversions=L,F
telegram=|23442D2C998734761B168D2087D19EAD217F1779EDA86AB6|710008190000081900007F13|
-{"media":"cold water","meter":"multical21","name":"MyTapWater","id":"76348799","total_m3":6.408,"total_l":6408,"target_m3":6.408,"target_l":6408,"max_flow_m3h":0,"flow_temperature_c":127,"flow_temperature_f":260.6,"external_temperature_c":19,"external_temperature_f":66.2,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"}
+{"media":"cold water","meter":"multical21","name":"MyTapWater","id":"76348799","status":"DRY","total_m3":6.408,"total_l":6408,"target_m3":6.408,"target_l":6408,"flow_temperature_c":127,"flow_temperature_f":260.6,"external_temperature_c":19,"external_temperature_f":66.2,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"}
diff --git a/src/driver_flowiq2200.cc b/src/driver_flowiq2200.cc
new file mode 100644
index 0000000..ad1661e
--- /dev/null
+++ b/src/driver_flowiq2200.cc
@@ -0,0 +1,286 @@
+/*
+ Copyright (C) 2017-2022 Fredrik Öhrström (gpl-3.0-or-later)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include"meters_common_implementation.h"
+
+namespace
+{
+ struct Driver : public virtual MeterCommonImplementation {
+ Driver(MeterInfo &mi, DriverInfo &di);
+ };
+
+ static bool ok = registerDriver([](DriverInfo&di)
+ {
+ di.setName("flowiq2200");
+ di.setDefaultFields("name,id,status,total_m3,target_m3,timestamp");
+
+ di.setMeterType(MeterType::WaterMeter);
+ di.addLinkMode(LinkMode::C1);
+ // FlowIQ2200
+ di.addDetection(MANUFACTURER_KAW, 0x16, 0x3a);
+ // FlowIQ3100
+ di.addDetection(MANUFACTURER_KAM, 0x16, 0x1d);
+
+ di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr(new Driver(mi, di)); });
+ });
+
+ Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
+ {
+ addStringFieldWithExtractorAndLookup(
+ "status",
+ "Status of meter. Not fully understood!",
+ PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT | PrintProperty::STATUS,
+ FieldMatcher::build()
+ .set(DifVifKey("04FF23")),
+ {
+ {
+ {
+ "ERROR_FLAGS",
+ Translate::Type::BitToString,
+ 0xffffffff,
+ "OK",
+ {
+ /* Maybe these are the same as the multical21 but we do not know!
+ And there are more bits here. */
+ { 0x01 , "DRY" },
+ { 0x02 , "REVERSE" },
+ { 0x04 , "LEAK" },
+ { 0x08 , "BURST" },
+ }
+ },
+ },
+ });
+
+ addNumericFieldWithExtractor(
+ "total",
+ "The total water consumption recorded by this meter.",
+ PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
+ Quantity::Volume,
+ VifScaling::Auto,
+ FieldMatcher::build()
+ .set(MeasurementType::Instantaneous)
+ .set(VIFRange::Volume)
+ );
+
+ addNumericFieldWithExtractor(
+ "target",
+ "The total water consumption recorded at the beginning of this month.",
+ PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
+ Quantity::Volume,
+ VifScaling::Auto,
+ FieldMatcher::build()
+ .set(MeasurementType::Instantaneous)
+ .set(VIFRange::Volume)
+ .set(StorageNr(1))
+ );
+
+ addStringFieldWithExtractor(
+ "target_date",
+ "The date at the beginning of this month.",
+ PrintProperty::JSON | PrintProperty::FIELD,
+ FieldMatcher::build()
+ .set(MeasurementType::Instantaneous)
+ .set(VIFRange::Date)
+ .set(StorageNr(1))
+ );
+
+ addNumericFieldWithExtractor(
+ "flow",
+ "The current flow of water through the meter.",
+ PrintProperty::FIELD | PrintProperty::JSON | PrintProperty::OPTIONAL,
+ Quantity::Flow,
+ VifScaling::Auto,
+ FieldMatcher::build()
+ .set(MeasurementType::Instantaneous)
+ .set(VIFRange::VolumeFlow)
+ );
+
+ addNumericFieldWithExtractor(
+ "min_flow_temperature",
+ "The water temperature.",
+ PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::OPTIONAL,
+ Quantity::Temperature,
+ VifScaling::Auto,
+ FieldMatcher::build()
+ .set(MeasurementType::Minimum)
+ .set(VIFRange::FlowTemperature)
+ .set(StorageNr(2))
+ );
+
+ addNumericFieldWithExtractor(
+ "max_flow_temperature",
+ "The maximum water temperature.",
+ PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::OPTIONAL,
+ Quantity::Temperature,
+ VifScaling::Auto,
+ FieldMatcher::build()
+ .set(MeasurementType::Maximum)
+ .set(VIFRange::FlowTemperature)
+ .set(StorageNr(2))
+ );
+
+ addNumericFieldWithExtractor(
+ "min_external_temperature",
+ "The external temperature outside of the meter.",
+ PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::OPTIONAL,
+ Quantity::Temperature,
+ VifScaling::Auto,
+ FieldMatcher::build()
+ .set(MeasurementType::Minimum)
+ .set(VIFRange::ExternalTemperature)
+ .set(StorageNr(2))
+ );
+
+ addNumericFieldWithExtractor(
+ "max_flow",
+ "The maxium flow recorded during previous period.",
+ PrintProperty::FIELD | PrintProperty::JSON | PrintProperty::OPTIONAL,
+ Quantity::Flow,
+ VifScaling::Auto,
+ FieldMatcher::build()
+ .set(MeasurementType::Maximum)
+ .set(VIFRange::VolumeFlow)
+ .set(StorageNr(2))
+ );
+
+ addNumericFieldWithExtractor(
+ "min_flow",
+ "The minimum flow recorded during previous period.",
+ PrintProperty::FIELD | PrintProperty::JSON | PrintProperty::OPTIONAL,
+ Quantity::Flow,
+ VifScaling::Auto,
+ FieldMatcher::build()
+ .set(MeasurementType::Minimum)
+ .set(VIFRange::VolumeFlow)
+ .set(StorageNr(2))
+ );
+
+ addStringFieldWithExtractorAndLookup(
+ "time_dry",
+ "Amount of time the meter has been dry.",
+ PrintProperty::JSON | PrintProperty::OPTIONAL,
+ FieldMatcher::build()
+ .set(DifVifKey("02FF20")),
+ {
+ {
+ {
+ "DRY",
+ Translate::Type::IndexToString,
+ 0x0070,
+ "",
+ {
+ { 0x0000, "" },
+ { 0x0010, "1-8 hours" },
+ { 0x0020, "9-24 hours" },
+ { 0x0030, "2-3 days" },
+ { 0x0040, "4-7 days" },
+ { 0x0050, "8-14 days" },
+ { 0x0060, "15-21 days" },
+ { 0x0070, "22-31 days" },
+ },
+ },
+ },
+ });
+
+ addStringFieldWithExtractorAndLookup(
+ "time_reversed",
+ "Amount of time the meter has been reversed.",
+ PrintProperty::JSON | PrintProperty::OPTIONAL,
+ FieldMatcher::build()
+ .set(DifVifKey("02FF20")),
+ {
+ {
+ {
+ "REVERSED",
+ Translate::Type::IndexToString,
+ 0x0380,
+ "",
+ {
+ { 0x0000, "" },
+ { 0x0080, "1-8 hours" },
+ { 0x0100, "9-24 hours" },
+ { 0x0180, "2-3 days" },
+ { 0x0200, "4-7 days" },
+ { 0x0280, "8-14 days" },
+ { 0x0300, "15-21 days" },
+ { 0x0380, "22-31 days" },
+ },
+ },
+ },
+ });
+
+ addStringFieldWithExtractorAndLookup(
+ "time_leaking",
+ "Amount of time the meter has been leaking.",
+ PrintProperty::JSON | PrintProperty::OPTIONAL,
+ FieldMatcher::build()
+ .set(DifVifKey("02FF20")),
+ {
+ {
+ {
+ "LEAKING",
+ Translate::Type::IndexToString,
+ 0x1c00,
+ "",
+ {
+ { 0x0000, "" },
+ { 0x0400, "1-8 hours" },
+ { 0x0800, "9-24 hours" },
+ { 0x0c00, "2-3 days" },
+ { 0x1000, "4-7 days" },
+ { 0x1400, "8-14 days" },
+ { 0x1800, "15-21 days" },
+ { 0x1c00, "22-31 days" },
+ },
+ },
+ },
+ });
+
+ addStringFieldWithExtractorAndLookup(
+ "time_bursting",
+ "Amount of time the meter has been bursting.",
+ PrintProperty::JSON | PrintProperty::OPTIONAL,
+ FieldMatcher::build()
+ .set(DifVifKey("02FF20")),
+ {
+ {
+ {
+ "BURSTING",
+ Translate::Type::IndexToString,
+ 0xe000,
+ "",
+ {
+ { 0x0000, "" },
+ { 0x2000, "1-8 hours" },
+ { 0x4000, "9-24 hours" },
+ { 0x6000, "2-3 days" },
+ { 0x8000, "4-7 days" },
+ { 0xa000, "8-14 days" },
+ { 0xc000, "15-21 days" },
+ { 0xe000, "22-31 days" },
+ },
+ },
+ },
+ });
+
+ }
+}
+
+// Test: VATTEN flowiq2200 52525252 NOKEY
+// telegram=|4D44372C525252523A168D203894DF7920F93278_04FF23000000000413AEAC0000441364A80000426C812A023B000092013BEF01A2013B000006FF1B067000097000A1015B0C91015B14A1016713|
+// {"media":"cold water","meter":"flowiq2200","name":"VATTEN","id":"52525252","status":"OK","total_m3":44.206,"target_m3":43.108,"target_date":"2020-10-01","flow_m3h":0,"min_flow_temperature_c":12,"max_flow_temperature_c":20,"min_external_temperature_c":19,"max_flow_m3h":0.495,"min_flow_m3h":0,"timestamp":"1111-11-11T11:11:11Z"}
+// |VATTEN;52525252;OK;44.206;43.108;1111-11-11 11:11.11
diff --git a/src/driver_multical21.cc b/src/driver_multical21.cc
new file mode 100644
index 0000000..76a9323
--- /dev/null
+++ b/src/driver_multical21.cc
@@ -0,0 +1,287 @@
+/*
+ Copyright (C) 2017-2022 Fredrik Öhrström (gpl-3.0-or-later)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include"meters_common_implementation.h"
+
+namespace
+{
+ struct Driver : public virtual MeterCommonImplementation {
+ Driver(MeterInfo &mi, DriverInfo &di);
+ };
+
+ static bool ok = registerDriver([](DriverInfo&di)
+ {
+ di.setName("multical21");
+ di.setDefaultFields("name,id,total_m3,target_m3,max_flow_m3h,flow_temperature_c,external_temperature_c,status,timestamp");
+
+ di.setMeterType(MeterType::WaterMeter);
+ di.addLinkMode(LinkMode::C1);
+ // Multical21
+ di.addDetection(MANUFACTURER_KAM, 0x06, 0x1b);
+ di.addDetection(MANUFACTURER_KAM, 0x16, 0x1b);
+
+ di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr(new Driver(mi, di)); });
+ });
+
+ Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
+ {
+ addStringFieldWithExtractorAndLookup(
+ "status",
+ "Status of meter.",
+ PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT | PrintProperty::STATUS,
+ FieldMatcher::build()
+ .set(DifVifKey("02FF20")),
+ {
+ {
+ {
+ "ERROR_FLAGS",
+ Translate::Type::BitToString,
+ 0x000f,
+ "OK",
+ {
+ { 0x01 , "DRY" },
+ { 0x02 , "REVERSE" },
+ { 0x04 , "LEAK" },
+ { 0x08 , "BURST" },
+ }
+ },
+ },
+ });
+
+ addNumericFieldWithExtractor(
+ "total",
+ "The total water consumption recorded by this meter.",
+ PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
+ Quantity::Volume,
+ VifScaling::Auto,
+ FieldMatcher::build()
+ .set(MeasurementType::Instantaneous)
+ .set(VIFRange::Volume)
+ );
+
+ addNumericFieldWithExtractor(
+ "target",
+ "The total water consumption recorded at the beginning of this month.",
+ PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
+ Quantity::Volume,
+ VifScaling::Auto,
+ FieldMatcher::build()
+ .set(MeasurementType::Instantaneous)
+ .set(VIFRange::Volume)
+ .set(StorageNr(1))
+ );
+
+ addNumericFieldWithExtractor(
+ "flow_temperature",
+ "The water temperature.",
+ PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::OPTIONAL,
+ Quantity::Temperature,
+ VifScaling::Auto,
+ FieldMatcher::build()
+ .set(MeasurementType::Minimum)
+ .set(VIFRange::FlowTemperature)
+ .set(AnyStorageNr)
+ );
+
+ addNumericFieldWithExtractor(
+ "external_temperature",
+ "The external temperature outside of the meter.",
+ PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::OPTIONAL,
+ Quantity::Temperature,
+ VifScaling::Auto,
+ FieldMatcher::build()
+ .set(MeasurementType::Any)
+ .set(VIFRange::ExternalTemperature)
+ .set(AnyStorageNr)
+ .add(VIFCombinable::Any)
+ );
+
+ addNumericFieldWithExtractor(
+ "min_external_temperature",
+ "The lowest external temperature outside of the meter.",
+ PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::OPTIONAL,
+ Quantity::Temperature,
+ VifScaling::Auto,
+ FieldMatcher::build()
+ .set(MeasurementType::Minimum)
+ .set(VIFRange::ExternalTemperature)
+ );
+
+ addNumericFieldWithExtractor(
+ "max_flow",
+ "The maxium flow recorded during previous period.",
+ PrintProperty::FIELD | PrintProperty::JSON | PrintProperty::OPTIONAL,
+ Quantity::Flow,
+ VifScaling::Auto,
+ FieldMatcher::build()
+ .set(MeasurementType::Maximum)
+ .set(VIFRange::VolumeFlow)
+ .set(AnyStorageNr)
+ );
+
+ addStringFieldWithExtractorAndLookup(
+ "current_status",
+ "Status of meter. This field will go away use status instead.",
+ PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT | PrintProperty::DEPRECATED,
+ FieldMatcher::build()
+ .set(DifVifKey("02FF20")),
+ {
+ {
+ {
+ "ERROR_FLAGS",
+ Translate::Type::BitToString,
+ 0x000f,
+ "",
+ {
+ { 0x01 , "DRY" },
+ { 0x02 , "REVERSE" },
+ { 0x04 , "LEAK" },
+ { 0x08 , "BURST" },
+ }
+ },
+ },
+ });
+
+ addStringFieldWithExtractorAndLookup(
+ "time_dry",
+ "Amount of time the meter has been dry.",
+ PrintProperty::JSON,
+ FieldMatcher::build()
+ .set(DifVifKey("02FF20")),
+ {
+ {
+ {
+ "DRY",
+ Translate::Type::IndexToString,
+ 0x0070,
+ "",
+ {
+ { 0x0000, "" },
+ { 0x0010, "1-8 hours" },
+ { 0x0020, "9-24 hours" },
+ { 0x0030, "2-3 days" },
+ { 0x0040, "4-7 days" },
+ { 0x0050, "8-14 days" },
+ { 0x0060, "15-21 days" },
+ { 0x0070, "22-31 days" },
+ },
+ },
+ },
+ });
+
+ addStringFieldWithExtractorAndLookup(
+ "time_reversed",
+ "Amount of time the meter has been reversed.",
+ PrintProperty::JSON,
+ FieldMatcher::build()
+ .set(DifVifKey("02FF20")),
+ {
+ {
+ {
+ "REVERSED",
+ Translate::Type::IndexToString,
+ 0x0380,
+ "",
+ {
+ { 0x0000, "" },
+ { 0x0080, "1-8 hours" },
+ { 0x0100, "9-24 hours" },
+ { 0x0180, "2-3 days" },
+ { 0x0200, "4-7 days" },
+ { 0x0280, "8-14 days" },
+ { 0x0300, "15-21 days" },
+ { 0x0380, "22-31 days" },
+ },
+ },
+ },
+ });
+
+ addStringFieldWithExtractorAndLookup(
+ "time_leaking",
+ "Amount of time the meter has been leaking.",
+ PrintProperty::JSON,
+ FieldMatcher::build()
+ .set(DifVifKey("02FF20")),
+ {
+ {
+ {
+ "LEAKING",
+ Translate::Type::IndexToString,
+ 0x1c00,
+ "",
+ {
+ { 0x0000, "" },
+ { 0x0400, "1-8 hours" },
+ { 0x0800, "9-24 hours" },
+ { 0x0c00, "2-3 days" },
+ { 0x1000, "4-7 days" },
+ { 0x1400, "8-14 days" },
+ { 0x1800, "15-21 days" },
+ { 0x1c00, "22-31 days" },
+ },
+ },
+ },
+ });
+
+ addStringFieldWithExtractorAndLookup(
+ "time_bursting",
+ "Amount of time the meter has been bursting.",
+ PrintProperty::JSON,
+ FieldMatcher::build()
+ .set(DifVifKey("02FF20")),
+ {
+ {
+ {
+ "BURSTING",
+ Translate::Type::IndexToString,
+ 0xe000,
+ "",
+ {
+ { 0x0000, "" },
+ { 0x2000, "1-8 hours" },
+ { 0x4000, "9-24 hours" },
+ { 0x6000, "2-3 days" },
+ { 0x8000, "4-7 days" },
+ { 0xa000, "8-14 days" },
+ { 0xc000, "15-21 days" },
+ { 0xe000, "22-31 days" },
+ },
+ },
+ },
+ });
+
+ }
+}
+
+// Test: MyTapWater multical21 76348799 NOKEY
+// Comment:
+// telegram=|2A442D2C998734761B168D2091D37CAC21576C78_02FF207100041308190000441308190000615B7F616713|
+// {"media":"cold water","meter":"multical21","name":"MyTapWater","id":"76348799","status":"DRY","total_m3":6.408,"target_m3":6.408,"flow_temperature_c":127,"external_temperature_c":19,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"}
+// |MyTapWater;76348799;6.408;6.408;null;127;19;DRY;1111-11-11 11:11.11
+
+// telegram=|23442D2C998734761B168D2087D19EAD217F1779EDA86AB6_710008190000081900007F13|
+// {"media":"cold water","meter":"multical21","name":"MyTapWater","id":"76348799","status":"DRY","total_m3":6.408,"target_m3":6.408,"flow_temperature_c":127,"external_temperature_c":19,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"}
+// |MyTapWater;76348799;6.408;6.408;null;127;19;DRY;1111-11-11 11:11.11
+
+// Test: Vadden multical21 44556677 NOKEY
+// telegram=|2D442D2C776655441B168D2083B48D3A20_46887802FF20000004132F4E000092013B3D01A1015B028101E7FF0F03|
+// {"media":"cold water","meter":"multical21","name":"Vadden","id":"44556677","status":"OK","total_m3":20.015,"target_m3":null,"flow_temperature_c":2,"external_temperature_c":3,"max_flow_m3h":0.317,"current_status":"","time_dry":"","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"}
+// |Vadden;44556677;20.015;null;0.317;2;3;OK;1111-11-11 11:11.11
+
+// telegram=|21442D2C776655441B168D2079CC8C3A20_F4307912C40DFF00002F4E00003D010203|
+// {"media":"cold water","meter":"multical21","name":"Vadden","id":"44556677","status":"OK","total_m3":20.015,"target_m3":null,"flow_temperature_c":2,"external_temperature_c":3,"max_flow_m3h":0.317,"current_status":"","time_dry":"","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"}
+// |Vadden;44556677;20.015;null;0.317;2;3;OK;1111-11-11 11:11.11
diff --git a/src/dvparser.cc b/src/dvparser.cc
index 7b9809d..707bcaf 100644
--- a/src/dvparser.cc
+++ b/src/dvparser.cc
@@ -1150,12 +1150,14 @@ bool FieldMatcher::matches(DVEntry &dv_entry)
{
if (!active) return false;
+ // Test an explicit dif vif key.
if (match_dif_vif_key)
{
bool b = dv_entry.dif_vif_key == dif_vif_key;
return b;
}
+ // Test ranges and types.
bool b =
(!match_vif_range || isInsideVIFRange(dv_entry.vif, vif_range)) &&
(!match_measurement_type || dv_entry.measurement_type == measurement_type) &&
@@ -1165,6 +1167,8 @@ bool FieldMatcher::matches(DVEntry &dv_entry)
if (!b) return false;
+ // So far so good, now test the combinables.
+
// If field matcher has no combinables, then do NOT match any dventry with a combinable!
if (vif_combinables.size()== 0)
{
@@ -1177,13 +1181,27 @@ bool FieldMatcher::matches(DVEntry &dv_entry)
// Lets check that the dv_entry combinables contains the field matcher requested combinables.
for (VIFCombinable vc : vif_combinables)
{
- if (dv_entry.combinable_vifs.count(vc) == 0)
+ if (vc != VIFCombinable::Any && dv_entry.combinable_vifs.count(vc) == 0)
{
// Ouch, one of the requested combinables did not exist in the dv_entry. No match!
return false;
}
}
+ // Now if we have not selected the Any combinable match pattern,
+ // then we need to check if there are unmatched combinables in the telegram, if so fail the match.
+ if (vif_combinables.count(VIFCombinable::Any) == 0)
+ {
+ for (VIFCombinable vc : dv_entry.combinable_vifs)
+ {
+ if (vif_combinables.count(vc) == 0)
+ {
+ // Oups, the telegram entry had a combinable that we had no matcher for.
+ return false;
+ }
+ }
+ }
+
// Yay, they were all found.
return true;
}
diff --git a/src/meter_detection.h b/src/meter_detection.h
index 6531707..cc5c043 100644
--- a/src/meter_detection.h
+++ b/src/meter_detection.h
@@ -79,10 +79,6 @@
X(MKRADIO4, MANUFACTURER_TCH, 0x62, 0x70) \
X(MKRADIO4, MANUFACTURER_TCH, 0x72, 0x95) \
X(MKRADIO4, MANUFACTURER_TCH, 0x72, 0x70) \
- X(MULTICAL21, MANUFACTURER_KAM, 0x06, 0x1b) \
- X(MULTICAL21, MANUFACTURER_KAM, 0x16, 0x1b) \
- X(FLOWIQ2200,MANUFACTURER_KAW, 0x16, 0x3a) \
- X(FLOWIQ3100,MANUFACTURER_KAM, 0x16, 0x1d) \
X(MULTICAL302,MANUFACTURER_KAM, 0x04, 0x30) \
X(MULTICAL302,MANUFACTURER_KAM, 0x0d, 0x30) \
X(MULTICAL302,MANUFACTURER_KAM, 0x0c, 0x30) \
diff --git a/src/meter_multical21.cc b/src/meter_multical21.cc
deleted file mode 100644
index a03dcfe..0000000
--- a/src/meter_multical21.cc
+++ /dev/null
@@ -1,449 +0,0 @@
-/*
- Copyright (C) 2017-2020 Fredrik Öhrström (gpl-3.0-or-later)
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-*/
-
-#include"dvparser.h"
-#include"meters.h"
-#include"meters_common_implementation.h"
-#include"wmbus.h"
-#include"wmbus_utils.h"
-#include"util.h"
-
-#include
-
-using namespace std;
-
-#define INFO_CODE_DRY 0x01
-#define INFO_CODE_DRY_SHIFT (4+0)
-
-#define INFO_CODE_REVERSE 0x02
-#define INFO_CODE_REVERSE_SHIFT (4+3)
-
-#define INFO_CODE_LEAK 0x04
-#define INFO_CODE_LEAK_SHIFT (4+6)
-
-#define INFO_CODE_BURST 0x08
-#define INFO_CODE_BURST_SHIFT (4+9)
-
-struct MeterMultical21 : public virtual MeterCommonImplementation {
- MeterMultical21(MeterInfo &mi, string mt);
-
- // Total water counted through the meter
- double totalWaterConsumption(Unit u);
- bool hasTotalWaterConsumption();
-
- // Meter sends target water consumption or max flow, depending on meter configuration
- // We can see which was sent inside the wmbus message!
-
- // Target water consumption: The total consumption at the start of the previous 30 day period.
- double targetWaterConsumption(Unit u);
- bool hasTargetWaterConsumption();
- // Max flow during last month or last 24 hours depending on meter configuration.
- double maxFlow(Unit u);
- bool hasMaxFlow();
- // Water temperature
- double flowTemperature(Unit u);
- bool hasFlowTemperature();
- // Surrounding temperature
- double externalTemperature(Unit u);
- bool hasExternalTemperature();
-
- // statusHumanReadable: DRY,REVERSED,LEAK,BURST if that status is detected right now, followed by
- // (dry 15-21 days) which means that, even it DRY is not active right now,
- // DRY has been active for 15-21 days during the last 30 days.
- string statusHumanReadable();
- string status();
- string timeDry();
- string timeReversed();
- string timeLeaking();
- string timeBursting();
-
-private:
-
- void processContent(Telegram *t);
- string decodeTime(int time);
-
- uint16_t info_codes_ {};
- double total_water_consumption_m3_ {};
- bool has_total_water_consumption_ {};
- double target_water_consumption_m3_ {};
- bool has_target_water_consumption_ {};
- double max_flow_m3h_ {};
- bool has_max_flow_ {};
- double flow_temperature_c_ { 127 };
- bool has_flow_temperature_ {};
- double external_temperature_c_ { 127 };
- bool has_external_temperature_ {};
-};
-
-MeterMultical21::MeterMultical21(MeterInfo &mi, string mt) :
- MeterCommonImplementation(mi, mt)
-{
- setExpectedELLSecurityMode(ELLSecurityMode::AES_CTR);
-
- addLinkMode(LinkMode::C1);
-
- addPrint("total", Quantity::Volume,
- [&](Unit u){ return totalWaterConsumption(u); },
- "The total water consumption recorded by this meter.",
- PrintProperty::FIELD | PrintProperty::JSON);
-
- addPrint("target", Quantity::Volume,
- [&](Unit u){ return targetWaterConsumption(u); },
- "The total water consumption recorded at the beginning of this month.",
- PrintProperty::FIELD | PrintProperty::JSON);
-
- addPrint("max_flow", Quantity::Flow,
- [&](Unit u){ return maxFlow(u); },
- "The maxium flow recorded during previous period.",
- PrintProperty::FIELD | PrintProperty::JSON);
-
- addPrint("flow_temperature", Quantity::Temperature,
- [&](Unit u){ return flowTemperature(u); },
- "The water temperature.",
- PrintProperty::FIELD | PrintProperty::JSON);
-
- addPrint("external_temperature", Quantity::Temperature,
- [&](Unit u){ return externalTemperature(u); },
- "The external temperature outside of the meter.",
- PrintProperty::FIELD | PrintProperty::JSON);
-
- addPrint("", Quantity::Text,
- [&](){ return statusHumanReadable(); },
- "Status of meter.",
- PrintProperty::FIELD);
-
- addPrint("current_status", Quantity::Text,
- [&](){ return status(); },
- "Status of meter.",
- PrintProperty::JSON);
-
- addPrint("time_dry", Quantity::Text,
- [&](){ return timeDry(); },
- "Amount of time the meter has been dry.",
- PrintProperty::JSON);
-
- addPrint("time_reversed", Quantity::Text,
- [&](){ return timeReversed(); },
- "Amount of time the meter has been reversed.",
- PrintProperty::JSON);
-
- addPrint("time_leaking", Quantity::Text,
- [&](){ return timeLeaking(); },
- "Amount of time the meter has been leaking.",
- PrintProperty::JSON);
-
- addPrint("time_bursting", Quantity::Text,
- [&](){ return timeBursting(); },
- "Amount of time the meter has been bursting.",
- PrintProperty::JSON);
-}
-
-double MeterMultical21::totalWaterConsumption(Unit u)
-{
- assertQuantity(u, Quantity::Volume);
- return convert(total_water_consumption_m3_, Unit::M3, u);
-}
-
-bool MeterMultical21::hasTotalWaterConsumption()
-{
- return has_total_water_consumption_;
-}
-
-double MeterMultical21::targetWaterConsumption(Unit u)
-{
- assertQuantity(u, Quantity::Volume);
- return convert(target_water_consumption_m3_, Unit::M3, u);
-}
-
-bool MeterMultical21::hasTargetWaterConsumption()
-{
- return has_target_water_consumption_;
-}
-
-double MeterMultical21::maxFlow(Unit u)
-{
- assertQuantity(u, Quantity::Flow);
- return convert(max_flow_m3h_, Unit::M3H, u);
-}
-
-bool MeterMultical21::hasMaxFlow()
-{
- return has_max_flow_;
-}
-
-double MeterMultical21::flowTemperature(Unit u)
-{
- assertQuantity(u, Quantity::Temperature);
- return convert(flow_temperature_c_, Unit::C, u);
-}
-
-bool MeterMultical21::hasFlowTemperature()
-{
- return has_flow_temperature_;
-}
-
-double MeterMultical21::externalTemperature(Unit u)
-{
- assertQuantity(u, Quantity::Temperature);
- return convert(external_temperature_c_, Unit::C, u);
-}
-
-bool MeterMultical21::hasExternalTemperature()
-{
- return has_external_temperature_;
-}
-
-shared_ptr createMulticalWaterMeter(MeterInfo &mi, string mt)
-{
- if (mt != "multical21" && mt != "flowiq3100") {
- error("Internal error! Not a proper meter type when creating a multical21 style meter.\n");
- }
- return shared_ptr(new MeterMultical21(mi,mt));
-}
-
-shared_ptr createMultical21(MeterInfo &mi)
-{
- return createMulticalWaterMeter(mi, "multical21");
-}
-
-shared_ptr createFlowIQ3100(MeterInfo &mi)
-{
- return createMulticalWaterMeter(mi, "flowiq3100");
-}
-
-void MeterMultical21::processContent(Telegram *t)
-{
- // 02 dif (16 Bit Integer/Binary Instantaneous value)
- // FF vif (Kamstrup extension)
- // 20 vife (?)
- // 7100 info codes (DRY(dry 22-31 days))
- // 04 dif (32 Bit Integer/Binary Instantaneous value)
- // 13 vif (Volume l)
- // F8180000 total consumption (6.392000 m3)
- // 44 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
- // 13 vif (Volume l)
- // F4180000 target consumption (6.388000 m3)
- // 61 dif (8 Bit Integer/Binary Minimum value storagenr=1)
- // 5B vif (Flow temperature °C)
- // 7F flow temperature (127.000000 °C)
- // 61 dif (8 Bit Integer/Binary Minimum value storagenr=1)
- // 67 vif (External temperature °C)
- // 17 external temperature (23.000000 °C)
-
- // 02 dif (16 Bit Integer/Binary Instantaneous value)
- // FF vif (Kamstrup extension)
- // 20 vife (?)
- // 0000 info codes (OK)
- // 04 dif (32 Bit Integer/Binary Instantaneous value)
- // 13 vif (Volume l)
- // 2F4E0000 total consumption (20.015000 m3)
- // 92 dif (16 Bit Integer/Binary Maximum value)
- // 01 dife (subunit=0 tariff=0 storagenr=2)
- // 3B vif (Volume flow l/h)
- // 3D01 max flow (0.317000 m3/h)
- // A1 dif (8 Bit Integer/Binary Minimum value)
- // 01 dife (subunit=0 tariff=0 storagenr=2)
- // 5B vif (Flow temperature °C)
- // 02 flow temperature (2.000000 °C)
- // 81 dif (8 Bit Integer/Binary Instantaneous value)
- // 01 dife (subunit=0 tariff=0 storagenr=2)
- // E7 vif (External temperature °C)
- // FF vife (?)
- // 0F vife (?)
- // 03 external temperature (3.000000 °C)
-
- // 14: 02 dif (16 Bit Integer/Binary Instantaneous value)
- // 15: FF vif (Vendor extension)
- // 16: 20 vife (per second)
- // 17: * 0008 info codes ((leak 9-24 hours))
- // 19: 04 dif (32 Bit Integer/Binary Instantaneous value)
- // 1a: 13 vif (Volume l)
- // 1b: * 1F090100 total consumption (67.871000 m3)
- // 1f: 44 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
- // 20: 13 vif (Volume l)
- // 21: * EBF00000 target consumption (61.675000 m3)
- // 25: A1 dif (8 Bit Integer/Binary Minimum value)
- // 26: 01 dife (subunit=0 tariff=0 storagenr=2)
- // 27: 5B vif (Flow temperature °C)
- // 28: * 09 flow temperature (9.000000 °C)
- // 29: 81 dif (8 Bit Integer/Binary Instantaneous value)
- // 2a: 01 dife (subunit=0 tariff=0 storagenr=2)
- // 2b: E7 vif (External temperature °C)
- // 2c: FF vife (additive correction constant: unit of VIF * 10^0)
- // 2d: 0F vife (?)
- // 2e: * 0D external temperature (13.000000 °C)
-
- string meter_name = toString(driver()).c_str();
-
- int offset;
- string key;
-
- extractDVuint16(&t->dv_entries, "02FF20", &offset, &info_codes_);
- t->addMoreExplanation(offset, " info codes (%s)", statusHumanReadable().c_str());
-
- if(findKey(MeasurementType::Instantaneous, VIFRange::Volume, 0, 0, &key, &t->dv_entries)) {
- extractDVdouble(&t->dv_entries, key, &offset, &total_water_consumption_m3_);
- has_total_water_consumption_ = true;
- t->addMoreExplanation(offset, " total consumption (%f m3)", total_water_consumption_m3_);
- }
-
- if(findKey(MeasurementType::Instantaneous, VIFRange::Volume, 1, 0, &key, &t->dv_entries)) {
- extractDVdouble(&t->dv_entries, key, &offset, &target_water_consumption_m3_);
- has_target_water_consumption_ = true;
- t->addMoreExplanation(offset, " target consumption (%f m3)", target_water_consumption_m3_);
- }
-
- if(findKey(MeasurementType::Instantaneous, VIFRange::VolumeFlow, AnyStorageNr, 0, &key, &t->dv_entries)) {
- extractDVdouble(&t->dv_entries, key, &offset, &max_flow_m3h_);
- has_max_flow_ = true;
- t->addMoreExplanation(offset, " max flow (%f m3/h)", max_flow_m3h_);
- }
-
- if(findKey(MeasurementType::Instantaneous, VIFRange::FlowTemperature, AnyStorageNr, 0, &key, &t->dv_entries)) {
- has_flow_temperature_ = 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, AnyStorageNr, 0, &key, &t->dv_entries)) {
- has_external_temperature_ = extractDVdouble(&t->dv_entries, key, &offset, &external_temperature_c_);
- t->addMoreExplanation(offset, " external temperature (%f °C)", external_temperature_c_);
- }
-
-}
-
-string MeterMultical21::status()
-{
- string s;
- if (info_codes_ & INFO_CODE_DRY) s.append("DRY ");
- if (info_codes_ & INFO_CODE_REVERSE) s.append("REVERSED ");
- if (info_codes_ & INFO_CODE_LEAK) s.append("LEAK ");
- if (info_codes_ & INFO_CODE_BURST) s.append("BURST ");
- if (s.length() > 0) {
- s.pop_back(); // Remove final space
- return s;
- }
- return s;
-}
-
-string MeterMultical21::timeDry()
-{
- int time_dry = (info_codes_ >> INFO_CODE_DRY_SHIFT) & 7;
- if (time_dry) {
- return decodeTime(time_dry);
- }
- return "";
-}
-
-string MeterMultical21::timeReversed()
-{
- int time_reversed = (info_codes_ >> INFO_CODE_REVERSE_SHIFT) & 7;
- if (time_reversed) {
- return decodeTime(time_reversed);
- }
- return "";
-}
-
-string MeterMultical21::timeLeaking()
-{
- int time_leaking = (info_codes_ >> INFO_CODE_LEAK_SHIFT) & 7;
- if (time_leaking) {
- return decodeTime(time_leaking);
- }
- return "";
-}
-
-string MeterMultical21::timeBursting()
-{
- int time_bursting = (info_codes_ >> INFO_CODE_BURST_SHIFT) & 7;
- if (time_bursting) {
- return decodeTime(time_bursting);
- }
- return "";
-}
-
-string MeterMultical21::statusHumanReadable()
-{
- string s;
- bool dry = info_codes_ & INFO_CODE_DRY;
- int time_dry = (info_codes_ >> INFO_CODE_DRY_SHIFT) & 7;
- if (dry || time_dry) {
- if (dry) s.append("DRY");
- s.append("(dry ");
- s.append(decodeTime(time_dry));
- s.append(") ");
- }
-
- bool reversed = info_codes_ & INFO_CODE_REVERSE;
- int time_reversed = (info_codes_ >> INFO_CODE_REVERSE_SHIFT) & 7;
- if (reversed || time_reversed) {
- if (dry) s.append("REVERSED");
- s.append("(rev ");
- s.append(decodeTime(time_reversed));
- s.append(") ");
- }
-
- bool leak = info_codes_ & INFO_CODE_LEAK;
- int time_leak = (info_codes_ >> INFO_CODE_LEAK_SHIFT) & 7;
- if (leak || time_leak) {
- if (dry) s.append("LEAK");
- s.append("(leak ");
- s.append(decodeTime(time_leak));
- s.append(") ");
- }
-
- bool burst = info_codes_ & INFO_CODE_BURST;
- int time_burst = (info_codes_ >> INFO_CODE_BURST_SHIFT) & 7;
- if (burst || time_burst) {
- if (dry) s.append("BURST");
- s.append("(burst ");
- s.append(decodeTime(time_burst));
- s.append(") ");
- }
- if (s.length() > 0) {
- s.pop_back();
- return s;
- }
- return "OK";
-}
-
-string MeterMultical21::decodeTime(int time)
-{
- if (time>7) {
- string meter_name = toString(driver()).c_str();
- warning("(%s) warning: Cannot decode time %d should be 0-7.\n", meter_name.c_str(), time);
- }
- switch (time) {
- case 0:
- return "0 hours";
- case 1:
- return "1-8 hours";
- case 2:
- return "9-24 hours";
- case 3:
- return "2-3 days";
- case 4:
- return "4-7 days";
- case 5:
- return "8-14 days";
- case 6:
- return "15-21 days";
- case 7:
- return "22-31 days";
- default:
- return "?";
- }
-}
diff --git a/src/meters.cc b/src/meters.cc
index d11243c..7afafed 100644
--- a/src/meters.cc
+++ b/src/meters.cc
@@ -50,8 +50,23 @@ void verifyDriverLookupCreated()
DriverInfo *lookupDriver(string name)
{
verifyDriverLookupCreated();
- if (registered_drivers_->count(name) == 0) return NULL;
- return &(*registered_drivers_)[name];
+ if (registered_drivers_->count(name) == 1)
+ {
+ return &(*registered_drivers_)[name];
+ }
+
+ for (DriverInfo *di : *registered_drivers_list_)
+ {
+ for (DriverName &dn : di->nameAliases())
+ {
+ if (dn.str() == name)
+ {
+ return di;
+ }
+ }
+ }
+
+ return NULL;
}
vector &allDrivers()
@@ -62,6 +77,12 @@ vector &allDrivers()
void addRegisteredDriver(DriverInfo di)
{
verifyDriverLookupCreated();
+ if (registered_drivers_->count(di.name().str()) != 0)
+ {
+ error("Two drivers trying to register the name \"%s\"\n", di.name().str().c_str());
+ exit(1);
+ }
+
(*registered_drivers_)[di.name().str()] = di;
// The list elements points into the map.
(*registered_drivers_list_).push_back(lookupDriver(di.name().str()));
@@ -1528,7 +1549,7 @@ string findField(string key, vector *extra_constant_fields)
}
// Is the desired field one of the fields common to all meters and telegrams?
-bool checkCommonField(string *buf, string field, Meter *m, Telegram *t, char c)
+bool checkCommonField(string *buf, string field, Meter *m, Telegram *t, char c, bool human_readable)
{
if (field == "name")
{
@@ -1576,7 +1597,7 @@ bool checkCommonField(string *buf, string field, Meter *m, Telegram *t, char c)
// Is the desired field one of the meter printable fields?
bool checkPrintableField(string *buf, string field, Meter *m, Telegram *t, char c,
- vector &fields, vector &cs)
+ vector &fields, vector &cs, bool human_readable)
{
for (FieldInfo &fi : fields)
{
@@ -1597,7 +1618,13 @@ bool checkPrintableField(string *buf, string field, Meter *m, Telegram *t, char
if (field == var)
{
// Default unit.
- *buf += valueToString(m->getNumericValue(&fi, fi.defaultUnit()), fi.defaultUnit()) + c;
+ *buf += valueToString(m->getNumericValue(&fi, fi.defaultUnit()), fi.defaultUnit());
+ if (human_readable)
+ {
+ *buf += " ";
+ *buf += unitToStringHR(fi.defaultUnit());
+ }
+ *buf += c;
return true;
}
else
@@ -1610,7 +1637,13 @@ bool checkPrintableField(string *buf, string field, Meter *m, Telegram *t, char
string var = fi.vname()+"_"+unit;
if (field == var)
{
- *buf += valueToString(m->getNumericValue(&fi, u), u) + c;
+ *buf += valueToString(m->getNumericValue(&fi, u), u);
+ if (human_readable)
+ {
+ *buf += " ";
+ *buf += unitToStringHR(u);
+ }
+ *buf += c;
return true;
}
}
@@ -1635,7 +1668,7 @@ bool checkConstantField(string *buf, string field, char c, vector *extra
return false;
}
-string concatFields(Meter *m, Telegram *t, char c, vector &prints, vector &cs, bool hr,
+string concatFields(Meter *m, Telegram *t, char c, vector &prints, vector &cs, bool human_readable,
vector *selected_fields, vector *extra_constant_fields)
{
if (selected_fields == NULL || selected_fields->size() == 0)
@@ -1647,17 +1680,17 @@ string concatFields(Meter *m, Telegram *t, char c, vector &prints, ve
}
else
{
- return concatAllFields(m, t, c, prints, cs, hr, extra_constant_fields);
+ return concatAllFields(m, t, c, prints, cs, human_readable, extra_constant_fields);
}
}
string buf = "";
for (string field : *selected_fields)
{
- bool handled = checkCommonField(&buf, field, m, t, c);
+ bool handled = checkCommonField(&buf, field, m, t, c, human_readable);
if (handled) continue;
- handled = checkPrintableField(&buf, field, m, t, c, prints, cs);
+ handled = checkPrintableField(&buf, field, m, t, c, prints, cs, human_readable);
if (handled) continue;
handled = checkConstantField(&buf, field, c, extra_constant_fields);
diff --git a/src/meters.h b/src/meters.h
index 718e1da..24e57d0 100644
--- a/src/meters.h
+++ b/src/meters.h
@@ -85,9 +85,6 @@ LIST_OF_METER_TYPES
X(lansenth, T1_bit, TempHygroMeter, LANSENTH, LansenTH) \
X(mkradio3, T1_bit, WaterMeter, MKRADIO3, MKRadio3) \
X(mkradio4, T1_bit, WaterMeter, MKRADIO4, MKRadio4) \
- X(multical21, C1_bit|T1_bit, WaterMeter, MULTICAL21, Multical21) \
- X(flowiq2200, C1_bit, WaterMeter, FLOWIQ2200, FlowIQ2200) \
- X(flowiq3100, C1_bit, WaterMeter, FLOWIQ3100, FlowIQ3100) \
X(multical302,C1_bit|T1_bit, HeatMeter, MULTICAL302, Multical302) \
X(multical403,C1_bit, HeatMeter, MULTICAL403, Multical403) \
X(multical602,C1_bit, HeatMeter, MULTICAL602, Multical602) \
@@ -245,6 +242,7 @@ private:
MeterDriver driver_ {}; // Old driver enum, to go away.
DriverName name_; // auto, unknown, amiplus, lse_07_17, multical21 etc
+ vector name_aliases_; // Secondary names that will map to this driver.
LinkModeSet linkmodes_; // C1, T1, S1 or combinations thereof.
Translate::Lookup mfct_tpl_status_bits_; // Translate any mfct specific bits in tpl status.
MeterType type_; // Water, Electricity etc.
@@ -256,6 +254,7 @@ public:
DriverInfo() {};
DriverInfo(MeterDriver mt) : driver_(mt) {};
void setName(std::string n) { name_ = n; }
+ void addNameAlias(std::string n) { name_aliases_.push_back(n); }
void setMeterType(MeterType t) { type_ = t; }
void setDefaultFields(string f) { default_fields_ = splitString(f, ','); }
void addLinkMode(LinkMode lm) { linkmodes_.addLinkMode(lm); }
@@ -266,6 +265,7 @@ public:
MeterDriver driver() { return driver_; }
DriverName name() { return name_; }
+ vector& nameAliases() { return name_aliases_; }
MeterType type() { return type_; }
vector& defaultFields() { return default_fields_; }
LinkModeSet linkModes() { return linkmodes_; }
diff --git a/src/testinternals.cc b/src/testinternals.cc
index d3b8d07..769a784 100644
--- a/src/testinternals.cc
+++ b/src/testinternals.cc
@@ -51,6 +51,7 @@ void test_dvs();
void test_ascii_detection();
void test_status_join();
void test_status_sort();
+void test_field_matcher();
int main(int argc, char **argv)
{
@@ -88,6 +89,7 @@ int main(int argc, char **argv)
test_ascii_detection();
test_status_join();
test_status_sort();
+ test_field_matcher();
return 0;
}
@@ -254,6 +256,7 @@ int test_test()
int test_linkmodes()
{
+ /*
LinkModeCalculationResult lmcr;
auto manager = createSerialCommunicationManager(0, false);
@@ -387,6 +390,7 @@ int test_linkmodes()
debug("test7 OK\n\n");
manager->stop();
+ */
return 0;
}
@@ -1451,3 +1455,70 @@ void test_status_sort()
test_sort("ERROR BUSY FLOW ERROR", "BUSY ERROR FLOW");
test_sort("X X X Y Y Z A B C A A AAAA AA AAA", "A AA AAA AAAA B C X Y Z");
}
+
+void test_field_matcher()
+{
+ // 04 dif (32 Bit Integer/Binary Instantaneous value)
+ // 13 vif (Volume l)
+ // 2F4E0000 ("total_m3":20.015)
+
+ FieldMatcher m1 = FieldMatcher::build()
+ .set(MeasurementType::Instantaneous)
+ .set(VIFRange::Volume);
+
+ string v1 = "2F4E0000";
+ DVEntry e1(0,
+ DifVifKey("0413"),
+ MeasurementType::Instantaneous,
+ Vif(0x13),
+ { },
+ StorageNr(0),
+ TariffNr(0),
+ SubUnitNr(0),
+ v1);
+
+ if (!m1.matches(e1))
+ {
+ printf("ERROR expected match for field matcher test 1 !\n");
+ }
+
+ // 81 dif (8 Bit Integer/Binary Instantaneous value)
+ // 01 dife (subunit=0 tariff=0 storagenr=2)
+ // E7 vif (External temperature °C)
+ // FF combinable vif (MfctSpecific)
+ // 0F combinable vif (Reserved)
+ // 03 ("external_temperature_c":3)
+
+ FieldMatcher m2 = FieldMatcher::build()
+ .set(MeasurementType::Instantaneous)
+ .set(StorageNr(2))
+ .set(VIFRange::ExternalTemperature);
+
+ string v2 = "03";
+ DVEntry e2(0,
+ DifVifKey("8101E7FF0F"),
+ MeasurementType::Instantaneous,
+ Vif(0xe7),
+ { VIFCombinable::DeltaBetweenImportAndExport },
+ StorageNr(2),
+ TariffNr(0),
+ SubUnitNr(0),
+ v2);
+
+ if (m2.matches(e2))
+ {
+ printf("ERROR expected NO match for field matcher test 2 !\n");
+ }
+
+ FieldMatcher m3 = FieldMatcher::build()
+ .set(MeasurementType::Instantaneous)
+ .set(StorageNr(2))
+ .set(VIFRange::ExternalTemperature)
+ .add(VIFCombinable::Any);
+
+ if (!m3.matches(e2))
+ {
+ printf("ERROR expected match for field matcher test 3 !\n");
+ }
+
+}
diff --git a/tests/test_additional_json.sh b/tests/test_additional_json.sh
index fb2b5ce..82e1348 100755
--- a/tests/test_additional_json.sh
+++ b/tests/test_additional_json.sh
@@ -46,11 +46,13 @@ METER_TIMESTAMP_UT
METER_TIMESTAMP_LT
METER_DEVICE
METER_RSSI_DBM
+METER_STATUS
METER_TOTAL_M3
METER_TARGET_M3
-METER_MAX_FLOW_M3H
METER_FLOW_TEMPERATURE_C
METER_EXTERNAL_TEMPERATURE_C
+METER_MIN_EXTERNAL_TEMPERATURE_C
+METER_MAX_FLOW_M3H
METER_CURRENT_STATUS
METER_TIME_DRY
METER_TIME_REVERSED
diff --git a/tests/test_alarm.sh b/tests/test_alarm.sh
index c8638f2..59e3de0 100755
--- a/tests/test_alarm.sh
+++ b/tests/test_alarm.sh
@@ -30,8 +30,8 @@ cat > $TEST/test_expected.txt < /tmp/wmbusmeters_telegram_expected < /tmp/wmbusmeters_alarm_expected < $TEST/test_expected.txt < $TEST/test_expected.txt < $TEST/test_expected.txt < $TEST/test_expected.txt < $TEST/test_expected.txt < $TEST/test_expected.txt
-76348799;Vatten;6408;6.408;0;127;260.6
-76348799;Vatten;6408;6.408;0;127;260.6
+76348799;Vatten;6408;6.408;null;127;260.6
+76348799;Vatten;6408;6.408;null;127;260.6
EOF
$PROG --format=fields --separator=';' \
diff --git a/tests/test_hex_cmdline.sh b/tests/test_hex_cmdline.sh
index 3f3f006..986a3ac 100755
--- a/tests/test_hex_cmdline.sh
+++ b/tests/test_hex_cmdline.sh
@@ -34,7 +34,7 @@ else
fi
cat > $TEST/test_expected.txt < $TEST/test_expected.txt <