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!
|
||||
|
||||
Improve minomess driver to handle telegrams from wired m-bus module.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}
|
|
@ -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"}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
|
|
@ -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) \
|
||||
|
|
|
@ -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,10 +50,25 @@ void verifyDriverLookupCreated()
|
|||
DriverInfo *lookupDriver(string name)
|
||||
{
|
||||
verifyDriverLookupCreated();
|
||||
if (registered_drivers_->count(name) == 0) return NULL;
|
||||
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<DriverInfo*> &allDrivers()
|
||||
{
|
||||
return *registered_drivers_list_;
|
||||
|
@ -62,6 +77,12 @@ vector<DriverInfo*> &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<string> *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<FieldInfo> &fields, vector<Unit> &cs)
|
||||
vector<FieldInfo> &fields, vector<Unit> &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<string> *extra
|
|||
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)
|
||||
{
|
||||
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
|
||||
{
|
||||
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);
|
||||
|
|
|
@ -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<DriverName> 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<DriverName>& nameAliases() { return name_aliases_; }
|
||||
MeterType type() { return type_; }
|
||||
vector<string>& defaultFields() { return default_fields_; }
|
||||
LinkModeSet linkModes() { return linkmodes_; }
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -30,8 +30,8 @@ cat > $TEST/test_expected.txt <<EOF
|
|||
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","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","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
|
||||
|
||||
cat > /tmp/wmbusmeters_alarm_expected <<EOF
|
||||
|
|
|
@ -33,7 +33,7 @@ TESTRESULT="ERROR"
|
|||
cat > $TEST/test_expected.txt <<EOF
|
||||
Auto driver : multical21
|
||||
Best driver : 00/00
|
||||
Using driver : multical21(driver should be upgraded) 00/00
|
||||
Using driver : multical21 00/00
|
||||
000 : 2a length (42 bytes)
|
||||
001 : 44 dll-c (from meter SND_NR)
|
||||
002 : 2d2c dll-mfct (KAM)
|
||||
|
@ -52,16 +52,14 @@ Using driver : multical21(driver should be upgraded) 00/00
|
|||
"meter":"multical21",
|
||||
"name":"",
|
||||
"id":"76348799",
|
||||
"total_m3":0,
|
||||
"target_m3":0,
|
||||
"max_flow_m3h":0,
|
||||
"flow_temperature_c":127,
|
||||
"external_temperature_c":127,
|
||||
"current_status":"",
|
||||
"time_dry":"",
|
||||
"time_reversed":"",
|
||||
"time_leaking":"",
|
||||
"time_bursting":"",
|
||||
"status":null,
|
||||
"total_m3":null,
|
||||
"target_m3":null,
|
||||
"current_status":null,
|
||||
"time_dry":null,
|
||||
"time_reversed":null,
|
||||
"time_leaking":null,
|
||||
"time_bursting":null,
|
||||
"timestamp":"1111-11-11T11:11:11Z"
|
||||
}
|
||||
EOF
|
||||
|
@ -80,7 +78,7 @@ TESTRESULT="ERROR"
|
|||
cat > $TEST/test_expected.txt <<EOF
|
||||
Auto driver : multical21
|
||||
Best driver : 00/00
|
||||
Using driver : multical21(driver should be upgraded) 00/00
|
||||
Using driver : multical21 00/00
|
||||
000 : 23 length (35 bytes)
|
||||
001 : 44 dll-c (from meter SND_NR)
|
||||
002 : 2d2c dll-mfct (KAM)
|
||||
|
@ -99,16 +97,14 @@ Using driver : multical21(driver should be upgraded) 00/00
|
|||
"meter":"multical21",
|
||||
"name":"",
|
||||
"id":"76348799",
|
||||
"total_m3":0,
|
||||
"target_m3":0,
|
||||
"max_flow_m3h":0,
|
||||
"flow_temperature_c":127,
|
||||
"external_temperature_c":127,
|
||||
"current_status":"",
|
||||
"time_dry":"",
|
||||
"time_reversed":"",
|
||||
"time_leaking":"",
|
||||
"time_bursting":"",
|
||||
"status":null,
|
||||
"total_m3":null,
|
||||
"target_m3":null,
|
||||
"current_status":null,
|
||||
"time_dry":null,
|
||||
"time_reversed":null,
|
||||
"time_leaking":null,
|
||||
"time_bursting":null,
|
||||
"timestamp":"1111-11-11T11:11:11Z"
|
||||
}
|
||||
EOF
|
||||
|
@ -126,8 +122,8 @@ TESTRESULT="ERROR"
|
|||
|
||||
cat > $TEST/test_expected.txt <<EOF
|
||||
Auto driver : multical21
|
||||
Best driver : multical21(driver should be upgraded) 12/12
|
||||
Using driver : multical21(driver should be upgraded) 00/00
|
||||
Best driver : multical21 12/12
|
||||
Using driver : multical21 00/00
|
||||
000 : 2a length (42 bytes)
|
||||
001 : 44 dll-c (from meter SND_NR)
|
||||
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)
|
||||
021 : FF vif (Manufacturer specific)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
041 : 67 vif (External temperature °C)
|
||||
042 C!: 13 external temperature (19.000000 °C)
|
||||
042 C!: 13 ("external_temperature_c":19)
|
||||
|
||||
{
|
||||
"media":"cold water",
|
||||
"meter":"multical21",
|
||||
"name":"",
|
||||
"id":"76348799",
|
||||
"status":"DRY",
|
||||
"total_m3":6.408,
|
||||
"target_m3":6.408,
|
||||
"max_flow_m3h":0,
|
||||
"flow_temperature_c":127,
|
||||
"external_temperature_c":19,
|
||||
"current_status":"DRY",
|
||||
|
@ -189,8 +185,8 @@ TESTRESULT="ERROR"
|
|||
|
||||
cat > $TEST/test_expected.txt <<EOF
|
||||
Auto driver : multical21
|
||||
Best driver : multical21(driver should be upgraded) 12/12
|
||||
Using driver : multical21(driver should be upgraded) 00/00
|
||||
Best driver : multical21 12/12
|
||||
Using driver : multical21 00/00
|
||||
000 : 23 length (35 bytes)
|
||||
001 : 44 dll-c (from meter SND_NR)
|
||||
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))
|
||||
020 : eda8 format signature
|
||||
022 : e475 data crc
|
||||
024 C!: 7100 info codes (DRY(dry 22-31 days))
|
||||
026 C!: 09190000 total consumption (6.409000 m3)
|
||||
030 C!: 09190000 target consumption (6.409000 m3)
|
||||
034 C!: 7F flow temperature (127.000000 °C)
|
||||
035 C!: 16 external temperature (22.000000 °C)
|
||||
024 C!: 7100 ("status":"DRY") ("current_status":"DRY") ("time_dry":"22-31 days") ("time_reversed":"") ("time_leaking":"") ("time_bursting":"")
|
||||
026 C!: 09190000 ("total_m3":6.409)
|
||||
030 C!: 09190000 ("target_m3":6.409)
|
||||
034 C!: 7F ("flow_temperature_c":127)
|
||||
035 C!: 16 ("external_temperature_c":22)
|
||||
|
||||
{
|
||||
"media":"cold water",
|
||||
"meter":"multical21",
|
||||
"name":"",
|
||||
"id":"76348799",
|
||||
"status":"DRY",
|
||||
"total_m3":6.409,
|
||||
"target_m3":6.409,
|
||||
"max_flow_m3h":0,
|
||||
"flow_temperature_c":127,
|
||||
"external_temperature_c":22,
|
||||
"current_status":"DRY",
|
||||
|
|
|
@ -9,7 +9,7 @@ TESTNAME="Test ANYID"
|
|||
TESTRESULT="ERROR"
|
||||
|
||||
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
|
||||
|
||||
$PROG --format=json 2A442D2C998734761B168D2091D37CAC21576C7802FF207100041308190000441308190000615B7F616713 \
|
||||
|
|
|
@ -10,8 +10,8 @@ TESTNAME="Test selected fields"
|
|||
TESTRESULT="ERROR"
|
||||
|
||||
cat <<EOF > $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=';' \
|
||||
|
|
|
@ -34,7 +34,7 @@ else
|
|||
fi
|
||||
|
||||
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
|
||||
|
||||
TESTNAME="Test hex on commandline with meter"
|
||||
|
@ -94,7 +94,7 @@ TESTNAME="Test hex on stdin"
|
|||
|
||||
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":"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"}
|
||||
EOF
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue