kopia lustrzana https://github.com/weetmuts/wmbusmeters
Refactor multical21 and flowiq drivers.
rodzic
44c0ba1e74
commit
84fe29069a
12
CHANGES
12
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!
|
Thecem added support for the Multical 303 heat meter. Thanks thescem!
|
||||||
|
|
||||||
Improve minomess driver to handle telegrams from wired m-bus module.
|
Improve minomess driver to handle telegrams from wired m-bus module.
|
||||||
|
|
|
@ -445,9 +445,9 @@ Elster V200H (ev200)
|
||||||
Maddalena EVO 868 (evo868)
|
Maddalena EVO 868 (evo868)
|
||||||
Honeywell Q400 (q400)
|
Honeywell Q400 (q400)
|
||||||
Itron (itron)
|
Itron (itron)
|
||||||
Kamstrup Multical 21 (multical21)
|
Kamstrup Multical 21 (kamwater)
|
||||||
Kamstrup flowIQ 2200 (flowiq2200)
|
Kamstrup flowIQ 2200 (kamwater)
|
||||||
Kamstrup flowIQ 3100 (flowiq3100)
|
Kamstrup flowIQ 3100 (kamwater)
|
||||||
Qundis QWater5.5 (lse_07_17)
|
Qundis QWater5.5 (lse_07_17)
|
||||||
Sontex Supercom 587 (supercom587)
|
Sontex Supercom 587 (supercom587)
|
||||||
Sensus iPERL (iperl)
|
Sensus iPERL (iperl)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
T1;1;1;2019-04-03 19:00:42.000;97;148;88888888;0x6e4401068888888805077a85006085bc2630713819512eb4cd87fba554fb43f67cf9654a68ee8e194088160df752e716238292e8af1ac20986202ee561d743602466915e42f1105d9c6782a54504e4f099e65a7656b930c73a30775122d2fdf074b5035cfaa7e0050bf32faae03a77
|
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}
|
{"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
|
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
|
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}
|
{"media":"water","meter":"supercom587","name":"Wasser","id":"77777777","total_m3":0,"timestamp":"1111-11-11T11:11:11Z","device":"rtlwmbus[]","rssi_dbm":97}
|
|
@ -1,2 +1,2 @@
|
||||||
telegram=|2A442D2C998734761B168D2091D37CAC21576C78|02FF207100041308190000441308190000615B7F616713|
|
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"}
|
||||||
|
|
|
@ -2,22 +2,22 @@
|
||||||
|
|
||||||
# full telegram, must come before short, so that the hash of the format signature can be remembered!
|
# full telegram, must come before short, so that the hash of the format signature can be remembered!
|
||||||
telegram=|2A442D2C998734761B168D2091D37CAC21576C78_02FF207100041308190000441308190000615B7F616713|
|
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
|
# short telegram
|
||||||
telegram=|23442D2C998734761B168D2087D19EAD217F1779EDA86AB6_710008190000081900007F13|
|
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
|
# Tets Multical21 C1 telegrams with maximum flow configuration
|
||||||
telegram=|2D442D2C776655441B168D2083B48D3A20_46887802FF20000004132F4E000092013B3D01A1015B028101E7FF0F03|
|
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"}
|
{"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|
|
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"}
|
||||||
|
|
||||||
# Test FlowIQ2200 C1 telegrams
|
# Test FlowIQ2200 C1 telegrams
|
||||||
|
|
||||||
telegram=|4D44372C525252523A168D203894DF7920F93278_04FF23000000000413AEAC0000441364A80000426C812A023B000092013BEF01A2013B000006FF1B067000097000A1015B0C91015B14A1016713|
|
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
|
# Test Multical302 C1 telegrams
|
||||||
|
|
||||||
|
|
|
@ -4,4 +4,4 @@ telegram=|374468506549235827C3A2|129F25383300A8622600008200800A2AF86211517555287
|
||||||
|
|
||||||
# Test Multical21 C1 telegrams with --addconversions=L,F
|
# Test Multical21 C1 telegrams with --addconversions=L,F
|
||||||
telegram=|23442D2C998734761B168D2087D19EAD217F1779EDA86AB6|710008190000081900007F13|
|
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"}
|
||||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include"meters_common_implementation.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
struct Driver : public virtual MeterCommonImplementation {
|
||||||
|
Driver(MeterInfo &mi, DriverInfo &di);
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool ok = registerDriver([](DriverInfo&di)
|
||||||
|
{
|
||||||
|
di.setName("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<Meter>(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
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include"meters_common_implementation.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
struct Driver : public virtual MeterCommonImplementation {
|
||||||
|
Driver(MeterInfo &mi, DriverInfo &di);
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool ok = registerDriver([](DriverInfo&di)
|
||||||
|
{
|
||||||
|
di.setName("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<Meter>(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
|
|
@ -1150,12 +1150,14 @@ bool FieldMatcher::matches(DVEntry &dv_entry)
|
||||||
{
|
{
|
||||||
if (!active) return false;
|
if (!active) return false;
|
||||||
|
|
||||||
|
// Test an explicit dif vif key.
|
||||||
if (match_dif_vif_key)
|
if (match_dif_vif_key)
|
||||||
{
|
{
|
||||||
bool b = dv_entry.dif_vif_key == dif_vif_key;
|
bool b = dv_entry.dif_vif_key == dif_vif_key;
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test ranges and types.
|
||||||
bool b =
|
bool b =
|
||||||
(!match_vif_range || isInsideVIFRange(dv_entry.vif, vif_range)) &&
|
(!match_vif_range || isInsideVIFRange(dv_entry.vif, vif_range)) &&
|
||||||
(!match_measurement_type || dv_entry.measurement_type == measurement_type) &&
|
(!match_measurement_type || dv_entry.measurement_type == measurement_type) &&
|
||||||
|
@ -1165,6 +1167,8 @@ bool FieldMatcher::matches(DVEntry &dv_entry)
|
||||||
|
|
||||||
if (!b) return false;
|
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 field matcher has no combinables, then do NOT match any dventry with a combinable!
|
||||||
if (vif_combinables.size()== 0)
|
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.
|
// Lets check that the dv_entry combinables contains the field matcher requested combinables.
|
||||||
for (VIFCombinable vc : vif_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!
|
// Ouch, one of the requested combinables did not exist in the dv_entry. No match!
|
||||||
return false;
|
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.
|
// Yay, they were all found.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,10 +79,6 @@
|
||||||
X(MKRADIO4, MANUFACTURER_TCH, 0x62, 0x70) \
|
X(MKRADIO4, MANUFACTURER_TCH, 0x62, 0x70) \
|
||||||
X(MKRADIO4, MANUFACTURER_TCH, 0x72, 0x95) \
|
X(MKRADIO4, MANUFACTURER_TCH, 0x72, 0x95) \
|
||||||
X(MKRADIO4, MANUFACTURER_TCH, 0x72, 0x70) \
|
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, 0x04, 0x30) \
|
||||||
X(MULTICAL302,MANUFACTURER_KAM, 0x0d, 0x30) \
|
X(MULTICAL302,MANUFACTURER_KAM, 0x0d, 0x30) \
|
||||||
X(MULTICAL302,MANUFACTURER_KAM, 0x0c, 0x30) \
|
X(MULTICAL302,MANUFACTURER_KAM, 0x0c, 0x30) \
|
||||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include"dvparser.h"
|
|
||||||
#include"meters.h"
|
|
||||||
#include"meters_common_implementation.h"
|
|
||||||
#include"wmbus.h"
|
|
||||||
#include"wmbus_utils.h"
|
|
||||||
#include"util.h"
|
|
||||||
|
|
||||||
#include<assert.h>
|
|
||||||
|
|
||||||
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<Meter> 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<Meter>(new MeterMultical21(mi,mt));
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_ptr<Meter> createMultical21(MeterInfo &mi)
|
|
||||||
{
|
|
||||||
return createMulticalWaterMeter(mi, "multical21");
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_ptr<Meter> 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 "?";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -50,8 +50,23 @@ void verifyDriverLookupCreated()
|
||||||
DriverInfo *lookupDriver(string name)
|
DriverInfo *lookupDriver(string name)
|
||||||
{
|
{
|
||||||
verifyDriverLookupCreated();
|
verifyDriverLookupCreated();
|
||||||
if (registered_drivers_->count(name) == 0) return NULL;
|
if (registered_drivers_->count(name) == 1)
|
||||||
return &(*registered_drivers_)[name];
|
{
|
||||||
|
return &(*registered_drivers_)[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (DriverInfo *di : *registered_drivers_list_)
|
||||||
|
{
|
||||||
|
for (DriverName &dn : di->nameAliases())
|
||||||
|
{
|
||||||
|
if (dn.str() == name)
|
||||||
|
{
|
||||||
|
return di;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<DriverInfo*> &allDrivers()
|
vector<DriverInfo*> &allDrivers()
|
||||||
|
@ -62,6 +77,12 @@ vector<DriverInfo*> &allDrivers()
|
||||||
void addRegisteredDriver(DriverInfo di)
|
void addRegisteredDriver(DriverInfo di)
|
||||||
{
|
{
|
||||||
verifyDriverLookupCreated();
|
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;
|
(*registered_drivers_)[di.name().str()] = di;
|
||||||
// The list elements points into the map.
|
// The list elements points into the map.
|
||||||
(*registered_drivers_list_).push_back(lookupDriver(di.name().str()));
|
(*registered_drivers_list_).push_back(lookupDriver(di.name().str()));
|
||||||
|
@ -1528,7 +1549,7 @@ string findField(string key, vector<string> *extra_constant_fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is the desired field one of the fields common to all meters and telegrams?
|
// 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")
|
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?
|
// Is the desired field one of the meter printable fields?
|
||||||
bool checkPrintableField(string *buf, string field, Meter *m, Telegram *t, char c,
|
bool checkPrintableField(string *buf, string field, Meter *m, Telegram *t, char c,
|
||||||
vector<FieldInfo> &fields, vector<Unit> &cs)
|
vector<FieldInfo> &fields, vector<Unit> &cs, bool human_readable)
|
||||||
{
|
{
|
||||||
for (FieldInfo &fi : fields)
|
for (FieldInfo &fi : fields)
|
||||||
{
|
{
|
||||||
|
@ -1597,7 +1618,13 @@ bool checkPrintableField(string *buf, string field, Meter *m, Telegram *t, char
|
||||||
if (field == var)
|
if (field == var)
|
||||||
{
|
{
|
||||||
// Default unit.
|
// 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;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1610,7 +1637,13 @@ bool checkPrintableField(string *buf, string field, Meter *m, Telegram *t, char
|
||||||
string var = fi.vname()+"_"+unit;
|
string var = fi.vname()+"_"+unit;
|
||||||
if (field == var)
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1635,7 +1668,7 @@ bool checkConstantField(string *buf, string field, char c, vector<string> *extra
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
string concatFields(Meter *m, Telegram *t, char c, vector<FieldInfo> &prints, vector<Unit> &cs, bool hr,
|
string concatFields(Meter *m, Telegram *t, char c, vector<FieldInfo> &prints, vector<Unit> &cs, bool human_readable,
|
||||||
vector<string> *selected_fields, vector<string> *extra_constant_fields)
|
vector<string> *selected_fields, vector<string> *extra_constant_fields)
|
||||||
{
|
{
|
||||||
if (selected_fields == NULL || selected_fields->size() == 0)
|
if (selected_fields == NULL || selected_fields->size() == 0)
|
||||||
|
@ -1647,17 +1680,17 @@ string concatFields(Meter *m, Telegram *t, char c, vector<FieldInfo> &prints, ve
|
||||||
}
|
}
|
||||||
else
|
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 = "";
|
string buf = "";
|
||||||
|
|
||||||
for (string field : *selected_fields)
|
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;
|
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;
|
if (handled) continue;
|
||||||
|
|
||||||
handled = checkConstantField(&buf, field, c, extra_constant_fields);
|
handled = checkConstantField(&buf, field, c, extra_constant_fields);
|
||||||
|
|
|
@ -85,9 +85,6 @@ LIST_OF_METER_TYPES
|
||||||
X(lansenth, T1_bit, TempHygroMeter, LANSENTH, LansenTH) \
|
X(lansenth, T1_bit, TempHygroMeter, LANSENTH, LansenTH) \
|
||||||
X(mkradio3, T1_bit, WaterMeter, MKRADIO3, MKRadio3) \
|
X(mkradio3, T1_bit, WaterMeter, MKRADIO3, MKRadio3) \
|
||||||
X(mkradio4, T1_bit, WaterMeter, MKRADIO4, MKRadio4) \
|
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(multical302,C1_bit|T1_bit, HeatMeter, MULTICAL302, Multical302) \
|
||||||
X(multical403,C1_bit, HeatMeter, MULTICAL403, Multical403) \
|
X(multical403,C1_bit, HeatMeter, MULTICAL403, Multical403) \
|
||||||
X(multical602,C1_bit, HeatMeter, MULTICAL602, Multical602) \
|
X(multical602,C1_bit, HeatMeter, MULTICAL602, Multical602) \
|
||||||
|
@ -245,6 +242,7 @@ private:
|
||||||
|
|
||||||
MeterDriver driver_ {}; // Old driver enum, to go away.
|
MeterDriver driver_ {}; // Old driver enum, to go away.
|
||||||
DriverName name_; // auto, unknown, amiplus, lse_07_17, multical21 etc
|
DriverName name_; // auto, unknown, amiplus, lse_07_17, multical21 etc
|
||||||
|
vector<DriverName> name_aliases_; // Secondary names that will map to this driver.
|
||||||
LinkModeSet linkmodes_; // C1, T1, S1 or combinations thereof.
|
LinkModeSet linkmodes_; // C1, T1, S1 or combinations thereof.
|
||||||
Translate::Lookup mfct_tpl_status_bits_; // Translate any mfct specific bits in tpl status.
|
Translate::Lookup mfct_tpl_status_bits_; // Translate any mfct specific bits in tpl status.
|
||||||
MeterType type_; // Water, Electricity etc.
|
MeterType type_; // Water, Electricity etc.
|
||||||
|
@ -256,6 +254,7 @@ public:
|
||||||
DriverInfo() {};
|
DriverInfo() {};
|
||||||
DriverInfo(MeterDriver mt) : driver_(mt) {};
|
DriverInfo(MeterDriver mt) : driver_(mt) {};
|
||||||
void setName(std::string n) { name_ = n; }
|
void setName(std::string n) { name_ = n; }
|
||||||
|
void addNameAlias(std::string n) { name_aliases_.push_back(n); }
|
||||||
void setMeterType(MeterType t) { type_ = t; }
|
void setMeterType(MeterType t) { type_ = t; }
|
||||||
void setDefaultFields(string f) { default_fields_ = splitString(f, ','); }
|
void setDefaultFields(string f) { default_fields_ = splitString(f, ','); }
|
||||||
void addLinkMode(LinkMode lm) { linkmodes_.addLinkMode(lm); }
|
void addLinkMode(LinkMode lm) { linkmodes_.addLinkMode(lm); }
|
||||||
|
@ -266,6 +265,7 @@ public:
|
||||||
|
|
||||||
MeterDriver driver() { return driver_; }
|
MeterDriver driver() { return driver_; }
|
||||||
DriverName name() { return name_; }
|
DriverName name() { return name_; }
|
||||||
|
vector<DriverName>& nameAliases() { return name_aliases_; }
|
||||||
MeterType type() { return type_; }
|
MeterType type() { return type_; }
|
||||||
vector<string>& defaultFields() { return default_fields_; }
|
vector<string>& defaultFields() { return default_fields_; }
|
||||||
LinkModeSet linkModes() { return linkmodes_; }
|
LinkModeSet linkModes() { return linkmodes_; }
|
||||||
|
|
|
@ -51,6 +51,7 @@ void test_dvs();
|
||||||
void test_ascii_detection();
|
void test_ascii_detection();
|
||||||
void test_status_join();
|
void test_status_join();
|
||||||
void test_status_sort();
|
void test_status_sort();
|
||||||
|
void test_field_matcher();
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
@ -88,6 +89,7 @@ int main(int argc, char **argv)
|
||||||
test_ascii_detection();
|
test_ascii_detection();
|
||||||
test_status_join();
|
test_status_join();
|
||||||
test_status_sort();
|
test_status_sort();
|
||||||
|
test_field_matcher();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -254,6 +256,7 @@ int test_test()
|
||||||
|
|
||||||
int test_linkmodes()
|
int test_linkmodes()
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
LinkModeCalculationResult lmcr;
|
LinkModeCalculationResult lmcr;
|
||||||
auto manager = createSerialCommunicationManager(0, false);
|
auto manager = createSerialCommunicationManager(0, false);
|
||||||
|
|
||||||
|
@ -387,6 +390,7 @@ int test_linkmodes()
|
||||||
debug("test7 OK\n\n");
|
debug("test7 OK\n\n");
|
||||||
|
|
||||||
manager->stop();
|
manager->stop();
|
||||||
|
*/
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1451,3 +1455,70 @@ void test_status_sort()
|
||||||
test_sort("ERROR BUSY FLOW ERROR", "BUSY ERROR FLOW");
|
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");
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -46,11 +46,13 @@ METER_TIMESTAMP_UT
|
||||||
METER_TIMESTAMP_LT
|
METER_TIMESTAMP_LT
|
||||||
METER_DEVICE
|
METER_DEVICE
|
||||||
METER_RSSI_DBM
|
METER_RSSI_DBM
|
||||||
|
METER_STATUS
|
||||||
METER_TOTAL_M3
|
METER_TOTAL_M3
|
||||||
METER_TARGET_M3
|
METER_TARGET_M3
|
||||||
METER_MAX_FLOW_M3H
|
|
||||||
METER_FLOW_TEMPERATURE_C
|
METER_FLOW_TEMPERATURE_C
|
||||||
METER_EXTERNAL_TEMPERATURE_C
|
METER_EXTERNAL_TEMPERATURE_C
|
||||||
|
METER_MIN_EXTERNAL_TEMPERATURE_C
|
||||||
|
METER_MAX_FLOW_M3H
|
||||||
METER_CURRENT_STATUS
|
METER_CURRENT_STATUS
|
||||||
METER_TIME_DRY
|
METER_TIME_DRY
|
||||||
METER_TIME_REVERSED
|
METER_TIME_REVERSED
|
||||||
|
|
|
@ -30,8 +30,8 @@ cat > $TEST/test_expected.txt <<EOF
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat > /tmp/wmbusmeters_telegram_expected <<EOF
|
cat > /tmp/wmbusmeters_telegram_expected <<EOF
|
||||||
METER =={"media":"cold water","meter":"multical21","name":"Water","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"}==
|
METER =={"media":"cold water","meter":"multical21","name":"Water","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"}==
|
||||||
METER =={"media":"cold water","meter":"multical21","name":"Water","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"}==
|
METER =={"media":"cold water","meter":"multical21","name":"Water","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"}==
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat > /tmp/wmbusmeters_alarm_expected <<EOF
|
cat > /tmp/wmbusmeters_alarm_expected <<EOF
|
||||||
|
|
|
@ -33,7 +33,7 @@ TESTRESULT="ERROR"
|
||||||
cat > $TEST/test_expected.txt <<EOF
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
Auto driver : multical21
|
Auto driver : multical21
|
||||||
Best driver : 00/00
|
Best driver : 00/00
|
||||||
Using driver : multical21(driver should be upgraded) 00/00
|
Using driver : multical21 00/00
|
||||||
000 : 2a length (42 bytes)
|
000 : 2a length (42 bytes)
|
||||||
001 : 44 dll-c (from meter SND_NR)
|
001 : 44 dll-c (from meter SND_NR)
|
||||||
002 : 2d2c dll-mfct (KAM)
|
002 : 2d2c dll-mfct (KAM)
|
||||||
|
@ -52,16 +52,14 @@ Using driver : multical21(driver should be upgraded) 00/00
|
||||||
"meter":"multical21",
|
"meter":"multical21",
|
||||||
"name":"",
|
"name":"",
|
||||||
"id":"76348799",
|
"id":"76348799",
|
||||||
"total_m3":0,
|
"status":null,
|
||||||
"target_m3":0,
|
"total_m3":null,
|
||||||
"max_flow_m3h":0,
|
"target_m3":null,
|
||||||
"flow_temperature_c":127,
|
"current_status":null,
|
||||||
"external_temperature_c":127,
|
"time_dry":null,
|
||||||
"current_status":"",
|
"time_reversed":null,
|
||||||
"time_dry":"",
|
"time_leaking":null,
|
||||||
"time_reversed":"",
|
"time_bursting":null,
|
||||||
"time_leaking":"",
|
|
||||||
"time_bursting":"",
|
|
||||||
"timestamp":"1111-11-11T11:11:11Z"
|
"timestamp":"1111-11-11T11:11:11Z"
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
@ -80,7 +78,7 @@ TESTRESULT="ERROR"
|
||||||
cat > $TEST/test_expected.txt <<EOF
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
Auto driver : multical21
|
Auto driver : multical21
|
||||||
Best driver : 00/00
|
Best driver : 00/00
|
||||||
Using driver : multical21(driver should be upgraded) 00/00
|
Using driver : multical21 00/00
|
||||||
000 : 23 length (35 bytes)
|
000 : 23 length (35 bytes)
|
||||||
001 : 44 dll-c (from meter SND_NR)
|
001 : 44 dll-c (from meter SND_NR)
|
||||||
002 : 2d2c dll-mfct (KAM)
|
002 : 2d2c dll-mfct (KAM)
|
||||||
|
@ -99,16 +97,14 @@ Using driver : multical21(driver should be upgraded) 00/00
|
||||||
"meter":"multical21",
|
"meter":"multical21",
|
||||||
"name":"",
|
"name":"",
|
||||||
"id":"76348799",
|
"id":"76348799",
|
||||||
"total_m3":0,
|
"status":null,
|
||||||
"target_m3":0,
|
"total_m3":null,
|
||||||
"max_flow_m3h":0,
|
"target_m3":null,
|
||||||
"flow_temperature_c":127,
|
"current_status":null,
|
||||||
"external_temperature_c":127,
|
"time_dry":null,
|
||||||
"current_status":"",
|
"time_reversed":null,
|
||||||
"time_dry":"",
|
"time_leaking":null,
|
||||||
"time_reversed":"",
|
"time_bursting":null,
|
||||||
"time_leaking":"",
|
|
||||||
"time_bursting":"",
|
|
||||||
"timestamp":"1111-11-11T11:11:11Z"
|
"timestamp":"1111-11-11T11:11:11Z"
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
@ -126,8 +122,8 @@ TESTRESULT="ERROR"
|
||||||
|
|
||||||
cat > $TEST/test_expected.txt <<EOF
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
Auto driver : multical21
|
Auto driver : multical21
|
||||||
Best driver : multical21(driver should be upgraded) 12/12
|
Best driver : multical21 12/12
|
||||||
Using driver : multical21(driver should be upgraded) 00/00
|
Using driver : multical21 00/00
|
||||||
000 : 2a length (42 bytes)
|
000 : 2a length (42 bytes)
|
||||||
001 : 44 dll-c (from meter SND_NR)
|
001 : 44 dll-c (from meter SND_NR)
|
||||||
002 : 2d2c dll-mfct (KAM)
|
002 : 2d2c dll-mfct (KAM)
|
||||||
|
@ -143,28 +139,28 @@ Using driver : multical21(driver should be upgraded) 00/00
|
||||||
020 : 02 dif (16 Bit Integer/Binary Instantaneous value)
|
020 : 02 dif (16 Bit Integer/Binary Instantaneous value)
|
||||||
021 : FF vif (Manufacturer specific)
|
021 : FF vif (Manufacturer specific)
|
||||||
022 : 20 combinable vif (PerSecond)
|
022 : 20 combinable vif (PerSecond)
|
||||||
023 C!: 7100 info codes (DRY(dry 22-31 days))
|
023 C!: 7100 ("status":"DRY") ("current_status":"DRY") ("time_dry":"22-31 days") ("time_reversed":"") ("time_leaking":"") ("time_bursting":"")
|
||||||
025 : 04 dif (32 Bit Integer/Binary Instantaneous value)
|
025 : 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||||
026 : 13 vif (Volume l)
|
026 : 13 vif (Volume l)
|
||||||
027 C!: 08190000 total consumption (6.408000 m3)
|
027 C!: 08190000 ("total_m3":6.408)
|
||||||
031 : 44 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
|
031 : 44 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
|
||||||
032 : 13 vif (Volume l)
|
032 : 13 vif (Volume l)
|
||||||
033 C!: 08190000 target consumption (6.408000 m3)
|
033 C!: 08190000 ("target_m3":6.408)
|
||||||
037 : 61 dif (8 Bit Integer/Binary Minimum value storagenr=1)
|
037 : 61 dif (8 Bit Integer/Binary Minimum value storagenr=1)
|
||||||
038 : 5B vif (Flow temperature °C)
|
038 : 5B vif (Flow temperature °C)
|
||||||
039 C!: 7F flow temperature (127.000000 °C)
|
039 C!: 7F ("flow_temperature_c":127)
|
||||||
040 : 61 dif (8 Bit Integer/Binary Minimum value storagenr=1)
|
040 : 61 dif (8 Bit Integer/Binary Minimum value storagenr=1)
|
||||||
041 : 67 vif (External temperature °C)
|
041 : 67 vif (External temperature °C)
|
||||||
042 C!: 13 external temperature (19.000000 °C)
|
042 C!: 13 ("external_temperature_c":19)
|
||||||
|
|
||||||
{
|
{
|
||||||
"media":"cold water",
|
"media":"cold water",
|
||||||
"meter":"multical21",
|
"meter":"multical21",
|
||||||
"name":"",
|
"name":"",
|
||||||
"id":"76348799",
|
"id":"76348799",
|
||||||
|
"status":"DRY",
|
||||||
"total_m3":6.408,
|
"total_m3":6.408,
|
||||||
"target_m3":6.408,
|
"target_m3":6.408,
|
||||||
"max_flow_m3h":0,
|
|
||||||
"flow_temperature_c":127,
|
"flow_temperature_c":127,
|
||||||
"external_temperature_c":19,
|
"external_temperature_c":19,
|
||||||
"current_status":"DRY",
|
"current_status":"DRY",
|
||||||
|
@ -189,8 +185,8 @@ TESTRESULT="ERROR"
|
||||||
|
|
||||||
cat > $TEST/test_expected.txt <<EOF
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
Auto driver : multical21
|
Auto driver : multical21
|
||||||
Best driver : multical21(driver should be upgraded) 12/12
|
Best driver : multical21 12/12
|
||||||
Using driver : multical21(driver should be upgraded) 00/00
|
Using driver : multical21 00/00
|
||||||
000 : 23 length (35 bytes)
|
000 : 23 length (35 bytes)
|
||||||
001 : 44 dll-c (from meter SND_NR)
|
001 : 44 dll-c (from meter SND_NR)
|
||||||
002 : 2d2c dll-mfct (KAM)
|
002 : 2d2c dll-mfct (KAM)
|
||||||
|
@ -205,20 +201,20 @@ Using driver : multical21(driver should be upgraded) 00/00
|
||||||
019 : 79 tpl-ci-field (EN 13757-3 Application Layer with Compact frame (no tplh))
|
019 : 79 tpl-ci-field (EN 13757-3 Application Layer with Compact frame (no tplh))
|
||||||
020 : eda8 format signature
|
020 : eda8 format signature
|
||||||
022 : e475 data crc
|
022 : e475 data crc
|
||||||
024 C!: 7100 info codes (DRY(dry 22-31 days))
|
024 C!: 7100 ("status":"DRY") ("current_status":"DRY") ("time_dry":"22-31 days") ("time_reversed":"") ("time_leaking":"") ("time_bursting":"")
|
||||||
026 C!: 09190000 total consumption (6.409000 m3)
|
026 C!: 09190000 ("total_m3":6.409)
|
||||||
030 C!: 09190000 target consumption (6.409000 m3)
|
030 C!: 09190000 ("target_m3":6.409)
|
||||||
034 C!: 7F flow temperature (127.000000 °C)
|
034 C!: 7F ("flow_temperature_c":127)
|
||||||
035 C!: 16 external temperature (22.000000 °C)
|
035 C!: 16 ("external_temperature_c":22)
|
||||||
|
|
||||||
{
|
{
|
||||||
"media":"cold water",
|
"media":"cold water",
|
||||||
"meter":"multical21",
|
"meter":"multical21",
|
||||||
"name":"",
|
"name":"",
|
||||||
"id":"76348799",
|
"id":"76348799",
|
||||||
|
"status":"DRY",
|
||||||
"total_m3":6.409,
|
"total_m3":6.409,
|
||||||
"target_m3":6.409,
|
"target_m3":6.409,
|
||||||
"max_flow_m3h":0,
|
|
||||||
"flow_temperature_c":127,
|
"flow_temperature_c":127,
|
||||||
"external_temperature_c":22,
|
"external_temperature_c":22,
|
||||||
"current_status":"DRY",
|
"current_status":"DRY",
|
||||||
|
|
|
@ -9,7 +9,7 @@ TESTNAME="Test ANYID"
|
||||||
TESTRESULT="ERROR"
|
TESTRESULT="ERROR"
|
||||||
|
|
||||||
cat > $TEST/test_expected.txt <<EOF
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
{"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"}
|
{"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"}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
$PROG --format=json 2A442D2C998734761B168D2091D37CAC21576C7802FF207100041308190000441308190000615B7F616713 \
|
$PROG --format=json 2A442D2C998734761B168D2091D37CAC21576C7802FF207100041308190000441308190000615B7F616713 \
|
||||||
|
|
|
@ -10,8 +10,8 @@ TESTNAME="Test selected fields"
|
||||||
TESTRESULT="ERROR"
|
TESTRESULT="ERROR"
|
||||||
|
|
||||||
cat <<EOF > $TEST/test_expected.txt
|
cat <<EOF > $TEST/test_expected.txt
|
||||||
76348799;Vatten;6408;6.408;0;127;260.6
|
76348799;Vatten;6408;6.408;null;127;260.6
|
||||||
76348799;Vatten;6408;6.408;0;127;260.6
|
76348799;Vatten;6408;6.408;null;127;260.6
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
$PROG --format=fields --separator=';' \
|
$PROG --format=fields --separator=';' \
|
||||||
|
|
|
@ -34,7 +34,7 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cat > $TEST/test_expected.txt <<EOF
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
{"media":"cold water","meter":"multical21","name":"MyWater","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":"MyWater","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"}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
TESTNAME="Test hex on commandline with meter"
|
TESTNAME="Test hex on commandline with meter"
|
||||||
|
@ -94,7 +94,7 @@ TESTNAME="Test hex on stdin"
|
||||||
|
|
||||||
cat > $TEST/test_expected.txt <<EOF
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
{"media":"other","meter":"lansenpu","name":"MyCounter","id":"00010206","status":"OK","a_counter":4711,"b_counter":1234,"timestamp":"1111-11-11T11:11:11Z"}
|
{"media":"other","meter":"lansenpu","name":"MyCounter","id":"00010206","status":"OK","a_counter":4711,"b_counter":1234,"timestamp":"1111-11-11T11:11:11Z"}
|
||||||
{"media":"cold water","meter":"multical21","name":"MyWater","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":"MyWater","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"}
|
||||||
{"media":"other","meter":"lansenpu","name":"MyCounter","id":"00010206","status":"OK","a_counter":4711,"b_counter":1234,"timestamp":"1111-11-11T11:11:11Z"}
|
{"media":"other","meter":"lansenpu","name":"MyCounter","id":"00010206","status":"OK","a_counter":4711,"b_counter":1234,"timestamp":"1111-11-11T11:11:11Z"}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue