Refactor multical21 and flowiq drivers.

pull/601/head
Fredrik Öhrström 2022-09-03 13:25:37 +02:00
rodzic 44c0ba1e74
commit 84fe29069a
20 zmienionych plików z 777 dodań i 525 usunięć

12
CHANGES
Wyświetl plik

@ -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.

Wyświetl plik

@ -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)

Wyświetl plik

@ -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}

Wyświetl plik

@ -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"}

Wyświetl plik

@ -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

Wyświetl plik

@ -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"}

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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) \

Wyświetl plik

@ -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 "?";
}
}

Wyświetl plik

@ -50,8 +50,23 @@ void verifyDriverLookupCreated()
DriverInfo *lookupDriver(string name)
{
verifyDriverLookupCreated();
if (registered_drivers_->count(name) == 0) return NULL;
return &(*registered_drivers_)[name];
if (registered_drivers_->count(name) == 1)
{
return &(*registered_drivers_)[name];
}
for (DriverInfo *di : *registered_drivers_list_)
{
for (DriverName &dn : di->nameAliases())
{
if (dn.str() == name)
{
return di;
}
}
}
return NULL;
}
vector<DriverInfo*> &allDrivers()
@ -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);

Wyświetl plik

@ -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_; }

Wyświetl plik

@ -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");
}
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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",

Wyświetl plik

@ -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 \

Wyświetl plik

@ -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=';' \

Wyświetl plik

@ -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