Preparing tests for sorted json output keys.

pull/708/head
Fredrik Öhrström 2022-11-20 13:15:44 +01:00
rodzic 53a2d650e5
commit f650577ea2
20 zmienionych plików z 270 dodań i 450 usunięć

Wyświetl plik

@ -237,11 +237,6 @@ telegram=|37446850336633663943a2_10672c866100181c01000480794435d5000000000000000
{"media":"heat","meter":"compact5","name":"Heating2","id":"66336633","total_kwh":25250,"current_kwh":284,"previous_kwh":24966,"timestamp":"1111-11-11T11:11:11Z"}
|Heating2;66336633;25250;284;24966;1111-11-11 11:11.11
# Test Maddalena EVO 868 wmbus module on water meter
telegram=|aa4424347677787950077ac10000202f2f041306070000046d1e31b12104fd17000000000e787880048120004413c9040000426c9f2c840113c904000082016c9f2cd3013b9a0200c4016d0534a7218104fd280182046c9f2c840413c9040000c404131b00000084051300000000c405130000000084061300000000c406130000000084071300000000c407130000000084081300000000c408130000000084091300000000c4091300000000ffff|
{"media":"water","meter":"evo868","name":"Votchka","id":"79787776","total_m3":1.798,"device_date_time":"2021-01-17 17:30","current_status":"OK","fabrication_no":"002081048078","consumption_at_set_date_m3":1.225,"set_date":"2020-12-31","consumption_at_set_date_2_m3":1.225,"set_date_2":"2020-12-31","max_flow_since_datetime_m3h":0.666,"max_flow_datetime":"2021-01-07 20:05","consumption_at_history_1_m3":1.225,"history_1_date":"2020-12-31","consumption_at_history_2_m3":0.027,"history_2_date":"2020-11-30","consumption_at_history_3_m3":0,"history_3_date":"2020-10-31","consumption_at_history_4_m3":0,"history_4_date":"2020-09-30","consumption_at_history_5_m3":0,"history_5_date":"2020-08-31","consumption_at_history_6_m3":0,"history_6_date":"2020-07-31","consumption_at_history_7_m3":0,"history_7_date":"2020-06-30","consumption_at_history_8_m3":0,"history_8_date":"2020-05-31","consumption_at_history_9_m3":0,"history_9_date":"2020-04-30","consumption_at_history_10_m3":0,"history_10_date":"2020-03-31","consumption_at_history_11_m3":0,"history_11_date":"2020-02-29","consumption_at_history_12_m3":0,"history_12_date":"2020-01-31","timestamp":"1111-11-11T11:11:11Z"}
|Votchka;79787776;1.798000;OK;1.225000;2020-12-31;1111-11-11 11:11.11
# Test Gran-System-S electricity meter 101
telegram=||7844731e78610418010278046d0f13bc21040394030000841003690300008420032b00000084300300000000848010030000000084016d0000bc2184010394030000841103690300008421032b00000084310300000000848110030000000004fd482e09000004fd5b0000000002fb2d861304fd1700000201|
{"media":"electricity","meter":"gransystems","name":"Gran101","id":"18046178","total_energy_consumption_kwh":0.916,"voltage_at_phase_1_v":235,"voltage_at_phase_2_v":0,"voltage_at_phase_3_v":0,"currrent_at_phase_1_a":0,"currrent_at_phase_2_a":0,"currrent_at_phase_3_a":0,"frequency_hz":49.98,"status":"OK","timestamp":"1111-11-11T11:11:11Z"}

Wyświetl plik

@ -0,0 +1,151 @@
/*
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("evo868");
di.setDefaultFields("name,id,total_m3,set_date,consumption_at_set_date_m3,timestamp");
di.setMeterType(MeterType::WaterMeter);
di.addLinkMode(LinkMode::T1);
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
di.addDetection(MANUFACTURER_MAD, 0x07, 0x50);
});
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
{
addStringFieldWithExtractorAndLookup(
"current_status",
"Status of meter.",
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT
| PrintProperty::STATUS | PrintProperty::JOIN_TPL_STATUS,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::ErrorFlags),
{
{
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffff,
"OK",
{
// { 0x01 , "WOOT" },
}
},
},
});
addOptionalCommonFields("fabrication_no,meter_datetime");
addOptionalFlowRelatedFields("total_m3");
addNumericFieldWithExtractor(
"consumption_at_set_date",
"The total water consumption at the most recent billing period date.",
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
Quantity::Volume,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::Volume)
.set(StorageNr(1))
);
addStringFieldWithExtractor(
"set_date",
"The most recent billing period date.",
PrintProperty::JSON | PrintProperty::OPTIONAL,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::Date)
.set(StorageNr(1))
);
addNumericFieldWithExtractor(
"consumption_at_set_date_2",
"The total water consumption at the second most recent billing period date.",
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
Quantity::Volume,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::Volume)
.set(StorageNr(2))
);
addStringFieldWithExtractor(
"set_date_2",
"The second most recent billing period date.",
PrintProperty::JSON | PrintProperty::OPTIONAL,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::Date)
.set(StorageNr(2))
);
addNumericFieldWithExtractor(
"max_flow_since_datetime",
"Maximum water flow since date time.",
PrintProperty::JSON | PrintProperty::FIELD,
Quantity::Flow,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Maximum)
.set(VIFRange::VolumeFlow)
.set(StorageNr(3))
);
addStringFieldWithExtractor(
"max_flow_datetime",
"The datetime to which maximum flow is measured.",
PrintProperty::JSON | PrintProperty::OPTIONAL,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::DateTime)
.set(StorageNr(3))
);
addNumericFieldWithExtractor(
"consumption_at_history_{storage_counter-7counter}",
"The total water consumption at the historic date.",
PrintProperty::JSON,
Quantity::Volume,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::Volume)
.set(StorageNr(8),StorageNr(19))
);
}
}
// est: Votchka evo868 79787776 NOKEY
// omment: Test Maddalena EVO 868 wmbus module on water meter
// elegram=|aa4424347677787950077ac10000202f2f041306070000046d1e31b12104fd17000000000e787880048120004413c9040000426c9f2c840113c904000082016c9f2cd3013b9a0200c4016d0534a7218104fd280182046c9f2c840413c9040000c404131b00000084051300000000c405130000000084061300000000c406130000000084071300000000c407130000000084081300000000c408130000000084091300000000c4091300000000ffff|
// "media":"water","meter":"evo868","name":"Votchka","id":"79787776","total_m3":1.798,"device_date_time":"2021-01-17 17:30","current_status":"OK","fabrication_no":"002081048078","consumption_at_set_date_m3":1.225,"set_date":"2020-12-31","consumption_at_set_date_2_m3":1.225,"set_date_2":"2020-12-31","max_flow_since_datetime_m3h":0.666,"max_flow_datetime":"2021-01-07 20:05","consumption_at_history_1_m3":1.225,"history_1_date":"2020-12-31","consumption_at_history_2_m3":0.027,"history_2_date":"2020-11-30","consumption_at_history_3_m3":0,"history_3_date":"2020-10-31","consumption_at_history_4_m3":0,"history_4_date":"2020-09-30","consumption_at_history_5_m3":0,"history_5_date":"2020-08-31","consumption_at_history_6_m3":0,"history_6_date":"2020-07-31","consumption_at_history_7_m3":0,"history_7_date":"2020-06-30","consumption_at_history_8_m3":0,"history_8_date":"2020-05-31","consumption_at_history_9_m3":0,"history_9_date":"2020-04-30","consumption_at_history_10_m3":0,"history_10_date":"2020-03-31","consumption_at_history_11_m3":0,"history_11_date":"2020-02-29","consumption_at_history_12_m3":0,"history_12_date":"2020-01-31","timestamp":"1111-11-11T11:11:11Z"}
// Votchka;79787776;1.798;OK;1.225;2020-12-31;1111-11-11 11:11.11

Wyświetl plik

@ -282,6 +282,7 @@ struct SubUnitNr
SubUnitNr(int n) : nr_(n) {}
int intValue() { return nr_; }
bool operator==(SubUnitNr s) { return nr_ == s.nr_; }
bool operator!=(SubUnitNr s) { return nr_ != s.nr_; }
bool operator>=(SubUnitNr s) { return nr_ >= s.nr_; }
bool operator<=(SubUnitNr s) { return nr_ <= s.nr_; }
@ -450,6 +451,16 @@ struct FieldMatcher
FieldMatcher &set(IndexNr i) { index_nr = i; return *this; }
bool matches(DVEntry &dv_entry);
// Returns true of there is any range for storage, tariff, subunit nrs.
// I.e. this matcher is expected to match against multiple dv entries!
bool expectedToMatchAgainstMultipleEntries()
{
return (match_storage_nr && storage_nr_from != storage_nr_to)
|| (match_tariff_nr && tariff_nr_from != tariff_nr_to)
|| (match_subunit_nr && subunit_nr_from != subunit_nr_to);
}
std::string str();
};

Wyświetl plik

@ -1083,7 +1083,14 @@ string StringInterpolatorImplementation::apply(DVEntry *dve)
if (f < formulas_.size())
{
result += tostrprintf("%g", formulas_[f]->calculate(Unit::COUNTER, dve));
if (dve != NULL)
{
result += tostrprintf("%g", formulas_[f]->calculate(Unit::COUNTER, dve));
}
else
{
result += "{NULL}";
}
f++;
}
}

Wyświetl plik

@ -29,7 +29,6 @@
#define METER_DETECTION \
X(CCx01, MANUFACTURER_GSS, 0x02, 0x01) \
X(EURISII, MANUFACTURER_INE, 0x08, 0x55) \
X(EVO868, MANUFACTURER_MAD, 0x07, 0x50) \
X(HYDROCALM3,MANUFACTURER_BMT, 0x0d, 0x0b) \
X(HYDRODIGIT,MANUFACTURER_BMT, 0x06, 0x13) \
X(HYDRODIGIT,MANUFACTURER_BMT, 0x07, 0x13) \

Wyświetl plik

@ -1,371 +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 <algorithm>
using namespace std;
struct MeterEvo868 : public virtual MeterCommonImplementation {
MeterEvo868(MeterInfo &mi);
// Total water counted through the meter
double totalWaterConsumption(Unit u);
bool hasTotalWaterConsumption();
uint32_t error_flags_ {};
string fabrication_no_;
double consumption_at_set_date_m3_ {};
string set_date_;
double consumption_at_set_date_2_m3_ {};
string set_date_2_;
double max_flow_since_datetime_m3h_ {};
string max_flow_datetime_;
double consumption_at_history_date_m3_[12];
string history_date_[12];
string status();
private:
void processContent(Telegram *t);
double total_water_consumption_m3_ {};
string device_date_time_;
};
shared_ptr<Meter> createEVO868(MeterInfo &mi)
{
return shared_ptr<Meter>(new MeterEvo868(mi));
}
MeterEvo868::MeterEvo868(MeterInfo &mi) :
MeterCommonImplementation(mi, "evo868")
{
setMeterType(MeterType::WaterMeter);
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
addLinkMode(LinkMode::T1);
addPrint("total", Quantity::Volume,
[&](Unit u){ return totalWaterConsumption(u); },
"The total water consumption recorded by this meter.",
PrintProperty::FIELD | PrintProperty::JSON);
addPrint("device_date_time", Quantity::Text,
[&](){ return device_date_time_; },
"Device date time.",
PrintProperty::JSON);
addPrint("current_status", Quantity::Text,
[&](){ return status(); },
"Status of meter.",
PrintProperty::FIELD | PrintProperty::JSON);
addPrint("fabrication_no", Quantity::Text,
[&](){ return fabrication_no_; },
"Fabrication number.",
PrintProperty::JSON);
addPrint("consumption_at_set_date", Quantity::Volume,
[&](Unit u){ assertQuantity(u, Quantity::Volume); return convert(consumption_at_set_date_m3_, Unit::M3, u); },
"The total water consumption at the most recent billing period date.",
PrintProperty::FIELD | PrintProperty::JSON);
addPrint("set_date", Quantity::Text,
[&](){ return set_date_; },
"The most recent billing period date.",
PrintProperty::FIELD | PrintProperty::JSON);
addPrint("consumption_at_set_date_2", Quantity::Volume,
[&](Unit u){ assertQuantity(u, Quantity::Volume); return convert(consumption_at_set_date_2_m3_, Unit::M3, u); },
"The total water consumption at the second recent billing period date.",
PrintProperty::JSON);
addPrint("set_date_2", Quantity::Text,
[&](){ return set_date_2_; },
"The second recent billing period date.",
PrintProperty::JSON);
addPrint("max_flow_since_datetime", Quantity::Flow,
[&](Unit u){ assertQuantity(u, Quantity::Flow); return convert(max_flow_since_datetime_m3h_, Unit::M3H, u); },
"Maximum water flow since date time.",
PrintProperty::JSON);
addPrint("max_flow_datetime", Quantity::Text,
[&](){ return max_flow_datetime_; },
"The datetime to which maximum flow is measured.",
PrintProperty::JSON);
for (int i=1; i<=12; ++i)
{
string key = tostrprintf("consumption_at_history_%d", i);
string epl = tostrprintf("The total water consumption at the history date %d.", i);
addPrint(key, Quantity::Volume,
[this,i](Unit u){ assertQuantity(u, Quantity::Volume); return convert(consumption_at_history_date_m3_[i-1], Unit::M3, u); },
epl,
PrintProperty::JSON);
key = tostrprintf("history_%d_date", i);
epl = tostrprintf("The history date %d.", i);
addPrint(key, Quantity::Text,
[this,i](){ return history_date_[i-1]; },
epl,
PrintProperty::JSON);
}
}
void MeterEvo868::processContent(Telegram *t)
{
/*
(evo868) 11: 04 dif (32 Bit Integer/Binary Instantaneous value)
(evo868) 12: 13 vif (Volume l)
(evo868) 13: * 06070000 total consumption (1.798000 m3)
(evo868) 17: 04 dif (32 Bit Integer/Binary Instantaneous value)
(evo868) 18: 6D vif (Date and time type)
(evo868) 19: 1E31B121
(evo868) 1d: 04 dif (32 Bit Integer/Binary Instantaneous value)
(evo868) 1e: FD vif (Second extension of VIF-codes)
(evo868) 1f: 17 vife (Error flags (binary))
(evo868) 20: 00000000
(evo868) 24: 0E dif (12 digit BCD Instantaneous value)
(evo868) 25: 78 vif (Fabrication no)
(evo868) 26: 788004812000
(evo868) 2c: 44 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
(evo868) 2d: 13 vif (Volume l)
(evo868) 2e: C9040000
(evo868) 32: 42 dif (16 Bit Integer/Binary Instantaneous value storagenr=1)
(evo868) 33: 6C vif (Date type G)
(evo868) 34: 9F2C
(evo868) 36: 84 dif (32 Bit Integer/Binary Instantaneous value)
(evo868) 37: 01 dife (subunit=0 tariff=0 storagenr=2)
(evo868) 38: 13 vif (Volume l)
(evo868) 39: C9040000
(evo868) 3d: 82 dif (16 Bit Integer/Binary Instantaneous value)
(evo868) 3e: 01 dife (subunit=0 tariff=0 storagenr=2)
(evo868) 3f: 6C vif (Date type G)
(evo868) 40: 9F2C
(evo868) 42: D3 dif (24 Bit Integer/Binary Maximum value storagenr=1)
(evo868) 43: 01 dife (subunit=0 tariff=0 storagenr=3)
(evo868) 44: 3B vif (Volume flow l/h)
(evo868) 45: 9A0200
(evo868) 48: C4 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
(evo868) 49: 01 dife (subunit=0 tariff=0 storagenr=3)
(evo868) 4a: 6D vif (Date and time type)
(evo868) 4b: 0534A721
(evo868) 4f: 81 dif (8 Bit Integer/Binary Instantaneous value)
(evo868) 50: 04 dife (subunit=0 tariff=0 storagenr=8)
(evo868) 51: FD vif (Second extension of VIF-codes)
(evo868) 52: 28 vife (Storage interval month(s))
(evo868) 53: 01
(evo868) 54: 82 dif (16 Bit Integer/Binary Instantaneous value)
(evo868) 55: 04 dife (subunit=0 tariff=0 storagenr=8)
(evo868) 56: 6C vif (Date type G)
(evo868) 57: 9F2C
(evo868) 59: 84 dif (32 Bit Integer/Binary Instantaneous value)
(evo868) 5a: 04 dife (subunit=0 tariff=0 storagenr=8)
(evo868) 5b: 13 vif (Volume l)
(evo868) 5c: C9040000
(evo868) 60: C4 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
(evo868) 61: 04 dife (subunit=0 tariff=0 storagenr=9)
(evo868) 62: 13 vif (Volume l)
(evo868) 63: 1B000000
(evo868) 67: 84 dif (32 Bit Integer/Binary Instantaneous value)
(evo868) 68: 05 dife (subunit=0 tariff=0 storagenr=10)
(evo868) 69: 13 vif (Volume l)
(evo868) 6a: 00000000
(evo868) 6e: C4 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
(evo868) 6f: 05 dife (subunit=0 tariff=0 storagenr=11)
(evo868) 70: 13 vif (Volume l)
(evo868) 71: 00000000
(evo868) 75: 84 dif (32 Bit Integer/Binary Instantaneous value)
(evo868) 76: 06 dife (subunit=0 tariff=0 storagenr=12)
(evo868) 77: 13 vif (Volume l)
(evo868) 78: 00000000
(evo868) 7c: C4 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
(evo868) 7d: 06 dife (subunit=0 tariff=0 storagenr=13)
(evo868) 7e: 13 vif (Volume l)
(evo868) 7f: 00000000
(evo868) 83: 84 dif (32 Bit Integer/Binary Instantaneous value)
(evo868) 84: 07 dife (subunit=0 tariff=0 storagenr=14)
(evo868) 85: 13 vif (Volume l)
(evo868) 86: 00000000
(evo868) 8a: C4 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
(evo868) 8b: 07 dife (subunit=0 tariff=0 storagenr=15)
(evo868) 8c: 13 vif (Volume l)
(evo868) 8d: 00000000
(evo868) 91: 84 dif (32 Bit Integer/Binary Instantaneous value)
(evo868) 92: 08 dife (subunit=0 tariff=0 storagenr=16)
(evo868) 93: 13 vif (Volume l)
(evo868) 94: 00000000
(evo868) 98: C4 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
(evo868) 99: 08 dife (subunit=0 tariff=0 storagenr=17)
(evo868) 9a: 13 vif (Volume l)
(evo868) 9b: 00000000
(evo868) 9f: 84 dif (32 Bit Integer/Binary Instantaneous value)
(evo868) a0: 09 dife (subunit=0 tariff=0 storagenr=18)
(evo868) a1: 13 vif (Volume l)
(evo868) a2: 00000000
(evo868) a6: C4 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
(evo868) a7: 09 dife (subunit=0 tariff=0 storagenr=19)
(evo868) a8: 13 vif (Volume l)
(evo868) a9: 00000000
*/
int offset;
string key;
if(findKey(MeasurementType::Instantaneous, VIFRange::Volume, 0, 0, &key, &t->dv_entries)) {
extractDVdouble(&t->dv_entries, key, &offset, &total_water_consumption_m3_);
t->addMoreExplanation(offset, " total consumption (%f m3)", total_water_consumption_m3_);
}
if (findKey(MeasurementType::Instantaneous, VIFRange::DateTime, 0, 0, &key, &t->dv_entries)) {
struct tm datetime;
extractDVdate(&t->dv_entries, key, &offset, &datetime);
device_date_time_ = strdatetime(&datetime);
t->addMoreExplanation(offset, " device datetime (%s)", device_date_time_.c_str());
}
extractDVuint32(&t->dv_entries, "04FD17", &offset, &error_flags_);
t->addMoreExplanation(offset, " error flags (%s)", status().c_str());
extractDVReadableString(&t->dv_entries, "0E78", &offset, &fabrication_no_);
t->addMoreExplanation(offset, " fabrication no (%s)", fabrication_no_.c_str());
if(findKey(MeasurementType::Instantaneous, VIFRange::Volume, 1, 0, &key, &t->dv_entries)) {
extractDVdouble(&t->dv_entries, key, &offset, &consumption_at_set_date_m3_);
t->addMoreExplanation(offset, " consumption at set date (%f m3)", consumption_at_set_date_m3_);
}
if (findKey(MeasurementType::Instantaneous, VIFRange::Date, 1, 0, &key, &t->dv_entries)) {
struct tm date;
extractDVdate(&t->dv_entries, key, &offset, &date);
set_date_ = strdate(&date);
t->addMoreExplanation(offset, " set date (%s)", set_date_.c_str());
}
if(findKey(MeasurementType::Instantaneous, VIFRange::Volume, 2, 0, &key, &t->dv_entries)) {
extractDVdouble(&t->dv_entries, key, &offset, &consumption_at_set_date_2_m3_);
t->addMoreExplanation(offset, " consumption at set date 2 (%f m3)", consumption_at_set_date_2_m3_);
}
if (findKey(MeasurementType::Instantaneous, VIFRange::Date, 2, 0, &key, &t->dv_entries)) {
struct tm date;
extractDVdate(&t->dv_entries, key, &offset, &date);
set_date_2_ = strdate(&date);
t->addMoreExplanation(offset, " set date 2 (%s)", set_date_2_.c_str());
}
if(findKey(MeasurementType::Maximum, VIFRange::VolumeFlow, 3, 0, &key, &t->dv_entries)) {
extractDVdouble(&t->dv_entries, key, &offset, &max_flow_since_datetime_m3h_);
t->addMoreExplanation(offset, " max flow (%f m3/h)", max_flow_since_datetime_m3h_);
}
if (findKey(MeasurementType::Instantaneous, VIFRange::DateTime, 3, 0, &key, &t->dv_entries)) {
struct tm datetime;
extractDVdate(&t->dv_entries, key, &offset, &datetime);
max_flow_datetime_ = strdatetime(&datetime);
t->addMoreExplanation(offset, " max flow since datetime (%s)", max_flow_datetime_.c_str());
}
uint8_t month_increment = 0;
extractDVuint8(&t->dv_entries, "8104FD28", &offset, &month_increment);
t->addMoreExplanation(offset, " month increment (%d)", month_increment);
struct tm date;
if (findKey(MeasurementType::Instantaneous, VIFRange::Date, 8, 0, &key, &t->dv_entries)) {
extractDVdate(&t->dv_entries, key, &offset, &date);
string start = strdate(&date);
t->addMoreExplanation(offset, " history starts with date (%s)", start.c_str());
}
// 12 months of historical data, starting in storage 8.
for (int i=1; i<=12; ++i)
{
if(findKey(MeasurementType::Instantaneous, VIFRange::Volume, i+7, 0, &key, &t->dv_entries)) {
extractDVdouble(&t->dv_entries, key, &offset, &consumption_at_history_date_m3_[i-1]);
t->addMoreExplanation(offset, " consumption at history %d (%f m3)", i, consumption_at_history_date_m3_[i-1]);
struct tm d = date;
if (i>1) addMonths(&d, 1-i);
history_date_[i-1] = strdate(&d);
}
}
}
double MeterEvo868::totalWaterConsumption(Unit u)
{
assertQuantity(u, Quantity::Volume);
return convert(total_water_consumption_m3_, Unit::M3, u);
}
bool MeterEvo868::hasTotalWaterConsumption()
{
return true;
}
string MeterEvo868::status()
{
if (error_flags_ == 0)
{
return "OK";
}
/* Possible errors according to datasheet:
overflow (threshold configurable, must be activated)
backflow (threshold set, configurable)
leak
meter blocked
non-used (days threshold set, configurable)
magnetic and mechanical tampering (removal)
*/
// How do we decode these?
string info;
strprintf(&info, "ERROR bits %08x", error_flags_);
return info;
}

Wyświetl plik

@ -1871,7 +1871,8 @@ bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<ucha
void MeterCommonImplementation::processFieldExtractors(Telegram *t)
{
map<FieldInfo*,DVEntry*> found;
// Multiple dventries can be matched against a single wildcard FieldInfo.
map<FieldInfo*,set<DVEntry*>> founds;
// Sort the dv_entries based on their offset in the telegram.
// I.e. restore the ordering that was implicit in the telegram.
@ -1902,14 +1903,14 @@ void MeterCommonImplementation::processFieldExtractors(Telegram *t)
{
current_match_nr++;
if (fi.matcher().index_nr != IndexNr(current_match_nr))
if (fi.matcher().index_nr != IndexNr(current_match_nr) &&
!fi.matcher().expectedToMatchAgainstMultipleEntries())
{
// This field info did match, but requires another index nr!
// Increment the current index nr and look for the next match.
}
else if (found.count(&fi) == 0)
else if (founds[&fi].count(dve) == 0 || fi.matcher().expectedToMatchAgainstMultipleEntries())
{
// This field_info has not been matched to a dv_entry before!
debug("(meters) using field info %s(%s)[%d] to extract %s at offset %d\n",
fi.vname().c_str(),
toString(fi.xuantity()),
@ -1919,19 +1920,27 @@ void MeterCommonImplementation::processFieldExtractors(Telegram *t)
dve->addFieldInfo(&fi);
fi.performExtraction(this, t, dve);
found[&fi] = dve;
founds[&fi].insert(dve);
}
else
{
DVEntry *old = found[&fi];
if (isVerboseEnabled())
{
set<DVEntry*> old = founds[&fi];
string olds;
for (DVEntry *dve : old)
{
olds += to_string(dve->offset)+",";
}
olds.pop_back();
verbose("(meter) while processing field extractors ignoring dventry %s at offset %d matching since "
"field %s was already matched against dventry %s at offset %d !\n",
dve->dif_vif_key.str().c_str(),
dve->offset,
fi.vname().c_str(),
old->dif_vif_key.str().c_str(),
old->offset);
verbose("(meter) while processing field extractors ignoring dventry %s at offset %d matching since "
"field %s was already matched against offsets %s !\n",
dve->dif_vif_key.str().c_str(),
dve->offset,
fi.vname().c_str(),
olds.c_str());
}
}
}
}
@ -1945,7 +1954,7 @@ void MeterCommonImplementation::processFieldExtractors(Telegram *t)
{
fi.performExtraction(this, t, NULL);
}
else if (found.count(&fi) == 0 && fi.printProperties().hasJOINTPLSTATUS())
else if (founds.count(&fi) == 0 && fi.printProperties().hasJOINTPLSTATUS())
{
// This is a status field and it joins the tpl status but it also
// has a potential dve match, which did not trigger. Now
@ -2144,15 +2153,22 @@ FieldInfo::FieldInfo(int index,
string FieldInfo::renderJsonOnlyDefaultUnit(Meter *m)
{
return renderJson(m, NULL);
return renderJson(m, NULL, NULL);
}
string FieldInfo::renderJsonText(Meter *m)
{
return renderJson(m, NULL);
return renderJson(m, NULL, NULL);
}
string FieldInfo::generateFieldName(DVEntry *dve)
string FieldInfo::generateFieldNameNoUnit(DVEntry *dve)
{
if (!valid_field_name_) return "bad_field_name";
return field_name_->apply(dve);
}
string FieldInfo::generateFieldNameWithUnit(DVEntry *dve)
{
if (!valid_field_name_) return "bad_field_name";
@ -2167,12 +2183,14 @@ string FieldInfo::generateFieldName(DVEntry *dve)
return var+"_"+default_unit;
}
string FieldInfo::renderJson(Meter *m, vector<Unit> *conversions)
string FieldInfo::renderJson(Meter *m, DVEntry *dve, vector<Unit> *conversions)
{
string s;
string default_unit = unitToStringLowerCase(defaultUnit());
string var = vname();
string field_name = generateFieldNameNoUnit(dve);
if (xuantity() == Quantity::Text)
{
string v = m->getStringValue(this);
@ -2182,18 +2200,18 @@ string FieldInfo::renderJson(Meter *m, vector<Unit> *conversions)
// be translated into "something":null in the json, indicating that there is no value.
// This should not be a problem for now. Lets deal with it when a meter decides to send "null"
// as its version string for example.
s += "\""+var+"\":null";
s += "\""+field_name+"\":null";
}
else
{
// Normally the string values are quoted in json. TODO quote the value properly.
// A well crafted meter could send a version string with " and break the json format.
s += "\""+var+"\":\""+v+"\"";
s += "\""+field_name+"\":\""+v+"\"";
}
}
else
{
s += "\""+var+"_"+default_unit+"\":"+valueToString(m->getNumericValue(this, defaultUnit()), defaultUnit());
s += "\""+field_name+"_"+default_unit+"\":"+valueToString(m->getNumericValue(this, defaultUnit()), defaultUnit());
if (conversions != NULL)
{
@ -2202,7 +2220,7 @@ string FieldInfo::renderJson(Meter *m, vector<Unit> *conversions)
{
string unit = unitToStringLowerCase(u);
// Appending extra conversion unit.
s += ",\""+var+"_"+unit+"\":"+valueToString(m->getNumericValue(this, u), u);
s += ",\""+field_name+"_"+unit+"\":"+valueToString(m->getNumericValue(this, u), u);
}
}
}
@ -2259,7 +2277,7 @@ void MeterCommonImplementation::printMeter(Telegram *t,
}
// Iterate over the meter field infos...
map<FieldInfo*,DVEntry*> found; // Found from the telegram
map<FieldInfo*,set<DVEntry*>> founds; // Multiple dventries can match to a single field info.
set<string> found_vnames;
for (FieldInfo& fi : field_infos_)
@ -2274,10 +2292,11 @@ void MeterCommonImplementation::printMeter(Telegram *t,
// Has the entry been matches to this field, then print it as json.
if (dve->hasFieldInfo(&fi))
{
assert(found.count(&fi) == 0);
assert(founds[&fi].count(dve) == 0);
found[&fi] = dve;
found_vnames.insert(fi.vname());
founds[&fi].insert(dve);
string field_name = fi.generateFieldNameNoUnit(dve);
found_vnames.insert(field_name);
}
}
}
@ -2287,17 +2306,20 @@ void MeterCommonImplementation::printMeter(Telegram *t,
{
if (fi.printProperties().hasJSON())
{
if (found.count(&fi) != 0)
if (founds.count(&fi) != 0)
{
DVEntry *dve = found[&fi];
debug("(meters) render field %s(%s %s)[%d] with dventry @%d key %s data %s\n",
fi.vname().c_str(), toString(fi.xuantity()), unitToStringLowerCase(fi.defaultUnit()).c_str(), fi.index(),
dve->offset,
dve->dif_vif_key.str().c_str(),
dve->value.c_str());
string out = fi.renderJson(this, &conversions());
debug("(meters) %s\n", out.c_str());
s += indent+out+","+newline;
// This field info has matched against some dventries.
for (DVEntry *dve : founds[&fi])
{
debug("(meters) render field %s(%s %s)[%d] with dventry @%d key %s data %s\n",
fi.vname().c_str(), toString(fi.xuantity()), unitToStringLowerCase(fi.defaultUnit()).c_str(), fi.index(),
dve->offset,
dve->dif_vif_key.str().c_str(),
dve->value.c_str());
string out = fi.renderJson(this, dve, &conversions());
debug("(meters) %s\n", out.c_str());
s += indent+out+","+newline;
}
}
else
{
@ -2314,7 +2336,7 @@ void MeterCommonImplementation::printMeter(Telegram *t,
// Or if no value has been received, null.
debug("(meters) render field %s(%s)[%d] without dventry\n",
fi.vname().c_str(), toString(fi.xuantity()), fi.index());
string out = fi.renderJson(this, &conversions());
string out = fi.renderJson(this, NULL, &conversions());
debug("(meters) %s\n", out.c_str());
s += indent+out+","+newline;
}
@ -2813,8 +2835,11 @@ bool FieldInfo::extractNumeric(Meter *m, Telegram *t, DVEntry *dve)
assert(dve != NULL);
assert(key == "" || dve->dif_vif_key.str() == key);
// Generate the json field name:
string field_name = generateFieldName(dve);
string field_name;
if (isDebugEnabled())
{
field_name = generateFieldNameWithUnit(dve);
}
double extracted_double_value = NAN;
if (dve->extractDouble(&extracted_double_value,
@ -2848,7 +2873,7 @@ bool FieldInfo::extractNumeric(Meter *m, Telegram *t, DVEntry *dve)
extracted_double_value);
}
m->setNumericValue(this, default_unit_, convert(extracted_double_value, decoded_unit, default_unit_));
t->addMoreExplanation(dve->offset, renderJson(m, &m->conversions()));
t->addMoreExplanation(dve->offset, renderJson(m, dve, &m->conversions()));
found = true;
}
return found;
@ -2939,7 +2964,7 @@ bool FieldInfo::extractString(Meter *m, Telegram *t, DVEntry *dve)
assert(key == "" || dve->dif_vif_key.str() == key);
// Generate the json field name:
string field_name = generateFieldName(dve);
string field_name = generateFieldNameNoUnit(dve);
uint64_t extracted_bits {};
if (lookup_.hasLookups() || (print_properties_.hasJOINTPLSTATUS()))

Wyświetl plik

@ -62,7 +62,6 @@ LIST_OF_METER_TYPES
X(auto, 0, AutoMeter, AUTO, Auto) \
X(unknown, 0, UnknownMeter, UNKNOWN, Unknown) \
X(eurisii, T1_bit, HeatCostAllocationMeter, EURISII, EurisII) \
X(evo868, T1_bit, WaterMeter, EVO868, EVO868) \
X(gransystems,T1_bit, ElectricityMeter, CCx01, CCx01) \
X(hydrocalm3, T1_bit, HeatMeter, HYDROCALM3, HydrocalM3) \
X(hydrodigit, T1_bit, WaterMeter, HYDRODIGIT, Hydrodigit) \
@ -350,13 +349,14 @@ struct FieldInfo
void performCalculation(Meter *m);
string renderJsonOnlyDefaultUnit(Meter *m);
string renderJson(Meter *m, vector<Unit> *additional_conversions);
string renderJson(Meter *m, DVEntry *dve, vector<Unit> *additional_conversions);
string renderJsonText(Meter *m);
// Render the field name based on the actual field from the telegram.
// A FieldInfo can be declared to handle any number of storage fields of a certain range.
// The vname is then a pattern total_at_month_{storage_counter} that gets translated into
// total_at_month_2 (for the dventry with storage nr 2.)
string generateFieldName(DVEntry *dve);
string generateFieldNameWithUnit(DVEntry *dve);
string generateFieldNameNoUnit(DVEntry *dve);
// Check if the meter object stores a value for this field.
bool hasValue(Meter *m);

Wyświetl plik

@ -1790,6 +1790,9 @@ LIST_OF_QUANTITIES
test_si_convert(1.0, 60.0, Unit::Minute, "min", Unit::Second, "s", Quantity::Time, &from_set, &to_set);
// 1 day is 1/365.2425 year
test_si_convert(1.0, 1.0/365.2425, Unit::Day, "d", Unit::Year, "y", Quantity::Time, &from_set, &to_set);
// 1 month is 30.437 days
test_si_convert(2.0, 30.437*2, Unit::Month, "month", Unit::Day, "d", Quantity::Time, &from_set, &to_set);
test_si_convert(30.437*2, 2.0, Unit::Day, "d", Unit::Month, "month", Quantity::Time, &from_set, &to_set);
// 100 hours is 100/24 days.
test_si_convert(100.0, 100.0/24.0, Unit::Hour, "h", Unit::Day, "d", Quantity::Time, &from_set, &to_set);
// 1 year is 365.2425 days.

Wyświetl plik

@ -93,6 +93,7 @@ using namespace std;
X(Minute, 60.0, SIExp().s(1)) \
X(Hour, 3600.0, SIExp().s(1)) \
X(Day, 3600.0*24, SIExp().s(1)) \
X(Month, 3600.0*24*30.437, SIExp().s(1)) \
X(Year, 3600.0*24*365.2425, SIExp().s(1)) \
X(DateTimeUT, 1.0, SIExp().s(1)) \
X(DateTimeUTC, 1.0, SIExp().s(1)) \

Wyświetl plik

@ -102,6 +102,7 @@ LIST_OF_QUANTITIES
X(Minute,min,"min",Time,"minute") \
X(Hour,h,"h",Time,"hour") \
X(Day,d,"d",Time,"day") \
X(Month,month,"month",Time,"month") \
X(Year,y,"y",Time,"year") \
X(DateTimeUT,ut,"ut",PointInTime,"unix timestamp") \
X(DateTimeUTC,utc,"utc",PointInTime,"coordinated universal time") \

Wyświetl plik

@ -9,6 +9,12 @@ then
exit 1
fi
if ! command -v jq > /dev/null
then
echo "You have to install jq !"
exit 1
fi
$TESTINTERNAL
if [ "$?" = "0" ]; then
echo OK: test internals

Wyświetl plik

@ -8,15 +8,15 @@ TEST=testoutput
TESTNAME="Test ANYID"
TESTRESULT="ERROR"
cat > $TEST/test_expected.txt <<EOF
cat <<EOF | jq --sort-keys . > $TEST/test_expected.txt
{"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 \
Vatten auto ANYID NOKEY > $TEST/test_output.txt 2>&1
Vatten auto ANYID NOKEY 2>&1 | jq --sort-keys . > $TEST/test_output.txt
if [ "$?" = "0" ]
then
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
cat $TEST/test_output.txt | sed 's/"timestamp": "....-..-..T..:..:..Z"/"timestamp": "1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
diff $TEST/test_expected.txt $TEST/test_responses.txt
if [ "$?" = "0" ]
then

Wyświetl plik

@ -9,7 +9,7 @@ TEST=testoutput
TESTNAME="Test C1 meters"
TESTRESULT="ERROR"
cat simulations/simulation_c1.txt | grep '^{' > $TEST/test_expected.txt
cat simulations/simulation_c1.txt | grep '^{' | jq --sort-keys . > $TEST/test_expected.txt
$PROG --format=json simulations/simulation_c1.txt \
MyHeater multical302 67676767 NOKEY \
MyHeaterMj multical302 46464646 NOKEY \
@ -27,11 +27,11 @@ $PROG --format=json simulations/simulation_c1.txt \
Smokey ei6500 00012811 NOKEY \
Vatten weh_07 86868686 NOKEY \
Mino minomess 55036410 NOKEY \
> $TEST/test_output.txt 2> $TEST/test_stderr.txt
| jq --sort-keys . > $TEST/test_output.txt 2> $TEST/test_stderr.txt
if [ "$?" = "0" ]
then
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
cat $TEST/test_output.txt | sed 's/"timestamp": "....-..-..T..:..:..Z"/"timestamp": "1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
diff $TEST/test_expected.txt $TEST/test_responses.txt
if [ "$?" = "0" ]
then

Wyświetl plik

@ -41,8 +41,6 @@ do
echo OK json: $TESTNAME
TESTRESULT="OK"
else
jq --sort-keys . $TEST/test_expected_json.txt
jq --sort-keys . $TEST/test_response_json.txt
TESTRESULT="ERROR"
fi
else

Wyświetl plik

@ -8,9 +8,9 @@ TEST=testoutput
TESTNAME="Test list-envs and list-fields"
TESTRESULT="ERROR"
$PROG --listenvs=amiplus > $TEST/test_output.txt 2>&1
$PROG --listenvs=amiplus | sort > $TEST/test_output.txt 2>&1
cat <<EOF > $TEST/test_expected.txt
cat <<EOF | sort > $TEST/test_expected.txt
METER_JSON
METER_ID
METER_NAME
@ -54,9 +54,9 @@ then
exit 1
fi
$PROG --listfields=amiplus > $TEST/test_output.txt 2>&1
$PROG --listfields=amiplus | sort > $TEST/test_output.txt 2>&1
cat <<EOF > $TEST/test_expected.txt
cat <<EOF | sort > $TEST/test_expected.txt
id The meter id number.
name Your name for the meter.
media What does the meter measure?

Wyświetl plik

@ -255,11 +255,6 @@ Received telegram from: 66336633
type: Heat meter (0x43)
ver: 0x39
driver: compact5
Received telegram from: 79787776
manufacturer: (MAD) Maddalena, Italy (0x3424)
type: Water meter (0x07)
ver: 0x50
driver: evo868
Received telegram from: 18046178
manufacturer: (GSS) R D Gran System S, Belarus (0x1e73)
type: Electricity meter (0x02)

Wyświetl plik

@ -9,15 +9,15 @@ TEST=testoutput
TESTNAME="Test mbus"
TESTRESULT="ERROR"
cat simulations/simulation_mbus.txt | grep '^{' > $TEST/test_expected.txt
cat simulations/simulation_mbus.txt | grep '^{' | jq --sort-keys . > $TEST/test_expected.txt
$PROG --format=json simulations/simulation_mbus.txt \
MyUltra ultraheat 70444600 NOKEY \
MySenso sensostar 10484075 NOKEY \
> $TEST/test_output.txt 2> $TEST/test_stderr.txt
2> $TEST/test_stderr.txt | jq --sort-keys . > $TEST/test_output.txt
if [ "$?" = "0" ]
then
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
cat $TEST/test_output.txt | sed 's/"timestamp": "....-..-..T..:..:..Z"/"timestamp": "1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
diff $TEST/test_expected.txt $TEST/test_responses.txt
if [ "$?" = "0" ]
then

Wyświetl plik

@ -14,11 +14,11 @@ METERS="HCA auto 91835132 NOKEY \
QWW auto 11121314 NOKEY \
QWWW auto 12417708 NOKEY"
cat simulations/simulation_s1.txt | grep '^{' > $TEST/test_expected.txt
$PROG --format=json simulations/simulation_s1.txt $METERS > $TEST/test_output.txt 2> $TEST/test_stderr.txt
cat simulations/simulation_s1.txt | grep '^{' | jq --sort-keys . > $TEST/test_expected.txt
$PROG --format=json simulations/simulation_s1.txt $METERS 2> $TEST/test_stderr.txt | jq --sort-keys . > $TEST/test_output.txt
if [ "$?" = "0" ]
then
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
cat $TEST/test_output.txt | sed 's/"timestamp": "....-..-..T..:..:..Z"/"timestamp": "1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
diff $TEST/test_expected.txt $TEST/test_responses.txt
if [ "$?" = "0" ]
then

Wyświetl plik

@ -24,7 +24,6 @@ METERS="MyWarmWater supercom587 12345678 NOKEY
Gran101 gransystems 18046178 NOKEY
Gran301 gransystems 20100117 NOKEY
HeatMeter eurisii 88018801 NOKEY
Votchka evo868 79787776 NOKEY
Smokeo lansensm 00010204 NOKEY
Tempoo lansenth 00010203 NOKEY
Dooro lansendw 00010205 NOKEY
@ -70,11 +69,11 @@ METERS="MyWarmWater supercom587 12345678 NOKEY
QSmokep qsmoke 48128850 NOKEY"
cat simulations/simulation_t1.txt | grep '^{' > $TEST/test_expected.txt
$PROG --format=json simulations/simulation_t1.txt $METERS > $TEST/test_output.txt 2> $TEST/test_stderr.txt
cat simulations/simulation_t1.txt | grep '^{' | jq --sort-keys . > $TEST/test_expected.txt
$PROG --format=json simulations/simulation_t1.txt $METERS 2> $TEST/test_stderr.txt | jq --sort-keys . > $TEST/test_output.txt
if [ "$?" = "0" ]
then
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
cat $TEST/test_output.txt | sed 's/"timestamp": "....-..-..T..:..:..Z"/"timestamp": "1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
diff $TEST/test_expected.txt $TEST/test_responses.txt
if [ "$?" = "0" ]
then