kopia lustrzana https://github.com/weetmuts/wmbusmeters
Add new handling of meter drivers.
rodzic
ff6f207463
commit
e3e2941c32
|
@ -5,9 +5,9 @@ telegram=|2e4433300502010007ff7ab66800002f2f02fd1b550002fd971d01000efd3a23000000
|
||||||
|
|
||||||
# Now use a correct telegram but supply the wrong meter driver in the test.
|
# Now use a correct telegram but supply the wrong meter driver in the test.
|
||||||
|
|
||||||
telegram=|2e44333006020100071d7ab66800002f2f02fd1b550002fd971d01000efd3a2300000000008e40fd3a000000000000|
|
#elegram=|2e44333006020100071d7ab66800002f2f02fd1b550002fd971d01000efd3a2300000000008e40fd3a000000000000|
|
||||||
|
|
||||||
# Run telegrams again, the warnings should not be printed again.
|
# Run telegrams again, the warnings should not be printed again.
|
||||||
|
|
||||||
telegram=|2e4433300502010007ff7ab66800002f2f02fd1b550002fd971d01000efd3a2300000000008e40fd3a000000000000|
|
#elegram=|2e4433300502010007ff7ab66800002f2f02fd1b550002fd971d01000efd3a2300000000008e40fd3a000000000000|
|
||||||
telegram=|2e44333006020100071d7ab66800002f2f02fd1b550002fd971d01000efd3a2300000000008e40fd3a000000000000|
|
#elegram=|2e44333006020100071d7ab66800002f2f02fd1b550002fd971d01000efd3a2300000000008e40fd3a000000000000|
|
||||||
|
|
|
@ -621,7 +621,11 @@ shared_ptr<Configuration> parseCommandLine(int argc, char **argv) {
|
||||||
MeterInfo mi;
|
MeterInfo mi;
|
||||||
mi.parse(name, driver, id, key);
|
mi.parse(name, driver, id, key);
|
||||||
|
|
||||||
if (mi.driver == MeterDriver::UNKNOWN) error("Not a valid meter driver \"%s\"\n", driver.c_str());
|
if (mi.driver == MeterDriver::UNKNOWN &&
|
||||||
|
mi.driver_name.str() == "")
|
||||||
|
{
|
||||||
|
error("Not a valid meter driver \"%s\"\n", driver.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
LinkModeSet default_modes = toMeterLinkModeSet(mi.driver);
|
LinkModeSet default_modes = toMeterLinkModeSet(mi.driver);
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,8 @@ const char *toString(ValueInformation v)
|
||||||
{
|
{
|
||||||
switch (v) {
|
switch (v) {
|
||||||
case ValueInformation::None: return "None";
|
case ValueInformation::None: return "None";
|
||||||
#define X(name,from,to) case ValueInformation::name: return #name;
|
case ValueInformation::Any: return "Any";
|
||||||
|
#define X(name,from,to,quantity) case ValueInformation::name: return #name;
|
||||||
LIST_OF_VALUETYPES
|
LIST_OF_VALUETYPES
|
||||||
#undef X
|
#undef X
|
||||||
}
|
}
|
||||||
|
@ -42,7 +43,7 @@ LIST_OF_VALUETYPES
|
||||||
|
|
||||||
ValueInformation toValueInformation(int i)
|
ValueInformation toValueInformation(int i)
|
||||||
{
|
{
|
||||||
#define X(name,from,to) if (from <= i && i <= to) return ValueInformation::name;
|
#define X(name,from,to,quantity) if (from <= i && i <= to) return ValueInformation::name;
|
||||||
LIST_OF_VALUETYPES
|
LIST_OF_VALUETYPES
|
||||||
#undef X
|
#undef X
|
||||||
return ValueInformation::None;
|
return ValueInformation::None;
|
||||||
|
@ -318,8 +319,9 @@ bool parseDV(Telegram *t,
|
||||||
void valueInfoRange(ValueInformation v, int *low, int *hi)
|
void valueInfoRange(ValueInformation v, int *low, int *hi)
|
||||||
{
|
{
|
||||||
switch (v) {
|
switch (v) {
|
||||||
|
case ValueInformation::Any:
|
||||||
case ValueInformation::None: *low = 0; *hi = 0; return;
|
case ValueInformation::None: *low = 0; *hi = 0; return;
|
||||||
#define X(name,from,to) case ValueInformation::name: *low = from; *hi = to; return;
|
#define X(name,from,to,quantity) case ValueInformation::name: *low = from; *hi = to; return;
|
||||||
LIST_OF_VALUETYPES
|
LIST_OF_VALUETYPES
|
||||||
#undef X
|
#undef X
|
||||||
}
|
}
|
||||||
|
@ -521,7 +523,7 @@ bool extractDVdouble(map<string,pair<int,DVEntry>> *values,
|
||||||
{
|
{
|
||||||
vector<uchar> v;
|
vector<uchar> v;
|
||||||
hex2bin(p.second.value, &v);
|
hex2bin(p.second.value, &v);
|
||||||
unsigned int raw = 0;
|
uint64_t raw = 0;
|
||||||
if (t == 0x1) {
|
if (t == 0x1) {
|
||||||
assert(v.size() == 1);
|
assert(v.size() == 1);
|
||||||
raw = v[0];
|
raw = v[0];
|
||||||
|
@ -569,7 +571,7 @@ bool extractDVdouble(map<string,pair<int,DVEntry>> *values,
|
||||||
{
|
{
|
||||||
// 74140000 -> 00001474
|
// 74140000 -> 00001474
|
||||||
string& v = p.second.value;
|
string& v = p.second.value;
|
||||||
unsigned int raw = 0;
|
uint64_t raw = 0;
|
||||||
if (t == 0x9) {
|
if (t == 0x9) {
|
||||||
assert(v.size() == 2);
|
assert(v.size() == 2);
|
||||||
raw = (v[0]-'0')*10 + (v[1]-'0');
|
raw = (v[0]-'0')*10 + (v[1]-'0');
|
||||||
|
|
|
@ -28,25 +28,26 @@
|
||||||
#include<vector>
|
#include<vector>
|
||||||
|
|
||||||
#define LIST_OF_VALUETYPES \
|
#define LIST_OF_VALUETYPES \
|
||||||
X(Volume,0x10,0x17) \
|
X(Volume,0x10,0x17,Quantity::Volume) \
|
||||||
X(OperatingTime,0x24,0x27) \
|
X(OperatingTime,0x24,0x27, Quantity::Time) \
|
||||||
X(VolumeFlow,0x38,0x3F) \
|
X(VolumeFlow,0x38,0x3F, Quantity::Flow) \
|
||||||
X(FlowTemperature,0x58,0x5B) \
|
X(FlowTemperature,0x58,0x5B, Quantity::Temperature) \
|
||||||
X(ReturnTemperature,0x5C,0x5F) \
|
X(ReturnTemperature,0x5C,0x5F, Quantity::Temperature) \
|
||||||
X(TemperatureDifference,0x60,0x63) \
|
X(TemperatureDifference,0x60,0x63, Quantity::Temperature) \
|
||||||
X(ExternalTemperature,0x64,0x67) \
|
X(ExternalTemperature,0x64,0x67, Quantity::Temperature) \
|
||||||
X(HeatCostAllocation,0x6E,0x6E) \
|
X(HeatCostAllocation,0x6E,0x6E, Quantity::HCA) \
|
||||||
X(Date,0x6C,0x6C) \
|
X(Date,0x6C,0x6C, Quantity::PointInTime) \
|
||||||
X(DateTime,0x6D,0x6D) \
|
X(DateTime,0x6D,0x6D, Quantity::PointInTime) \
|
||||||
X(EnergyMJ,0x0E,0x0F) \
|
X(EnergyMJ,0x0E,0x0F, Quantity::Energy) \
|
||||||
X(EnergyWh,0x00,0x07) \
|
X(EnergyWh,0x00,0x07, Quantity::Energy) \
|
||||||
X(PowerW,0x28,0x2f) \
|
X(PowerW,0x28,0x2f, Quantity::Power) \
|
||||||
X(ActualityDuration,0x74,0x77) \
|
X(ActualityDuration,0x74,0x77, Quantity::Time) \
|
||||||
|
|
||||||
enum class ValueInformation
|
enum class ValueInformation
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
#define X(name,from,to) name,
|
Any,
|
||||||
|
#define X(name,from,to,quantity) name,
|
||||||
LIST_OF_VALUETYPES
|
LIST_OF_VALUETYPES
|
||||||
#undef X
|
#undef X
|
||||||
};
|
};
|
||||||
|
@ -54,6 +55,52 @@ LIST_OF_VALUETYPES
|
||||||
const char *toString(ValueInformation v);
|
const char *toString(ValueInformation v);
|
||||||
ValueInformation toValueInformation(int i);
|
ValueInformation toValueInformation(int i);
|
||||||
|
|
||||||
|
struct DifVifKey
|
||||||
|
{
|
||||||
|
DifVifKey(string key) : key_(key) {}
|
||||||
|
string str() { return key_; }
|
||||||
|
bool useSearchInstead() { return key_ == ""; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
string key_;
|
||||||
|
};
|
||||||
|
|
||||||
|
static DifVifKey NoDifVifKey = DifVifKey("");
|
||||||
|
|
||||||
|
struct StorageNr
|
||||||
|
{
|
||||||
|
StorageNr(int n) : nr_(n) {}
|
||||||
|
int intValue() { return nr_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int nr_;
|
||||||
|
};
|
||||||
|
|
||||||
|
static StorageNr AnyStorageNr = StorageNr(-1);
|
||||||
|
|
||||||
|
struct TariffNr
|
||||||
|
{
|
||||||
|
TariffNr(int n) : nr_(n) {}
|
||||||
|
int intValue() { return nr_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int nr_;
|
||||||
|
};
|
||||||
|
|
||||||
|
static TariffNr AnyTariffNr = TariffNr(-1);
|
||||||
|
|
||||||
|
struct IndexNr
|
||||||
|
{
|
||||||
|
IndexNr(int n) : nr_(n) {}
|
||||||
|
int intValue() { return nr_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int nr_;
|
||||||
|
};
|
||||||
|
|
||||||
|
static IndexNr AnyIndexNr = IndexNr(-1);
|
||||||
|
|
||||||
bool loadFormatBytesFromSignature(uint16_t format_signature, vector<uchar> *format_bytes);
|
bool loadFormatBytesFromSignature(uint16_t format_signature, vector<uchar> *format_bytes);
|
||||||
|
|
||||||
bool parseDV(Telegram *t,
|
bool parseDV(Telegram *t,
|
||||||
|
@ -74,7 +121,7 @@ bool findKey(MeasurementType mt, ValueInformation vi, int storagenr, int tariffn
|
||||||
// Some meters have multiple identical DIF/VIF values! Meh, they are not using storage nrs or tariff nrs.
|
// Some meters have multiple identical DIF/VIF values! Meh, they are not using storage nrs or tariff nrs.
|
||||||
// So here we can pick for example nr 2 of an identical set if DIF/VIF values.
|
// So here we can pick for example nr 2 of an identical set if DIF/VIF values.
|
||||||
// Nr 1 means the first found value.
|
// Nr 1 means the first found value.
|
||||||
bool findKeyWithNr(MeasurementType mt, ValueInformation vi, int storagenr, int tariffnr, int nr,
|
bool findKeyWithNr(MeasurementType mt, ValueInformation vi, int storagenr, int tariffnr, int indexnr,
|
||||||
std::string *key, std::map<std::string,std::pair<int,DVEntry>> *values);
|
std::string *key, std::map<std::string,std::pair<int,DVEntry>> *values);
|
||||||
|
|
||||||
#define ANY_STORAGENR -1
|
#define ANY_STORAGENR -1
|
||||||
|
|
19
src/main.cc
19
src/main.cc
|
@ -214,7 +214,7 @@ void list_fields(Configuration *config, string meter_driver)
|
||||||
int width = 13; // Width of timestamp_utc
|
int width = 13; // Width of timestamp_utc
|
||||||
for (auto &p : meter->prints())
|
for (auto &p : meter->prints())
|
||||||
{
|
{
|
||||||
if ((int)p.field_name.size() > width) width = p.field_name.size();
|
if ((int)p.fieldName().size() > width) width = p.fieldName().size();
|
||||||
}
|
}
|
||||||
|
|
||||||
string id = padLeft("id", width);
|
string id = padLeft("id", width);
|
||||||
|
@ -239,9 +239,9 @@ void list_fields(Configuration *config, string meter_driver)
|
||||||
printf("%s The rssi for the received telegram as reported by the device.\n", rssi.c_str());
|
printf("%s The rssi for the received telegram as reported by the device.\n", rssi.c_str());
|
||||||
for (auto &p : meter->prints())
|
for (auto &p : meter->prints())
|
||||||
{
|
{
|
||||||
if (p.vname == "") continue;
|
if (p.vname() == "") continue;
|
||||||
string fn = padLeft(p.field_name, width);
|
string fn = padLeft(p.fieldName(), width);
|
||||||
printf("%s %s\n", fn.c_str(), p.help.c_str());
|
printf("%s %s\n", fn.c_str(), p.help().c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,6 +254,17 @@ void list_meters(Configuration *config)
|
||||||
printf("%-14s %s\n", #mname, #info);
|
printf("%-14s %s\n", #mname, #info);
|
||||||
LIST_OF_METERS
|
LIST_OF_METERS
|
||||||
#undef X
|
#undef X
|
||||||
|
|
||||||
|
for (DriverInfo &di : allRegisteredDrivers())
|
||||||
|
{
|
||||||
|
string mname = di.name().str();
|
||||||
|
const char *info = toString(di.type());
|
||||||
|
|
||||||
|
if (config->list_meters_search == "" || \
|
||||||
|
stringFoundCaseIgnored(info, config->list_meters_search) || \
|
||||||
|
stringFoundCaseIgnored(mname.c_str(), config->list_meters_search)) \
|
||||||
|
printf("%-14s %s\n", mname.c_str(), info);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TmpUnit
|
struct TmpUnit
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright (C) 2019-2020 Fredrik Öhrström
|
Copyright (C) 2019-2021 Fredrik Öhrström
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -22,153 +22,164 @@
|
||||||
#include"wmbus_utils.h"
|
#include"wmbus_utils.h"
|
||||||
#include"util.h"
|
#include"util.h"
|
||||||
|
|
||||||
struct MeterAmiplus : public virtual MeterCommonImplementation {
|
struct MeterAmiplus : public virtual MeterCommonImplementation
|
||||||
MeterAmiplus(MeterInfo &mi);
|
{
|
||||||
|
MeterAmiplus(MeterInfo &mi, DriverInfo &di);
|
||||||
double totalEnergyConsumption(Unit u);
|
|
||||||
double currentPowerConsumption(Unit u);
|
|
||||||
double totalEnergyProduction(Unit u);
|
|
||||||
double currentPowerProduction(Unit u);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
void processContent(Telegram *t);
|
double total_energy_consumption_kwh_ {};
|
||||||
|
double current_power_consumption_kw_ {};
|
||||||
double total_energy_kwh_ {};
|
double total_energy_production_kwh_ {};
|
||||||
double current_power_kw_ {};
|
double current_power_production_kw_ {};
|
||||||
double total_energy_returned_kwh_ {};
|
double phase_1_v_ {};
|
||||||
double current_power_returned_kw_ {};
|
double phase_2_v_ {};
|
||||||
double voltage_L_[3]{0, 0, 0};
|
double phase_3_v_ {};
|
||||||
|
|
||||||
string device_date_time_;
|
string device_date_time_;
|
||||||
};
|
};
|
||||||
|
|
||||||
MeterAmiplus::MeterAmiplus(MeterInfo &mi) :
|
static bool ok = registerDriver([](DriverInfo&di)
|
||||||
MeterCommonImplementation(mi, "amiplus")
|
|
||||||
{
|
{
|
||||||
setMeterType(MeterType::ElectricityMeter);
|
di.setName("amiplus");
|
||||||
|
di.setMeterType(MeterType::ElectricityMeter);
|
||||||
|
di.setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||||
|
di.addLinkMode(LinkMode::T1);
|
||||||
|
di.addDetection(MANUFACTURER_APA, 0x02, 0x02);
|
||||||
|
di.addDetection(MANUFACTURER_DEV, 0x37, 0x02);
|
||||||
|
di.addDetection(MANUFACTURER_DEV, 0x02, 0x00);
|
||||||
|
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new MeterAmiplus(mi, di)); });
|
||||||
|
});
|
||||||
|
|
||||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
MeterAmiplus::MeterAmiplus(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||||
|
{
|
||||||
|
addFieldWithExtractor(
|
||||||
|
"total_energy_consumption",
|
||||||
|
Quantity::Energy,
|
||||||
|
NoDifVifKey,
|
||||||
|
VifScaling::Auto,
|
||||||
|
MeasurementType::Instantaneous,
|
||||||
|
ValueInformation::EnergyWh,
|
||||||
|
StorageNr(0),
|
||||||
|
TariffNr(0),
|
||||||
|
IndexNr(1),
|
||||||
|
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||||
|
"The total energy consumption recorded by this meter.",
|
||||||
|
SET_FUNC(total_energy_consumption_kwh_, Unit::KWH),
|
||||||
|
GET_FUNC(total_energy_consumption_kwh_, Unit::KWH));
|
||||||
|
|
||||||
addLinkMode(LinkMode::T1);
|
addFieldWithExtractor(
|
||||||
|
"current_power_consumption",
|
||||||
|
Quantity::Power,
|
||||||
|
NoDifVifKey,
|
||||||
|
VifScaling::Auto,
|
||||||
|
MeasurementType::Instantaneous,
|
||||||
|
ValueInformation::PowerW,
|
||||||
|
StorageNr(0),
|
||||||
|
TariffNr(0),
|
||||||
|
IndexNr(1),
|
||||||
|
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||||
|
"Current power consumption.",
|
||||||
|
SET_FUNC(current_power_consumption_kw_, Unit::KW),
|
||||||
|
GET_FUNC(current_power_consumption_kw_, Unit::KW));
|
||||||
|
|
||||||
addPrint("total_energy_consumption", Quantity::Energy,
|
addFieldWithExtractor(
|
||||||
[&](Unit u){ return totalEnergyConsumption(u); },
|
"total_energy_production",
|
||||||
"The total energy consumption recorded by this meter.",
|
Quantity::Energy,
|
||||||
true, true);
|
DifVifKey("0E833C"),
|
||||||
|
VifScaling::Auto,
|
||||||
|
MeasurementType::Unknown,
|
||||||
|
ValueInformation::None,
|
||||||
|
AnyStorageNr,
|
||||||
|
AnyTariffNr,
|
||||||
|
IndexNr(1),
|
||||||
|
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||||
|
"The total energy production recorded by this meter.",
|
||||||
|
SET_FUNC(total_energy_production_kwh_, Unit::KWH),
|
||||||
|
GET_FUNC(total_energy_production_kwh_, Unit::KWH));
|
||||||
|
|
||||||
addPrint("current_power_consumption", Quantity::Power,
|
addFieldWithExtractor(
|
||||||
[&](Unit u){ return currentPowerConsumption(u); },
|
"current_power_production",
|
||||||
"Current power consumption.",
|
Quantity::Power,
|
||||||
true, true);
|
DifVifKey("0BAB3C"),
|
||||||
|
VifScaling::Auto,
|
||||||
|
MeasurementType::Unknown,
|
||||||
|
ValueInformation::Any,
|
||||||
|
AnyStorageNr,
|
||||||
|
AnyTariffNr,
|
||||||
|
IndexNr(1),
|
||||||
|
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||||
|
"Current power production.",
|
||||||
|
SET_FUNC(current_power_production_kw_, Unit::KW),
|
||||||
|
GET_FUNC(current_power_production_kw_, Unit::KW));
|
||||||
|
|
||||||
addPrint("total_energy_production", Quantity::Energy,
|
addFieldWithExtractor(
|
||||||
[&](Unit u){ return totalEnergyProduction(u); },
|
"voltage_at_phase_1",
|
||||||
"The total energy production recorded by this meter.",
|
Quantity::Voltage,
|
||||||
true, true);
|
DifVifKey("0AFDC9FC01"),
|
||||||
|
VifScaling::None,
|
||||||
|
MeasurementType::Unknown,
|
||||||
|
ValueInformation::Any,
|
||||||
|
AnyStorageNr,
|
||||||
|
AnyTariffNr,
|
||||||
|
IndexNr(1),
|
||||||
|
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||||
|
"Voltage at phase L1.",
|
||||||
|
SET_FUNC(phase_1_v_, Unit::Volt),
|
||||||
|
GET_FUNC(phase_1_v_, Unit::Volt));
|
||||||
|
|
||||||
addPrint("current_power_production", Quantity::Power,
|
addFieldWithExtractor(
|
||||||
[&](Unit u){ return currentPowerProduction(u); },
|
"voltage_at_phase_2",
|
||||||
"Current power production.",
|
Quantity::Voltage,
|
||||||
true, true);
|
DifVifKey("0AFDC9FC02"),
|
||||||
|
VifScaling::None,
|
||||||
|
MeasurementType::Unknown,
|
||||||
|
ValueInformation::Any,
|
||||||
|
AnyStorageNr,
|
||||||
|
AnyTariffNr,
|
||||||
|
IndexNr(1),
|
||||||
|
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||||
|
"Voltage at phase L2.",
|
||||||
|
SET_FUNC(phase_2_v_, Unit::Volt),
|
||||||
|
GET_FUNC(phase_2_v_, Unit::Volt));
|
||||||
|
|
||||||
addPrint("voltage_at_phase_1", Quantity::Voltage,
|
addFieldWithExtractor(
|
||||||
[&](Unit u){ return convert(voltage_L_[0], Unit::Volt, u); },
|
"voltage_at_phase_3",
|
||||||
"Voltage at phase L1.",
|
Quantity::Voltage,
|
||||||
true, true);
|
DifVifKey("0AFDC9FC03"),
|
||||||
|
VifScaling::None,
|
||||||
|
MeasurementType::Unknown,
|
||||||
|
ValueInformation::Any,
|
||||||
|
AnyStorageNr,
|
||||||
|
AnyTariffNr,
|
||||||
|
IndexNr(1),
|
||||||
|
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||||
|
"Voltage at phase L3.",
|
||||||
|
SET_FUNC(phase_3_v_, Unit::Volt),
|
||||||
|
GET_FUNC(phase_3_v_, Unit::Volt));
|
||||||
|
|
||||||
addPrint("voltage_at_phase_2", Quantity::Voltage,
|
addStringFieldWithExtractor(
|
||||||
[&](Unit u){ return convert(voltage_L_[1], Unit::Volt, u); },
|
"device_date_time",
|
||||||
"Voltage at phase L2.",
|
Quantity::Text,
|
||||||
true, true);
|
NoDifVifKey,
|
||||||
|
MeasurementType::Instantaneous,
|
||||||
addPrint("voltage_at_phase_3", Quantity::Voltage,
|
ValueInformation::DateTime,
|
||||||
[&](Unit u){ return convert(voltage_L_[2], Unit::Volt, u); },
|
StorageNr(0),
|
||||||
"Voltage at phase L3.",
|
TariffNr(0),
|
||||||
true, true);
|
IndexNr(1),
|
||||||
|
PrintProperty::JSON,
|
||||||
addPrint("device_date_time", Quantity::Text,
|
"Device date time.",
|
||||||
[&](){ return device_date_time_; },
|
SET_STRING_FUNC(device_date_time_),
|
||||||
"Device date time.",
|
GET_STRING_FUNC(device_date_time_));
|
||||||
false, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Meter> createAmiplus(MeterInfo &mi)
|
// Test: MyElectricity1 amiplus 10101010 NOKEY
|
||||||
{
|
// telegram=|4E4401061010101002027A00004005|2F2F0E035040691500000B2B300300066D00790C7423400C78371204860BABC8FC100000000E833C8074000000000BAB3C0000000AFDC9FC0136022F2F2F2F2F|
|
||||||
return shared_ptr<Meter>(new MeterAmiplus(mi));
|
// {"media":"electricity","meter":"amiplus","name":"MyElectricity1","id":"10101010","total_energy_consumption_kwh":15694.05,"current_power_consumption_kw":0.33,"total_energy_production_kwh":7.48,"current_power_production_kw":0,"voltage_at_phase_1_v":236,"voltage_at_phase_2_v":0,"voltage_at_phase_3_v":0,"device_date_time":"2019-03-20 12:57","timestamp":"1111-11-11T11:11:11Z"}
|
||||||
}
|
// |MyElectricity1;10101010;15694.050000;0.330000;7.480000;0.000000;236.000000;0.000000;0.000000;1111-11-11 11:11.11
|
||||||
|
|
||||||
double MeterAmiplus::totalEnergyConsumption(Unit u)
|
// Test: MyElectricity2 amiplus 00254358 NOKEY
|
||||||
{
|
// amiplus/apator electricity meter with three phase voltages
|
||||||
assertQuantity(u, Quantity::Energy);
|
|
||||||
return convert(total_energy_kwh_, Unit::KWH, u);
|
|
||||||
}
|
|
||||||
|
|
||||||
double MeterAmiplus::currentPowerConsumption(Unit u)
|
// telegram=|5E44B6105843250000027A2A005005|2F2F0C7835221400066D404708AC2A400E032022650900000E833C0000000000001B2B9647000B2B5510000BAB3C0000000AFDC9FC0135020AFDC9FC0245020AFDC9FC0339020BABC8FC100000002F2F|
|
||||||
{
|
// {"media":"electricity","meter":"amiplus","name":"MyElectricity2","id":"00254358","total_energy_consumption_kwh":9652.22,"current_power_consumption_kw":1.055,"total_energy_production_kwh":0,"current_power_production_kw":0,"voltage_at_phase_1_v":235,"voltage_at_phase_2_v":245,"voltage_at_phase_3_v":239,"device_date_time":"2021-10-12 08:07","timestamp":"1111-11-11T11:11:11Z"}
|
||||||
assertQuantity(u, Quantity::Power);
|
// |MyElectricity2;00254358;9652.220000;1.055000;0.000000;0.000000;235.000000;245.000000;239.000000;1111-11-11 11:11.11
|
||||||
return convert(current_power_kw_, Unit::KW, u);
|
|
||||||
}
|
|
||||||
|
|
||||||
double MeterAmiplus::totalEnergyProduction(Unit u)
|
|
||||||
{
|
|
||||||
assertQuantity(u, Quantity::Energy);
|
|
||||||
return convert(total_energy_returned_kwh_, Unit::KWH, u);
|
|
||||||
}
|
|
||||||
|
|
||||||
double MeterAmiplus::currentPowerProduction(Unit u)
|
|
||||||
{
|
|
||||||
assertQuantity(u, Quantity::Power);
|
|
||||||
return convert(current_power_returned_kw_, Unit::KW, u);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MeterAmiplus::processContent(Telegram *t)
|
|
||||||
{
|
|
||||||
int offset;
|
|
||||||
string key;
|
|
||||||
|
|
||||||
if (findKey(MeasurementType::Unknown, ValueInformation::EnergyWh, 0, 0, &key, &t->values)) {
|
|
||||||
extractDVdouble(&t->values, key, &offset, &total_energy_kwh_);
|
|
||||||
t->addMoreExplanation(offset, " total energy (%f kwh)", total_energy_kwh_);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (findKey(MeasurementType::Unknown, ValueInformation::PowerW, 0, 0, &key, &t->values)) {
|
|
||||||
extractDVdouble(&t->values, key, &offset, ¤t_power_kw_);
|
|
||||||
t->addMoreExplanation(offset, " current power (%f kw)", current_power_kw_);
|
|
||||||
}
|
|
||||||
|
|
||||||
extractDVdouble(&t->values, "0E833C", &offset, &total_energy_returned_kwh_);
|
|
||||||
t->addMoreExplanation(offset, " total energy returned (%f kwh)", total_energy_returned_kwh_);
|
|
||||||
|
|
||||||
extractDVdouble(&t->values, "0BAB3C", &offset, ¤t_power_returned_kw_);
|
|
||||||
t->addMoreExplanation(offset, " current power returned (%f kw)", current_power_returned_kw_);
|
|
||||||
|
|
||||||
voltage_L_[0]=voltage_L_[1]=voltage_L_[2] = 0;
|
|
||||||
uint64_t tmpvolt {};
|
|
||||||
|
|
||||||
if (extractDVlong(&t->values, "0AFDC9FC01", &offset, &tmpvolt))
|
|
||||||
{
|
|
||||||
voltage_L_[0] = ((double)tmpvolt);
|
|
||||||
t->addMoreExplanation(offset, " voltage L1 (%f volts)", voltage_L_[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extractDVlong(&t->values, "0AFDC9FC02", &offset, &tmpvolt))
|
|
||||||
{
|
|
||||||
voltage_L_[1] = ((double)tmpvolt);
|
|
||||||
t->addMoreExplanation(offset, " voltage L2 (%f volts)", voltage_L_[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extractDVlong(&t->values, "0AFDC9FC03", &offset, &tmpvolt))
|
|
||||||
{
|
|
||||||
voltage_L_[2] = ((double)tmpvolt);
|
|
||||||
t->addMoreExplanation(offset, " voltage L3 (%f volts)", voltage_L_[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (findKey(MeasurementType::Unknown, ValueInformation::DateTime, 0, 0, &key, &t->values)) {
|
|
||||||
struct tm datetime;
|
|
||||||
extractDVdate(&t->values, key, &offset, &datetime);
|
|
||||||
device_date_time_ = strdatetime(&datetime);
|
|
||||||
t->addMoreExplanation(offset, " device datetime (%s)", device_date_time_.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -26,26 +26,28 @@
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
struct MeterAuto : public virtual MeterCommonImplementation {
|
struct MeterAuto : public virtual MeterCommonImplementation {
|
||||||
MeterAuto(MeterInfo &mi);
|
MeterAuto(MeterInfo &mi, DriverInfo &di);
|
||||||
|
|
||||||
string meter_info_;
|
|
||||||
void processContent(Telegram *t);
|
void processContent(Telegram *t);
|
||||||
};
|
};
|
||||||
|
|
||||||
MeterAuto::MeterAuto(MeterInfo &mi) :
|
bool ok = registerDriver([](DriverInfo&di)
|
||||||
MeterCommonImplementation(mi, "auto")
|
{
|
||||||
|
di.setName("auto");
|
||||||
|
di.setMeterType(MeterType::AutoMeter);
|
||||||
|
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new MeterAuto(mi, di)); });
|
||||||
|
});
|
||||||
|
|
||||||
|
MeterAuto::MeterAuto(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||||
{
|
{
|
||||||
addPrint("meter_info", Quantity::Text,
|
|
||||||
[&](){ return meter_info_; },
|
|
||||||
"Information about the meter telegram.",
|
|
||||||
true, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Meter> createAuto(MeterInfo &mi)
|
shared_ptr<Meter> createAuto(MeterInfo &mi)
|
||||||
{
|
{
|
||||||
return shared_ptr<Meter>(new MeterAuto(mi));
|
DriverInfo di;
|
||||||
|
di.setName("auto");
|
||||||
|
return shared_ptr<Meter>(new MeterAuto(mi, di));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeterAuto::processContent(Telegram *t)
|
void MeterAuto::processContent(Telegram *t)
|
||||||
|
|
|
@ -22,9 +22,6 @@
|
||||||
// meter driver, manufacturer, media, version
|
// meter driver, manufacturer, media, version
|
||||||
//
|
//
|
||||||
#define METER_DETECTION \
|
#define METER_DETECTION \
|
||||||
X(AMIPLUS, MANUFACTURER_APA, 0x02, 0x02) \
|
|
||||||
X(AMIPLUS, MANUFACTURER_DEV, 0x37, 0x02) \
|
|
||||||
X(AMIPLUS, MANUFACTURER_DEV, 0x02, 0x00) \
|
|
||||||
X(AVENTIESWM,MANUFACTURER_AAA, 0x07, 0x25) \
|
X(AVENTIESWM,MANUFACTURER_AAA, 0x07, 0x25) \
|
||||||
X(AVENTIESHCA,MANUFACTURER_AAA, 0x08, 0x55) \
|
X(AVENTIESHCA,MANUFACTURER_AAA, 0x08, 0x55) \
|
||||||
X(APATOR08, 0x8614/*APT?*/, 0x03, 0x03) \
|
X(APATOR08, 0x8614/*APT?*/, 0x03, 0x03) \
|
||||||
|
@ -73,7 +70,6 @@
|
||||||
X(IZAR3, MANUFACTURER_SAP, 0x00, 0x88) \
|
X(IZAR3, MANUFACTURER_SAP, 0x00, 0x88) \
|
||||||
X(LANSENSM, MANUFACTURER_LAS, 0x1a, 0x03) \
|
X(LANSENSM, MANUFACTURER_LAS, 0x1a, 0x03) \
|
||||||
X(LANSENTH, MANUFACTURER_LAS, 0x1b, 0x07) \
|
X(LANSENTH, MANUFACTURER_LAS, 0x1b, 0x07) \
|
||||||
X(LANSENDW, MANUFACTURER_LAS, 0x1d, 0x07) \
|
|
||||||
X(LANSENPU, MANUFACTURER_LAS, 0x00, 0x14) \
|
X(LANSENPU, MANUFACTURER_LAS, 0x00, 0x14) \
|
||||||
X(LANSENPU, MANUFACTURER_LAS, 0x00, 0x0b) \
|
X(LANSENPU, MANUFACTURER_LAS, 0x00, 0x0b) \
|
||||||
X(LSE_07_17, MANUFACTURER_LSE, 0x06, 0x18) \
|
X(LSE_07_17, MANUFACTURER_LSE, 0x06, 0x18) \
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
struct MeterLansenDW : public virtual MeterCommonImplementation
|
struct MeterLansenDW : public virtual MeterCommonImplementation
|
||||||
{
|
{
|
||||||
MeterLansenDW(MeterInfo &mi);
|
MeterLansenDW(MeterInfo &mi, DriverInfo &di);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
@ -40,16 +40,17 @@ private:
|
||||||
double pulse_counter_b_ {};
|
double pulse_counter_b_ {};
|
||||||
};
|
};
|
||||||
|
|
||||||
static DriverInfo di = addDriver(
|
static bool ok = registerDriver([](DriverInfo&di)
|
||||||
"lansendw",
|
{
|
||||||
T1_bit,
|
di.setName("lansendw");
|
||||||
MeterType::DoorWindowDetector,
|
di.setMeterType(MeterType::DoorWindowDetector);
|
||||||
[](MeterInfo& mi){ return shared_ptr<Meter>(new MeterLansenDW(mi)); },
|
di.addLinkMode(LinkMode::T1);
|
||||||
{ { MANUFACTURER_LAS, 0x1d, 0x07 } }
|
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new MeterLansenDW(mi, di)); });
|
||||||
);
|
di.addDetection(MANUFACTURER_LAS, 0x1d, 0x07);
|
||||||
|
});
|
||||||
|
|
||||||
MeterLansenDW::MeterLansenDW(MeterInfo &mi) :
|
MeterLansenDW::MeterLansenDW(MeterInfo &mi, DriverInfo &di) :
|
||||||
MeterCommonImplementation(mi, "lansendw")
|
MeterCommonImplementation(mi, di)
|
||||||
{
|
{
|
||||||
setMeterType(MeterType::DoorWindowDetector);
|
setMeterType(MeterType::DoorWindowDetector);
|
||||||
|
|
||||||
|
@ -62,6 +63,12 @@ MeterLansenDW::MeterLansenDW(MeterInfo &mi) :
|
||||||
"The current status: OPEN or CLOSED.",
|
"The current status: OPEN or CLOSED.",
|
||||||
true, true);
|
true, true);
|
||||||
|
|
||||||
|
/*
|
||||||
|
addPrint("statuss", Quantity::Text,
|
||||||
|
[&](){ return status(); },
|
||||||
|
"The current status: OPEN or CLOSED.",
|
||||||
|
true, true);
|
||||||
|
*/
|
||||||
addPrint("counter_a", Quantity::Counter,
|
addPrint("counter_a", Quantity::Counter,
|
||||||
[&](Unit u) { assertQuantity(u, Quantity::Counter); return pulse_counter_a_; },
|
[&](Unit u) { assertQuantity(u, Quantity::Counter); return pulse_counter_a_; },
|
||||||
"How many times the door/window has been opened or closed.",
|
"How many times the door/window has been opened or closed.",
|
||||||
|
@ -76,7 +83,9 @@ MeterLansenDW::MeterLansenDW(MeterInfo &mi) :
|
||||||
|
|
||||||
shared_ptr<Meter> createLansenDW(MeterInfo &mi)
|
shared_ptr<Meter> createLansenDW(MeterInfo &mi)
|
||||||
{
|
{
|
||||||
return shared_ptr<Meter>(new MeterLansenDW(mi));
|
DriverInfo di;
|
||||||
|
di.setName("lansendw");
|
||||||
|
return shared_ptr<Meter>(new MeterLansenDW(mi, di));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -127,17 +136,17 @@ void MeterLansenDW::processContent(Telegram *t)
|
||||||
|
|
||||||
if (extractDVuint16(&t->values, "02FD1B", &offset, &info_codes_))
|
if (extractDVuint16(&t->values, "02FD1B", &offset, &info_codes_))
|
||||||
{
|
{
|
||||||
t->addMoreExplanation(offset, renderJsonField("status"));
|
t->addMoreExplanation(offset, renderJsonOnlyDefaultUnit("status"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extractDVdouble(&t->values, "0EFD3A", &offset, &pulse_counter_a_, false))
|
if (extractDVdouble(&t->values, "0EFD3A", &offset, &pulse_counter_a_, false))
|
||||||
{
|
{
|
||||||
t->addMoreExplanation(offset, renderJsonField("counter_a"));
|
t->addMoreExplanation(offset, renderJsonOnlyDefaultUnit("counter_a"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extractDVdouble(&t->values, "8E40FD3A", &offset, &pulse_counter_b_, false))
|
if (extractDVdouble(&t->values, "8E40FD3A", &offset, &pulse_counter_b_, false))
|
||||||
{
|
{
|
||||||
t->addMoreExplanation(offset, renderJsonField("counter_b"));
|
t->addMoreExplanation(offset, renderJsonOnlyDefaultUnit("counter_b"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,9 +45,7 @@ MeterPIIGTH::MeterPIIGTH(MeterInfo &mi) :
|
||||||
{
|
{
|
||||||
setMeterType(MeterType::TempHygroMeter);
|
setMeterType(MeterType::TempHygroMeter);
|
||||||
|
|
||||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
addLinkMode(LinkMode::MBUS);
|
||||||
|
|
||||||
addLinkMode(LinkMode::T1);
|
|
||||||
|
|
||||||
addPrint("current_temperature", Quantity::Temperature,
|
addPrint("current_temperature", Quantity::Temperature,
|
||||||
[&](Unit u){ return currentTemperature(u); },
|
[&](Unit u){ return currentTemperature(u); },
|
||||||
|
|
|
@ -28,24 +28,27 @@ using namespace std;
|
||||||
|
|
||||||
|
|
||||||
struct MeterUnknown : public virtual MeterCommonImplementation {
|
struct MeterUnknown : public virtual MeterCommonImplementation {
|
||||||
MeterUnknown(MeterInfo &mi);
|
MeterUnknown(MeterInfo &mi, DriverInfo &di);
|
||||||
|
|
||||||
string meter_info_;
|
|
||||||
void processContent(Telegram *t);
|
void processContent(Telegram *t);
|
||||||
};
|
};
|
||||||
|
|
||||||
MeterUnknown::MeterUnknown(MeterInfo &mi) :
|
static bool ok = registerDriver([](DriverInfo&di)
|
||||||
MeterCommonImplementation(mi, "auto")
|
{
|
||||||
|
di.setName("unknown");
|
||||||
|
di.setMeterType(MeterType::UnknownMeter);
|
||||||
|
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new MeterUnknown(mi, di)); });
|
||||||
|
});
|
||||||
|
|
||||||
|
MeterUnknown::MeterUnknown(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||||
{
|
{
|
||||||
addPrint("meter_info", Quantity::Text,
|
|
||||||
[&](){ return meter_info_; },
|
|
||||||
"Information about the meter telegram.",
|
|
||||||
true, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Meter> createUnknown(MeterInfo &mi)
|
shared_ptr<Meter> createUnknown(MeterInfo &mi)
|
||||||
{
|
{
|
||||||
return shared_ptr<Meter>(new MeterUnknown(mi));
|
DriverInfo di;
|
||||||
|
di.setName("unknown");
|
||||||
|
return shared_ptr<Meter>(new MeterUnknown(mi, di));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeterUnknown::processContent(Telegram *t)
|
void MeterUnknown::processContent(Telegram *t)
|
||||||
|
|
615
src/meters.cc
615
src/meters.cc
|
@ -30,24 +30,68 @@
|
||||||
#include<cmath>
|
#include<cmath>
|
||||||
|
|
||||||
|
|
||||||
map<string, DriverInfo> all_drivers_;
|
map<string, DriverInfo> all_registered_drivers_;
|
||||||
|
vector<DriverInfo> all_registered_drivers_list_;
|
||||||
|
|
||||||
DriverInfo addDriver(string n,
|
bool DriverInfo::detect(uint16_t mfct, uchar type, uchar version)
|
||||||
LinkModeSet lms,
|
|
||||||
MeterType t,
|
|
||||||
function<shared_ptr<Meter>(MeterInfo&)> constructor,
|
|
||||||
vector<DriverDetect> d)
|
|
||||||
{
|
{
|
||||||
assert(all_drivers_.count(n) == 0); // A driver must have a unique name.
|
for (auto &dd : detect_)
|
||||||
|
{
|
||||||
|
if (dd.mfct == 0 && dd.type == 0 && dd.version == 0) continue; // Ignore drivers with no detection.
|
||||||
|
if (dd.mfct == mfct && dd.type == type && dd.version == version) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
DriverInfo di = { n, lms, t, constructor, d };
|
void DriverInfo::setExpectedELLSecurityMode(ELLSecurityMode dsm)
|
||||||
all_drivers_[n] = di;
|
{
|
||||||
|
// TODO should check that the telegram is encrypted using the same mode.
|
||||||
|
}
|
||||||
|
|
||||||
|
void DriverInfo::setExpectedTPLSecurityMode(TPLSecurityMode tsm)
|
||||||
|
{
|
||||||
|
// TODO should check that the telegram is encrypted using the same mode.
|
||||||
|
}
|
||||||
|
|
||||||
|
bool registerDriver(function<void(DriverInfo&)> setup)
|
||||||
|
{
|
||||||
|
DriverInfo di;
|
||||||
|
setup(di);
|
||||||
|
|
||||||
|
// Check that the driver name is unique.
|
||||||
|
assert(all_registered_drivers_.count(di.name().str()) == 0);
|
||||||
|
|
||||||
|
// Check that no other driver also triggers on the same detection values.
|
||||||
|
/*
|
||||||
|
for (auto &d : di.detect())
|
||||||
|
{
|
||||||
|
for (auto &p : all_registered_drivers_)
|
||||||
|
{
|
||||||
|
bool foo = p.second.detect(d.mfct, d.type, d.version);
|
||||||
|
assert(!foo);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// Everything looks, good install this driver.
|
||||||
|
all_registered_drivers_[di.name().str()] = di;
|
||||||
|
all_registered_drivers_list_.push_back(di);
|
||||||
|
|
||||||
// This code is invoked from the static initializers of DriverInfos when starting
|
// This code is invoked from the static initializers of DriverInfos when starting
|
||||||
// wmbusmeters. Thus we do not yet know if the user has supplied --debug or similar setting.
|
// wmbusmeters. Thus we do not yet know if the user has supplied --debug or similar setting.
|
||||||
// To debug this you have to uncomment the printf below.
|
// To debug this you have to uncomment the printf below.
|
||||||
// fprintf(stderr, "(STATIC) added driver: %s\n", n.c_str());
|
// fprintf(stderr, "(STATIC) added driver: %s\n", n.c_str());
|
||||||
return di;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lookupDriverInfo(string& driver, DriverInfo *out_di)
|
||||||
|
{
|
||||||
|
if (all_registered_drivers_.count(driver) == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_di = all_registered_drivers_[driver];
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MeterManagerImplementation : public virtual MeterManager
|
struct MeterManagerImplementation : public virtual MeterManager
|
||||||
|
@ -183,7 +227,7 @@ public:
|
||||||
if (MeterCommonImplementation::isTelegramForMeter(&t, NULL, &mi))
|
if (MeterCommonImplementation::isTelegramForMeter(&t, NULL, &mi))
|
||||||
{
|
{
|
||||||
// We found a match, make a copy of the meter info.
|
// We found a match, make a copy of the meter info.
|
||||||
MeterInfo tmp = mi;
|
MeterInfo meter_info = mi;
|
||||||
// Overwrite the wildcard pattern with the highest level id.
|
// Overwrite the wildcard pattern with the highest level id.
|
||||||
// The last id in the t.ids is the highest level id.
|
// The last id in the t.ids is the highest level id.
|
||||||
// For example: a telegram can have dll_id,tpl_id
|
// For example: a telegram can have dll_id,tpl_id
|
||||||
|
@ -192,24 +236,30 @@ public:
|
||||||
// then the dll_id will be picked.
|
// then the dll_id will be picked.
|
||||||
vector<string> tmp_ids;
|
vector<string> tmp_ids;
|
||||||
tmp_ids.push_back(t.ids.back());
|
tmp_ids.push_back(t.ids.back());
|
||||||
tmp.ids = tmp_ids;
|
meter_info.ids = tmp_ids;
|
||||||
tmp.idsc = t.ids.back();
|
meter_info.idsc = t.ids.back();
|
||||||
|
|
||||||
if (tmp.driver == MeterDriver::AUTO)
|
if (meter_info.driver == MeterDriver::AUTO)
|
||||||
{
|
{
|
||||||
// Look up the proper meter driver!
|
// Look up the proper meter driver!
|
||||||
tmp.driver = pickMeterDriver(&t);
|
DriverInfo di = pickMeterDriver(&t);
|
||||||
if (tmp.driver == MeterDriver::UNKNOWN)
|
if (di.driver() == MeterDriver::UNKNOWN && di.name().str() == "")
|
||||||
{
|
{
|
||||||
|
printf("GURKA Driver not found %d %d %d\n", t.dll_mfct, t.dll_type, t.dll_version);
|
||||||
if (should_analyze_ == false)
|
if (should_analyze_ == false)
|
||||||
{
|
{
|
||||||
// We are not analyzing, so warn here.
|
// We are not analyzing, so warn here.
|
||||||
warnForUnknownDriver(mi.name, &t);
|
warnForUnknownDriver(mi.name, &t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
meter_info.driver = di.driver();
|
||||||
|
meter_info.driver_name = di.name();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Now build a meter object with for this exact id.
|
// Now build a meter object with for this exact id.
|
||||||
auto meter = createMeter(&tmp);
|
auto meter = createMeter(&meter_info);
|
||||||
addMeter(meter);
|
addMeter(meter);
|
||||||
string idsc = toIdsCommaSeparated(t.ids);
|
string idsc = toIdsCommaSeparated(t.ids);
|
||||||
verbose("(meter) used meter template %s %s %s to match %s\n",
|
verbose("(meter) used meter template %s %s %s to match %s\n",
|
||||||
|
@ -223,7 +273,7 @@ public:
|
||||||
notice("(wmbusmeters) started meter %d (%s %s %s)\n",
|
notice("(wmbusmeters) started meter %d (%s %s %s)\n",
|
||||||
meter->index(),
|
meter->index(),
|
||||||
mi.name.c_str(),
|
mi.name.c_str(),
|
||||||
tmp.idsc.c_str(),
|
meter_info.idsc.c_str(),
|
||||||
toString(mi.driver).c_str());
|
toString(mi.driver).c_str());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -231,7 +281,7 @@ public:
|
||||||
verbose("(meter) started meter %d (%s %s %s)\n",
|
verbose("(meter) started meter %d (%s %s %s)\n",
|
||||||
meter->index(),
|
meter->index(),
|
||||||
mi.name.c_str(),
|
mi.name.c_str(),
|
||||||
tmp.idsc.c_str(),
|
meter_info.idsc.c_str(),
|
||||||
toString(mi.driver).c_str());
|
toString(mi.driver).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -334,7 +384,7 @@ LIST_OF_METERS
|
||||||
bool hide_output = drivers.size() > 1;
|
bool hide_output = drivers.size() > 1;
|
||||||
bool printed = false;
|
bool printed = false;
|
||||||
bool handled = false;
|
bool handled = false;
|
||||||
MeterDriver best_driver {};
|
DriverInfo best_driver;
|
||||||
// For the best driver we have:
|
// For the best driver we have:
|
||||||
int best_content_length = 0;
|
int best_content_length = 0;
|
||||||
int best_understood_content_length = 0;
|
int best_understood_content_length = 0;
|
||||||
|
@ -385,24 +435,24 @@ LIST_OF_METERS
|
||||||
}
|
}
|
||||||
if (handled)
|
if (handled)
|
||||||
{
|
{
|
||||||
MeterDriver auto_driver = pickMeterDriver(&t);
|
DriverInfo auto_driver = pickMeterDriver(&t);
|
||||||
string ad = toString(auto_driver);
|
string ad = toString(auto_driver);
|
||||||
string bd = toString(best_driver);
|
string bd = toString(best_driver);
|
||||||
if (auto_driver != MeterDriver::UNKNOWN)
|
if (auto_driver.driver() != MeterDriver::UNKNOWN)
|
||||||
{
|
{
|
||||||
if (best_driver != auto_driver)
|
if (ad != bd)
|
||||||
{
|
{
|
||||||
printf("\nUsing driver \"%s\" based on mfct/type/version driver lookup table.\n", ad.c_str());
|
printf("\nUsing driver \"%s\" based on mfct/type/version driver lookup table.\n", ad.c_str());
|
||||||
printf("But a better match could perhaps be driver \"%s\" with %d/%d content bytes understood.\n",
|
printf("But a better match could perhaps be driver \"%s\" with %d/%d content bytes understood.\n",
|
||||||
bd.c_str(), best_understood_content_length, best_content_length);
|
bd.c_str(), best_understood_content_length, best_content_length);
|
||||||
mi.driver = auto_driver;
|
mi.driver_name = auto_driver.name();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
printf("\nUsing driver \"%s\" based on mfct/type/version driver lookup table.\n", ad.c_str());
|
printf("\nUsing driver \"%s\" based on mfct/type/version driver lookup table.\n", ad.c_str());
|
||||||
printf("Which is also the best matching driver with %d/%d content bytes understood.\n",
|
printf("Which is also the best matching driver with %d/%d content bytes understood.\n",
|
||||||
best_understood_content_length, best_content_length);
|
best_understood_content_length, best_content_length);
|
||||||
mi.driver = best_driver;
|
mi.driver_name = best_driver.name();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -410,7 +460,7 @@ LIST_OF_METERS
|
||||||
printf("\nUsing driver \"%s\" based on best content match with %d/%d content bytes understood.\n",
|
printf("\nUsing driver \"%s\" based on best content match with %d/%d content bytes understood.\n",
|
||||||
bd.c_str(), best_understood_content_length, best_content_length);
|
bd.c_str(), best_understood_content_length, best_content_length);
|
||||||
printf("The mfct/type/version combo was not found in the driver lookup table.\n");
|
printf("The mfct/type/version combo was not found in the driver lookup table.\n");
|
||||||
mi.driver = best_driver;
|
mi.driver_name = best_driver.name();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!printed)
|
if (!printed)
|
||||||
|
@ -458,6 +508,25 @@ MeterCommonImplementation::MeterCommonImplementation(MeterInfo &mi,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MeterCommonImplementation::MeterCommonImplementation(MeterInfo &mi,
|
||||||
|
DriverInfo &di) :
|
||||||
|
driver_(di.name().str()), bus_(mi.bus), name_(mi.name)
|
||||||
|
{
|
||||||
|
ids_ = mi.ids;
|
||||||
|
idsc_ = toIdsCommaSeparated(ids_);
|
||||||
|
|
||||||
|
if (mi.key.length() > 0)
|
||||||
|
{
|
||||||
|
hex2bin(mi.key, &meter_keys_.confidentiality_key);
|
||||||
|
}
|
||||||
|
for (auto s : mi.shells) {
|
||||||
|
addShell(s);
|
||||||
|
}
|
||||||
|
for (auto j : mi.extra_constant_fields) {
|
||||||
|
addExtraConstantField(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MeterCommonImplementation::addConversions(std::vector<Unit> cs)
|
void MeterCommonImplementation::addConversions(std::vector<Unit> cs)
|
||||||
{
|
{
|
||||||
for (Unit c : cs)
|
for (Unit c : cs)
|
||||||
|
@ -489,7 +558,11 @@ vector<string> &MeterCommonImplementation::meterExtraConstantFields()
|
||||||
MeterDriver MeterCommonImplementation::driver()
|
MeterDriver MeterCommonImplementation::driver()
|
||||||
{
|
{
|
||||||
return toMeterDriver(driver_);
|
return toMeterDriver(driver_);
|
||||||
|
}
|
||||||
|
|
||||||
|
DriverName MeterCommonImplementation::driverName()
|
||||||
|
{
|
||||||
|
return driver_name_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeterCommonImplementation::setMeterType(MeterType mt)
|
void MeterCommonImplementation::setMeterType(MeterType mt)
|
||||||
|
@ -508,7 +581,29 @@ void MeterCommonImplementation::addPrint(string vname, Quantity vquantity,
|
||||||
string default_unit = unitToStringLowerCase(defaultUnitForQuantity(vquantity));
|
string default_unit = unitToStringLowerCase(defaultUnitForQuantity(vquantity));
|
||||||
string field_name = vname+"_"+default_unit;
|
string field_name = vname+"_"+default_unit;
|
||||||
fields_.push_back(field_name);
|
fields_.push_back(field_name);
|
||||||
prints_.push_back( { vname, vquantity, defaultUnitForQuantity(vquantity), getValueFunc, NULL, help, field, json, field_name });
|
prints_.push_back(
|
||||||
|
FieldInfo(vname,
|
||||||
|
vquantity,
|
||||||
|
defaultUnitForQuantity(vquantity),
|
||||||
|
NoDifVifKey,
|
||||||
|
VifScaling::Auto,
|
||||||
|
MeasurementType::Unknown,
|
||||||
|
ValueInformation::None,
|
||||||
|
AnyStorageNr,
|
||||||
|
AnyTariffNr,
|
||||||
|
IndexNr(1),
|
||||||
|
help,
|
||||||
|
field,
|
||||||
|
json,
|
||||||
|
false,
|
||||||
|
field_name,
|
||||||
|
getValueFunc,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeterCommonImplementation::addPrint(string vname, Quantity vquantity, Unit unit,
|
void MeterCommonImplementation::addPrint(string vname, Quantity vquantity, Unit unit,
|
||||||
|
@ -517,14 +612,213 @@ void MeterCommonImplementation::addPrint(string vname, Quantity vquantity, Unit
|
||||||
string default_unit = unitToStringLowerCase(defaultUnitForQuantity(vquantity));
|
string default_unit = unitToStringLowerCase(defaultUnitForQuantity(vquantity));
|
||||||
string field_name = vname+"_"+default_unit;
|
string field_name = vname+"_"+default_unit;
|
||||||
fields_.push_back(field_name);
|
fields_.push_back(field_name);
|
||||||
prints_.push_back( { vname, vquantity, unit, getValueFunc, NULL, help, field, json, field_name });
|
prints_.push_back(
|
||||||
|
FieldInfo(vname,
|
||||||
|
vquantity,
|
||||||
|
unit,
|
||||||
|
NoDifVifKey,
|
||||||
|
VifScaling::Auto,
|
||||||
|
MeasurementType::Unknown,
|
||||||
|
ValueInformation::None,
|
||||||
|
AnyStorageNr,
|
||||||
|
AnyTariffNr,
|
||||||
|
IndexNr(1),
|
||||||
|
help,
|
||||||
|
field,
|
||||||
|
json,
|
||||||
|
false,
|
||||||
|
field_name,
|
||||||
|
getValueFunc,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeterCommonImplementation::addPrint(string vname, Quantity vquantity,
|
void MeterCommonImplementation::addPrint(string vname, Quantity vquantity,
|
||||||
function<string()> getValueFunc,
|
function<string()> getValueFunc,
|
||||||
string help, bool field, bool json)
|
string help, bool field, bool json)
|
||||||
{
|
{
|
||||||
prints_.push_back( { vname, vquantity, defaultUnitForQuantity(vquantity), NULL, getValueFunc, help, field, json, vname } );
|
prints_.push_back(
|
||||||
|
FieldInfo(vname,
|
||||||
|
vquantity,
|
||||||
|
defaultUnitForQuantity(vquantity),
|
||||||
|
NoDifVifKey,
|
||||||
|
VifScaling::Auto,
|
||||||
|
MeasurementType::Unknown,
|
||||||
|
ValueInformation::None,
|
||||||
|
AnyStorageNr,
|
||||||
|
AnyTariffNr,
|
||||||
|
IndexNr(1),
|
||||||
|
help,
|
||||||
|
field,
|
||||||
|
json,
|
||||||
|
false,
|
||||||
|
vname,
|
||||||
|
NULL,
|
||||||
|
getValueFunc,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeterCommonImplementation::addFieldWithExtractor(
|
||||||
|
string vname,
|
||||||
|
Quantity vquantity,
|
||||||
|
DifVifKey dif_vif_key,
|
||||||
|
VifScaling vif_scaling,
|
||||||
|
MeasurementType mt,
|
||||||
|
ValueInformation vi,
|
||||||
|
StorageNr s,
|
||||||
|
TariffNr t,
|
||||||
|
IndexNr i,
|
||||||
|
int print_properties,
|
||||||
|
string help,
|
||||||
|
function<void(Unit,double)> setValueFunc,
|
||||||
|
function<double(Unit)> getValueFunc)
|
||||||
|
{
|
||||||
|
string default_unit = unitToStringLowerCase(defaultUnitForQuantity(vquantity));
|
||||||
|
string field_name = vname+"_"+default_unit;
|
||||||
|
fields_.push_back(field_name);
|
||||||
|
|
||||||
|
// Compose the extract function.
|
||||||
|
function<bool(FieldInfo *p,Meter *m, Telegram *t)> extract =
|
||||||
|
[](FieldInfo *fi, Meter *m, Telegram *t)
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
string key = fi->difVifKey().str();
|
||||||
|
int offset {};
|
||||||
|
if (key == "")
|
||||||
|
{
|
||||||
|
// Search for key.
|
||||||
|
bool ok = findKeyWithNr(fi->measurementType(),
|
||||||
|
fi->valueInformation(),
|
||||||
|
fi->storageNr().intValue(),
|
||||||
|
fi->tariffNr().intValue(),
|
||||||
|
fi->indexNr().intValue(),
|
||||||
|
&key,
|
||||||
|
&t->values);
|
||||||
|
if (!ok) return false;
|
||||||
|
}
|
||||||
|
double extracted_double_value = NAN;
|
||||||
|
if (extractDVdouble(&t->values,
|
||||||
|
key,
|
||||||
|
&offset,
|
||||||
|
&extracted_double_value,
|
||||||
|
fi->vifScaling() == VifScaling::Auto))
|
||||||
|
{
|
||||||
|
fi->setValueDouble(fi->defaultUnit(), extracted_double_value);
|
||||||
|
t->addMoreExplanation(offset, fi->renderJson(&m->conversions()));
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
};
|
||||||
|
|
||||||
|
prints_.push_back(
|
||||||
|
FieldInfo(vname,
|
||||||
|
vquantity,
|
||||||
|
defaultUnitForQuantity(vquantity),
|
||||||
|
dif_vif_key,
|
||||||
|
vif_scaling,
|
||||||
|
mt,
|
||||||
|
vi,
|
||||||
|
s,
|
||||||
|
t,
|
||||||
|
i,
|
||||||
|
help,
|
||||||
|
(print_properties & PrintProperty::FIELD) != 0,
|
||||||
|
(print_properties & PrintProperty::JSON) != 0,
|
||||||
|
(print_properties & PrintProperty::IMPORTANT) != 0,
|
||||||
|
field_name,
|
||||||
|
getValueFunc,
|
||||||
|
NULL,
|
||||||
|
setValueFunc,
|
||||||
|
NULL,
|
||||||
|
extract,
|
||||||
|
NULL
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeterCommonImplementation::addStringFieldWithExtractor(
|
||||||
|
string vname,
|
||||||
|
Quantity vquantity,
|
||||||
|
DifVifKey dif_vif_key,
|
||||||
|
MeasurementType mt,
|
||||||
|
ValueInformation vi,
|
||||||
|
StorageNr s,
|
||||||
|
TariffNr t,
|
||||||
|
IndexNr i,
|
||||||
|
int print_properties,
|
||||||
|
string help,
|
||||||
|
function<void(string)> setValueFunc,
|
||||||
|
function<string()> getValueFunc)
|
||||||
|
{
|
||||||
|
string default_unit = unitToStringLowerCase(defaultUnitForQuantity(vquantity));
|
||||||
|
string field_name = vname+"_"+default_unit;
|
||||||
|
fields_.push_back(field_name);
|
||||||
|
|
||||||
|
// Compose the extract function.
|
||||||
|
function<bool(FieldInfo *p,Meter *m, Telegram *t)> extract =
|
||||||
|
[](FieldInfo *fi, Meter *m, Telegram *t)
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
string key = fi->difVifKey().str();
|
||||||
|
int offset {};
|
||||||
|
if (key == "")
|
||||||
|
{
|
||||||
|
// Search for key.
|
||||||
|
bool ok = findKeyWithNr(fi->measurementType(),
|
||||||
|
fi->valueInformation(),
|
||||||
|
fi->storageNr().intValue(),
|
||||||
|
fi->tariffNr().intValue(),
|
||||||
|
fi->indexNr().intValue(),
|
||||||
|
&key,
|
||||||
|
&t->values);
|
||||||
|
if (!ok) return false;
|
||||||
|
}
|
||||||
|
if (fi->valueInformation() == ValueInformation::DateTime)
|
||||||
|
{
|
||||||
|
struct tm datetime;
|
||||||
|
extractDVdate(&t->values, key, &offset, &datetime);
|
||||||
|
string extracted_device_date_time = strdatetime(&datetime);
|
||||||
|
fi->setValueString(extracted_device_date_time);
|
||||||
|
t->addMoreExplanation(offset, fi->renderJsonText());
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
};
|
||||||
|
|
||||||
|
prints_.push_back(
|
||||||
|
FieldInfo(vname,
|
||||||
|
vquantity,
|
||||||
|
defaultUnitForQuantity(vquantity),
|
||||||
|
dif_vif_key,
|
||||||
|
VifScaling::None,
|
||||||
|
mt,
|
||||||
|
vi,
|
||||||
|
s,
|
||||||
|
t,
|
||||||
|
i,
|
||||||
|
help,
|
||||||
|
(print_properties & PrintProperty::FIELD) != 0,
|
||||||
|
(print_properties & PrintProperty::JSON) != 0,
|
||||||
|
(print_properties & PrintProperty::IMPORTANT) != 0,
|
||||||
|
field_name,
|
||||||
|
NULL,
|
||||||
|
getValueFunc,
|
||||||
|
NULL,
|
||||||
|
setValueFunc,
|
||||||
|
NULL,
|
||||||
|
extract
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeterCommonImplementation::poll(shared_ptr<BusManager> bus)
|
void MeterCommonImplementation::poll(shared_ptr<BusManager> bus)
|
||||||
|
@ -546,7 +840,7 @@ vector<string> MeterCommonImplementation::fields()
|
||||||
return fields_;
|
return fields_;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<Print> MeterCommonImplementation::prints()
|
vector<FieldInfo> MeterCommonImplementation::prints()
|
||||||
{
|
{
|
||||||
return prints_;
|
return prints_;
|
||||||
}
|
}
|
||||||
|
@ -599,6 +893,14 @@ LIST_OF_METERS
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *toString(MeterType type)
|
||||||
|
{
|
||||||
|
#define X(tname) if (type == MeterType::tname) return #tname;
|
||||||
|
LIST_OF_METER_TYPES
|
||||||
|
#undef X
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
string toString(MeterDriver mt)
|
string toString(MeterDriver mt)
|
||||||
{
|
{
|
||||||
#define X(mname,link,info,type,cname) if (mt == MeterDriver::type) return #mname;
|
#define X(mname,link,info,type,cname) if (mt == MeterDriver::type) return #mname;
|
||||||
|
@ -607,6 +909,12 @@ LIST_OF_METERS
|
||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string toString(DriverInfo &di)
|
||||||
|
{
|
||||||
|
if (di.driver() != MeterDriver::UNKNOWN) return toString(di.driver());
|
||||||
|
return di.name().str();
|
||||||
|
}
|
||||||
|
|
||||||
MeterDriver toMeterDriver(string& t)
|
MeterDriver toMeterDriver(string& t)
|
||||||
{
|
{
|
||||||
#define X(mname,linkmodes,info,type,cname) if (t == #mname) return MeterDriver::type;
|
#define X(mname,linkmodes,info,type,cname) if (t == #mname) return MeterDriver::type;
|
||||||
|
@ -727,26 +1035,6 @@ MeterKeys *MeterCommonImplementation::meterKeys()
|
||||||
return &meter_keys_;
|
return &meter_keys_;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<string> MeterCommonImplementation::getRecords()
|
|
||||||
{
|
|
||||||
vector<string> recs;
|
|
||||||
for (auto& p : values_)
|
|
||||||
{
|
|
||||||
recs.push_back(p.first);
|
|
||||||
}
|
|
||||||
return recs;
|
|
||||||
}
|
|
||||||
|
|
||||||
double MeterCommonImplementation::getRecordAsDouble(string record)
|
|
||||||
{
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t MeterCommonImplementation::getRecordAsUInt16(string record)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int MeterCommonImplementation::index()
|
int MeterCommonImplementation::index()
|
||||||
{
|
{
|
||||||
return index_;
|
return index_;
|
||||||
|
@ -770,7 +1058,7 @@ void MeterCommonImplementation::triggerUpdate(Telegram *t)
|
||||||
t->handled = true;
|
t->handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
string concatAllFields(Meter *m, Telegram *t, char c, vector<Print> &prints, vector<Unit> &cs, bool hr,
|
string concatAllFields(Meter *m, Telegram *t, char c, vector<FieldInfo> &prints, vector<Unit> &cs, bool hr,
|
||||||
vector<string> *extra_constant_fields)
|
vector<string> *extra_constant_fields)
|
||||||
{
|
{
|
||||||
string s;
|
string s;
|
||||||
|
@ -784,13 +1072,13 @@ string concatAllFields(Meter *m, Telegram *t, char c, vector<Print> &prints, vec
|
||||||
{
|
{
|
||||||
s += c;
|
s += c;
|
||||||
}
|
}
|
||||||
for (Print p : prints)
|
for (FieldInfo p : prints)
|
||||||
{
|
{
|
||||||
if (p.field)
|
if (p.field())
|
||||||
{
|
{
|
||||||
if (p.getValueDouble)
|
if (p.hasGetValueDouble())
|
||||||
{
|
{
|
||||||
Unit u = replaceWithConversionUnit(p.default_unit, cs);
|
Unit u = replaceWithConversionUnit(p.defaultUnit(), cs);
|
||||||
double v = p.getValueDouble(u);
|
double v = p.getValueDouble(u);
|
||||||
if (hr) {
|
if (hr) {
|
||||||
s += valueToString(v, u);
|
s += valueToString(v, u);
|
||||||
|
@ -799,7 +1087,7 @@ string concatAllFields(Meter *m, Telegram *t, char c, vector<Print> &prints, vec
|
||||||
s += to_string(v);
|
s += to_string(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (p.getValueString)
|
if (p.hasGetValueString())
|
||||||
{
|
{
|
||||||
s += p.getValueString();
|
s += p.getValueString();
|
||||||
}
|
}
|
||||||
|
@ -872,38 +1160,38 @@ bool checkCommonField(string *buf, string field, Meter *m, Telegram *t, char c)
|
||||||
|
|
||||||
// Is the desired field one of the meter printable fields?
|
// Is the desired field one of the meter printable fields?
|
||||||
bool checkPrintableField(string *buf, string field, Meter *m, Telegram *t, char c,
|
bool checkPrintableField(string *buf, string field, Meter *m, Telegram *t, char c,
|
||||||
vector<Print> &prints, vector<Unit> &cs)
|
vector<FieldInfo> &prints, vector<Unit> &cs)
|
||||||
{
|
{
|
||||||
for (Print p : prints)
|
for (FieldInfo p : prints)
|
||||||
{
|
{
|
||||||
if (p.getValueString)
|
if (p.hasGetValueString())
|
||||||
{
|
{
|
||||||
// Strings are simply just print them.
|
// Strings are simply just print them.
|
||||||
if (field == p.vname)
|
if (field == p.vname())
|
||||||
{
|
{
|
||||||
*buf += p.getValueString() + c;
|
*buf += p.getValueString() + c;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (p.getValueDouble)
|
else if (p.hasGetValueDouble())
|
||||||
{
|
{
|
||||||
// Doubles have to be converted into the proper unit.
|
// Doubles have to be converted into the proper unit.
|
||||||
string default_unit = unitToStringLowerCase(p.default_unit);
|
string default_unit = unitToStringLowerCase(p.defaultUnit());
|
||||||
string var = p.vname+"_"+default_unit;
|
string var = p.vname()+"_"+default_unit;
|
||||||
if (field == var)
|
if (field == var)
|
||||||
{
|
{
|
||||||
// Default unit.
|
// Default unit.
|
||||||
*buf += valueToString(p.getValueDouble(p.default_unit), p.default_unit) + c;
|
*buf += valueToString(p.getValueDouble(p.defaultUnit()), p.defaultUnit()) + c;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Added conversion unit.
|
// Added conversion unit.
|
||||||
Unit u = replaceWithConversionUnit(p.default_unit, cs);
|
Unit u = replaceWithConversionUnit(p.defaultUnit(), cs);
|
||||||
if (u != p.default_unit)
|
if (u != p.defaultUnit())
|
||||||
{
|
{
|
||||||
string unit = unitToStringLowerCase(u);
|
string unit = unitToStringLowerCase(u);
|
||||||
string var = p.vname+"_"+unit;
|
string var = p.vname()+"_"+unit;
|
||||||
if (field == var)
|
if (field == var)
|
||||||
{
|
{
|
||||||
*buf += valueToString(p.getValueDouble(u), u) + c;
|
*buf += valueToString(p.getValueDouble(u), u) + c;
|
||||||
|
@ -932,7 +1220,7 @@ bool checkConstantField(string *buf, string field, char c, vector<string> *extra
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
string concatFields(Meter *m, Telegram *t, char c, vector<Print> &prints, vector<Unit> &cs, bool hr,
|
string concatFields(Meter *m, Telegram *t, char c, vector<FieldInfo> &prints, vector<Unit> &cs, bool hr,
|
||||||
vector<string> *selected_fields, vector<string> *extra_constant_fields)
|
vector<string> *selected_fields, vector<string> *extra_constant_fields)
|
||||||
{
|
{
|
||||||
if (selected_fields == NULL || selected_fields->size() == 0)
|
if (selected_fields == NULL || selected_fields->size() == 0)
|
||||||
|
@ -999,7 +1287,9 @@ bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<ucha
|
||||||
snprintf(log_prefix, 255, "(%s) log", meterDriver().c_str());
|
snprintf(log_prefix, 255, "(%s) log", meterDriver().c_str());
|
||||||
logTelegram(t.original, t.frame, t.header_size, t.suffix_size);
|
logTelegram(t.original, t.frame, t.header_size, t.suffix_size);
|
||||||
|
|
||||||
// Invoke meter specific parsing!
|
// Invoke standardized field extractors!
|
||||||
|
processFieldExtractors(&t);
|
||||||
|
// Invoke tailor made meter specific parsing!
|
||||||
processContent(&t);
|
processContent(&t);
|
||||||
// All done....
|
// All done....
|
||||||
|
|
||||||
|
@ -1014,43 +1304,82 @@ bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<ucha
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
string MeterCommonImplementation::renderJsonField(string vname)
|
void MeterCommonImplementation::processFieldExtractors(Telegram *t)
|
||||||
{
|
{
|
||||||
Print *found = NULL;
|
for (auto &fi : prints_)
|
||||||
for (Print &p : prints_)
|
|
||||||
{
|
{
|
||||||
if (p.vname == vname)
|
fi.performExtraction(this, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeterCommonImplementation::processContent(Telegram *t)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FieldInfo *MeterCommonImplementation::findFieldInfo(string vname)
|
||||||
|
{
|
||||||
|
FieldInfo *found = NULL;
|
||||||
|
for (FieldInfo &p : prints_)
|
||||||
|
{
|
||||||
|
if (p.vname() == vname)
|
||||||
{
|
{
|
||||||
found = &p;
|
found = &p;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!found) return "ERROR addPrint is missing for \""+vname+"\"";
|
return found;
|
||||||
|
|
||||||
return renderJsonField(found);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string MeterCommonImplementation::renderJsonField(Print *p)
|
string MeterCommonImplementation::renderJsonOnlyDefaultUnit(string vname)
|
||||||
|
{
|
||||||
|
FieldInfo *fi = findFieldInfo(vname);
|
||||||
|
|
||||||
|
if (fi == NULL) return "unknown field "+vname;
|
||||||
|
return fi->renderJsonOnlyDefaultUnit();
|
||||||
|
}
|
||||||
|
|
||||||
|
string FieldInfo::renderJsonOnlyDefaultUnit()
|
||||||
|
{
|
||||||
|
return renderJson(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
string FieldInfo::renderJsonText()
|
||||||
|
{
|
||||||
|
return renderJson(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
string FieldInfo::renderJson(vector<Unit> *conversions)
|
||||||
{
|
{
|
||||||
string s;
|
string s;
|
||||||
|
|
||||||
string default_unit = unitToStringLowerCase(p->default_unit);
|
string default_unit = unitToStringLowerCase(defaultUnit());
|
||||||
string var = p->vname;
|
string var = vname();
|
||||||
if (p->getValueString) {
|
if (hasGetValueString())
|
||||||
s += "\""+var+"\":\""+p->getValueString()+"\"";
|
{
|
||||||
|
s += "\""+var+"\":\""+getValueString()+"\"";
|
||||||
}
|
}
|
||||||
if (p->getValueDouble) {
|
else if (hasGetValueDouble())
|
||||||
s += "\""+var+"_"+default_unit+"\":"+valueToString(p->getValueDouble(p->default_unit), p->default_unit);
|
{
|
||||||
|
s += "\""+var+"_"+default_unit+"\":"+valueToString(getValueDouble(defaultUnit()), defaultUnit());
|
||||||
|
|
||||||
Unit u = replaceWithConversionUnit(p->default_unit, conversions_);
|
if (conversions != NULL)
|
||||||
if (u != p->default_unit)
|
|
||||||
{
|
{
|
||||||
string unit = unitToStringLowerCase(u);
|
Unit u = replaceWithConversionUnit(defaultUnit(), *conversions);
|
||||||
// Appending extra conversion unit.
|
if (u != defaultUnit())
|
||||||
s += ",\""+var+"_"+unit+"\":"+valueToString(p->getValueDouble(u), u);
|
{
|
||||||
|
string unit = unitToStringLowerCase(u);
|
||||||
|
// Appending extra conversion unit.
|
||||||
|
s += ",\""+var+"_"+unit+"\":"+valueToString(getValueDouble(u), u);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s = "?";
|
||||||
|
}
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1092,11 +1421,11 @@ void MeterCommonImplementation::printMeter(Telegram *t,
|
||||||
{
|
{
|
||||||
s += "\"id\":\"\",";
|
s += "\"id\":\"\",";
|
||||||
}
|
}
|
||||||
for (Print& p : prints_)
|
for (FieldInfo& p : prints_)
|
||||||
{
|
{
|
||||||
if (p.json)
|
if (p.json())
|
||||||
{
|
{
|
||||||
s += renderJsonField(&p)+",";
|
s += p.renderJson(&conversions())+",";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s += "\"timestamp\":\""+datetimeOfUpdateRobot()+"\"";
|
s += "\"timestamp\":\""+datetimeOfUpdateRobot()+"\"";
|
||||||
|
@ -1142,23 +1471,23 @@ void MeterCommonImplementation::printMeter(Telegram *t,
|
||||||
envs->push_back(string("METER_RSSI_DBM=")+to_string(t->about.rssi_dbm));
|
envs->push_back(string("METER_RSSI_DBM=")+to_string(t->about.rssi_dbm));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Print p : prints_)
|
for (FieldInfo p : prints_)
|
||||||
{
|
{
|
||||||
if (p.json)
|
if (p.json())
|
||||||
{
|
{
|
||||||
string default_unit = unitToStringUpperCase(p.default_unit);
|
string default_unit = unitToStringUpperCase(p.defaultUnit());
|
||||||
string var = p.vname;
|
string var = p.vname();
|
||||||
std::transform(var.begin(), var.end(), var.begin(), ::toupper);
|
std::transform(var.begin(), var.end(), var.begin(), ::toupper);
|
||||||
if (p.getValueString) {
|
if (p.hasGetValueString()) {
|
||||||
string envvar = "METER_"+var+"="+p.getValueString();
|
string envvar = "METER_"+var+"="+p.getValueString();
|
||||||
envs->push_back(envvar);
|
envs->push_back(envvar);
|
||||||
}
|
}
|
||||||
if (p.getValueDouble) {
|
if (p.hasGetValueDouble()) {
|
||||||
string envvar = "METER_"+var+"_"+default_unit+"="+valueToString(p.getValueDouble(p.default_unit), p.default_unit);
|
string envvar = "METER_"+var+"_"+default_unit+"="+valueToString(p.getValueDouble(p.defaultUnit()), p.defaultUnit());
|
||||||
envs->push_back(envvar);
|
envs->push_back(envvar);
|
||||||
|
|
||||||
Unit u = replaceWithConversionUnit(p.default_unit, conversions_);
|
Unit u = replaceWithConversionUnit(p.defaultUnit(), conversions_);
|
||||||
if (u != p.default_unit)
|
if (u != p.defaultUnit())
|
||||||
{
|
{
|
||||||
string unit = unitToStringUpperCase(u);
|
string unit = unitToStringUpperCase(u);
|
||||||
string envvar = "METER_"+var+"_"+unit+"="+valueToString(p.getValueDouble(u), u);
|
string envvar = "METER_"+var+"_"+unit+"="+valueToString(p.getValueDouble(u), u);
|
||||||
|
@ -1205,6 +1534,14 @@ void detectMeterDrivers(int manufacturer, int media, int version, vector<string>
|
||||||
#define X(TY,MA,ME,VE) { if (manufacturer == MA && (media == ME || ME == -1) && (version == VE || VE == -1)) { drivers->push_back(toString(MeterDriver::TY)); }}
|
#define X(TY,MA,ME,VE) { if (manufacturer == MA && (media == ME || ME == -1) && (version == VE || VE == -1)) { drivers->push_back(toString(MeterDriver::TY)); }}
|
||||||
METER_DETECTION
|
METER_DETECTION
|
||||||
#undef X
|
#undef X
|
||||||
|
|
||||||
|
for (auto &p : all_registered_drivers_)
|
||||||
|
{
|
||||||
|
if (p.second.detect(manufacturer, media, version))
|
||||||
|
{
|
||||||
|
drivers->push_back(p.second.name().str());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isMeterDriverValid(MeterDriver type, int manufacturer, int media, int version)
|
bool isMeterDriverValid(MeterDriver type, int manufacturer, int media, int version)
|
||||||
|
@ -1213,10 +1550,23 @@ bool isMeterDriverValid(MeterDriver type, int manufacturer, int media, int versi
|
||||||
METER_DETECTION
|
METER_DETECTION
|
||||||
#undef X
|
#undef X
|
||||||
|
|
||||||
|
for (auto &p : all_registered_drivers_)
|
||||||
|
{
|
||||||
|
if (p.second.detect(manufacturer, media, version))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
MeterDriver pickMeterDriver(Telegram *t)
|
vector<DriverInfo>& allRegisteredDrivers()
|
||||||
|
{
|
||||||
|
return all_registered_drivers_list_;
|
||||||
|
}
|
||||||
|
|
||||||
|
DriverInfo pickMeterDriver(Telegram *t)
|
||||||
{
|
{
|
||||||
int manufacturer = t->dll_mfct;
|
int manufacturer = t->dll_mfct;
|
||||||
int media = t->dll_type;
|
int media = t->dll_type;
|
||||||
|
@ -1232,6 +1582,15 @@ MeterDriver pickMeterDriver(Telegram *t)
|
||||||
#define X(TY,MA,ME,VE) { if (manufacturer == MA && (media == ME || ME == -1) && (version == VE || VE == -1)) { return MeterDriver::TY; }}
|
#define X(TY,MA,ME,VE) { if (manufacturer == MA && (media == ME || ME == -1) && (version == VE || VE == -1)) { return MeterDriver::TY; }}
|
||||||
METER_DETECTION
|
METER_DETECTION
|
||||||
#undef X
|
#undef X
|
||||||
|
|
||||||
|
for (auto &p : all_registered_drivers_)
|
||||||
|
{
|
||||||
|
if (p.second.detect(manufacturer, media, version))
|
||||||
|
{
|
||||||
|
return p.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return MeterDriver::UNKNOWN;
|
return MeterDriver::UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1241,6 +1600,19 @@ shared_ptr<Meter> createMeter(MeterInfo *mi)
|
||||||
|
|
||||||
const char *keymsg = (mi->key[0] == 0) ? "not-encrypted" : "encrypted";
|
const char *keymsg = (mi->key[0] == 0) ? "not-encrypted" : "encrypted";
|
||||||
|
|
||||||
|
if (all_registered_drivers_.count(mi->driver_name.str()) != 0)
|
||||||
|
{
|
||||||
|
DriverInfo& di = all_registered_drivers_[mi->driver_name.str()];
|
||||||
|
shared_ptr<Meter> newm = di.construct(*mi);
|
||||||
|
newm->addConversions(mi->conversions);
|
||||||
|
verbose("(meter) constructed \"%s\" \"%s\" \"%s\" %s\n",
|
||||||
|
mi->name.c_str(),
|
||||||
|
di.name().str().c_str(),
|
||||||
|
mi->idsc.c_str(),
|
||||||
|
keymsg);
|
||||||
|
return newm;
|
||||||
|
}
|
||||||
|
|
||||||
switch (mi->driver)
|
switch (mi->driver)
|
||||||
{
|
{
|
||||||
#define X(mname,link,info,driver,cname) \
|
#define X(mname,link,info,driver,cname) \
|
||||||
|
@ -1259,11 +1631,11 @@ LIST_OF_METERS
|
||||||
return newm;
|
return newm;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_driver_extras(string t, MeterDriver *out_driver, string *out_extras)
|
bool is_driver_extras(string t, MeterDriver *out_driver, DriverName *out_driver_name, string *out_extras)
|
||||||
{
|
{
|
||||||
// piigth(jump=foo)
|
// piigth(jump=foo)
|
||||||
// multical21
|
// multical21
|
||||||
|
DriverInfo di;
|
||||||
size_t ps = t.find('(');
|
size_t ps = t.find('(');
|
||||||
size_t pe = t.find(')');
|
size_t pe = t.find(')');
|
||||||
|
|
||||||
|
@ -1273,7 +1645,14 @@ bool is_driver_extras(string t, MeterDriver *out_driver, string *out_extras)
|
||||||
|
|
||||||
if (!found_parentheses)
|
if (!found_parentheses)
|
||||||
{
|
{
|
||||||
// No brackets nor parentheses found, is t a known wmbus device? like im871a amb8465 etc....
|
if (lookupDriverInfo(t, &di))
|
||||||
|
{
|
||||||
|
*out_driver_name = di.name();
|
||||||
|
// We found a registered driver.
|
||||||
|
*out_driver = MeterDriver::AUTO; // To go away!
|
||||||
|
*out_extras = "";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
MeterDriver md = toMeterDriver(t);
|
MeterDriver md = toMeterDriver(t);
|
||||||
if (md == MeterDriver::UNKNOWN) return false;
|
if (md == MeterDriver::UNKNOWN) return false;
|
||||||
*out_driver = md;
|
*out_driver = md;
|
||||||
|
@ -1286,8 +1665,18 @@ bool is_driver_extras(string t, MeterDriver *out_driver, string *out_extras)
|
||||||
te = ps;
|
te = ps;
|
||||||
|
|
||||||
string type = t.substr(0, te);
|
string type = t.substr(0, te);
|
||||||
|
|
||||||
|
bool found = lookupDriverInfo(type, &di);
|
||||||
|
|
||||||
MeterDriver md = toMeterDriver(type);
|
MeterDriver md = toMeterDriver(type);
|
||||||
if (md == MeterDriver::UNKNOWN) return false;
|
if (found)
|
||||||
|
{
|
||||||
|
*out_driver_name = di.name();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (md == MeterDriver::UNKNOWN) return false;
|
||||||
|
}
|
||||||
*out_driver = md;
|
*out_driver = md;
|
||||||
|
|
||||||
string extras = t.substr(ps+1, pe-ps-1);
|
string extras = t.substr(ps+1, pe-ps-1);
|
||||||
|
@ -1336,7 +1725,7 @@ bool MeterInfo::parse(string n, string d, string i, string k)
|
||||||
|
|
||||||
for (auto& p : parts)
|
for (auto& p : parts)
|
||||||
{
|
{
|
||||||
if (!driverextras_checked && is_driver_extras(p, &driver, &extras))
|
if (!driverextras_checked && is_driver_extras(p, &driver, &driver_name, &extras))
|
||||||
{
|
{
|
||||||
driverextras_checked = true;
|
driverextras_checked = true;
|
||||||
}
|
}
|
||||||
|
@ -1402,3 +1791,17 @@ bool isValidKey(string& key, MeterDriver mt)
|
||||||
vector<uchar> tmp;
|
vector<uchar> tmp;
|
||||||
return hex2bin(key, &tmp);
|
return hex2bin(key, &tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void FieldInfo::performExtraction(Meter *m, Telegram *t)
|
||||||
|
{
|
||||||
|
if (extract_double_)
|
||||||
|
{
|
||||||
|
extract_double_(this, m, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extract_string_)
|
||||||
|
{
|
||||||
|
extract_string_(this, m, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
193
src/meters.h
193
src/meters.h
|
@ -18,6 +18,7 @@
|
||||||
#ifndef METER_H_
|
#ifndef METER_H_
|
||||||
#define METER_H_
|
#define METER_H_
|
||||||
|
|
||||||
|
#include"dvparser.h"
|
||||||
#include"util.h"
|
#include"util.h"
|
||||||
#include"units.h"
|
#include"units.h"
|
||||||
#include"wmbus.h"
|
#include"wmbus.h"
|
||||||
|
@ -28,6 +29,7 @@
|
||||||
#include<vector>
|
#include<vector>
|
||||||
|
|
||||||
#define LIST_OF_METER_TYPES \
|
#define LIST_OF_METER_TYPES \
|
||||||
|
X(AutoMeter) \
|
||||||
X(UnknownMeter) \
|
X(UnknownMeter) \
|
||||||
X(DoorWindowDetector) \
|
X(DoorWindowDetector) \
|
||||||
X(ElectricityMeter) \
|
X(ElectricityMeter) \
|
||||||
|
@ -46,9 +48,8 @@ LIST_OF_METER_TYPES
|
||||||
};
|
};
|
||||||
|
|
||||||
#define LIST_OF_METERS \
|
#define LIST_OF_METERS \
|
||||||
X(auto, 0, UnknownMeter, AUTO, Auto) \
|
X(auto, 0, AutoMeter, AUTO, Auto) \
|
||||||
X(unknown, 0, UnknownMeter, UNKNOWN, Unknown) \
|
X(unknown, 0, UnknownMeter, UNKNOWN, Unknown) \
|
||||||
X(amiplus, T1_bit, ElectricityMeter, AMIPLUS, Amiplus) \
|
|
||||||
X(apator08, T1_bit, WaterMeter, APATOR08, Apator08) \
|
X(apator08, T1_bit, WaterMeter, APATOR08, Apator08) \
|
||||||
X(apator162, C1_bit|T1_bit, WaterMeter, APATOR162, Apator162) \
|
X(apator162, C1_bit|T1_bit, WaterMeter, APATOR162, Apator162) \
|
||||||
X(aventieswm, T1_bit, WaterMeter, AVENTIESWM, AventiesWM) \
|
X(aventieswm, T1_bit, WaterMeter, AVENTIESWM, AventiesWM) \
|
||||||
|
@ -78,7 +79,6 @@ LIST_OF_METER_TYPES
|
||||||
X(izar3, T1_bit, WaterMeter, IZAR3, Izar3) \
|
X(izar3, T1_bit, WaterMeter, IZAR3, Izar3) \
|
||||||
X(lansensm, T1_bit, SmokeDetector, LANSENSM, LansenSM) \
|
X(lansensm, T1_bit, SmokeDetector, LANSENSM, LansenSM) \
|
||||||
X(lansenth, T1_bit, TempHygroMeter, LANSENTH, LansenTH) \
|
X(lansenth, T1_bit, TempHygroMeter, LANSENTH, LansenTH) \
|
||||||
X(lansendw, T1_bit, DoorWindowDetector, LANSENDW, LansenDW) \
|
|
||||||
X(lansenpu, T1_bit, PulseCounter, LANSENPU, LansenPU) \
|
X(lansenpu, T1_bit, PulseCounter, LANSENPU, LansenPU) \
|
||||||
X(lse_07_17, S1_bit, WaterMeter, LSE_07_17, LSE_07_17) \
|
X(lse_07_17, S1_bit, WaterMeter, LSE_07_17, LSE_07_17) \
|
||||||
X(minomess, C1_bit, WaterMeter, MINOMESS, Minomess) \
|
X(minomess, C1_bit, WaterMeter, MINOMESS, Minomess) \
|
||||||
|
@ -123,6 +123,16 @@ LIST_OF_METERS
|
||||||
#undef X
|
#undef X
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DriverName
|
||||||
|
{
|
||||||
|
DriverName() {};
|
||||||
|
DriverName(string s) : name_(s) {};
|
||||||
|
string str() { return name_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
string name_;
|
||||||
|
};
|
||||||
|
|
||||||
struct MeterMatch
|
struct MeterMatch
|
||||||
{
|
{
|
||||||
MeterDriver driver;
|
MeterDriver driver;
|
||||||
|
@ -139,8 +149,6 @@ void detectMeterDrivers(int manufacturer, int media, int version, std::vector<st
|
||||||
// When entering the driver, check that the telegram is indeed known to be
|
// When entering the driver, check that the telegram is indeed known to be
|
||||||
// compatible with the driver(type), if not then print a warning.
|
// compatible with the driver(type), if not then print a warning.
|
||||||
bool isMeterDriverValid(MeterDriver type, int manufacturer, int media, int version);
|
bool isMeterDriverValid(MeterDriver type, int manufacturer, int media, int version);
|
||||||
// Return the best driver match for a telegram.
|
|
||||||
MeterDriver pickMeterDriver(Telegram *t);
|
|
||||||
|
|
||||||
bool isValidKey(string& key, MeterDriver mt);
|
bool isValidKey(string& key, MeterDriver mt);
|
||||||
|
|
||||||
|
@ -155,6 +163,7 @@ struct MeterInfo
|
||||||
// The bus can be the empty string, which means that it will fallback to the first defined bus.
|
// The bus can be the empty string, which means that it will fallback to the first defined bus.
|
||||||
string name; // User specified name of this (group of) meters.
|
string name; // User specified name of this (group of) meters.
|
||||||
MeterDriver driver {}; // Requested driver for decoding telegrams from this meter.
|
MeterDriver driver {}; // Requested driver for decoding telegrams from this meter.
|
||||||
|
DriverName driver_name; // Will replace MeterDriver.
|
||||||
string extras; // Extra driver specific settings.
|
string extras; // Extra driver specific settings.
|
||||||
vector<string> ids; // Match expressions for ids.
|
vector<string> ids; // Match expressions for ids.
|
||||||
string idsc; // Comma separated ids.
|
string idsc; // Comma separated ids.
|
||||||
|
@ -223,34 +232,152 @@ struct DriverDetect
|
||||||
|
|
||||||
struct DriverInfo
|
struct DriverInfo
|
||||||
{
|
{
|
||||||
string name; // amiplus, lse_07_17, multical21 etc
|
private:
|
||||||
LinkModeSet linkmodes; // C1, T1, S1 or combinations thereof.
|
|
||||||
MeterType type; // Water, Electricity etc.
|
MeterDriver driver_ {}; // Old driver enum, to go away.
|
||||||
function<shared_ptr<Meter>(MeterInfo&)> constructor; // Invoke this to create an instance of the driver.
|
DriverName name_; // auto, unknown, amiplus, lse_07_17, multical21 etc
|
||||||
vector<DriverDetect> detect;
|
LinkModeSet linkmodes_; // C1, T1, S1 or combinations thereof.
|
||||||
|
MeterType type_; // Water, Electricity etc.
|
||||||
|
function<shared_ptr<Meter>(MeterInfo&,DriverInfo&di)> constructor_; // Invoke this to create an instance of the driver.
|
||||||
|
vector<DriverDetect> detect_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DriverInfo() {};
|
||||||
|
DriverInfo(MeterDriver mt) : driver_(mt) {};
|
||||||
|
void setName(std::string n) { name_ = n; }
|
||||||
|
void setMeterType(MeterType t) { type_ = t; }
|
||||||
|
void setExpectedELLSecurityMode(ELLSecurityMode dsm);
|
||||||
|
void setExpectedTPLSecurityMode(TPLSecurityMode tsm);
|
||||||
|
|
||||||
|
void addLinkMode(LinkMode lm) { linkmodes_.addLinkMode(lm); }
|
||||||
|
void setConstructor(function<shared_ptr<Meter>(MeterInfo&,DriverInfo&)> c) { constructor_ = c; }
|
||||||
|
void addDetection(uint16_t mfct, uchar type, uchar ver) { detect_.push_back({ mfct, type, ver }); }
|
||||||
|
vector<DriverDetect> &detect() { return detect_; }
|
||||||
|
|
||||||
|
MeterDriver driver() { return driver_; }
|
||||||
|
DriverName name() { return name_; }
|
||||||
|
MeterType type() { return type_; }
|
||||||
|
LinkModeSet linkModes() { return linkmodes_; }
|
||||||
|
shared_ptr<Meter> construct(MeterInfo& mi) { return constructor_(mi, *this); }
|
||||||
|
bool detect(uint16_t mfct, uchar type, uchar version);
|
||||||
};
|
};
|
||||||
|
|
||||||
// The function addDriver is called as part of the static initialization inside a driver class.
|
bool registerDriver(function<void(DriverInfo&di)> setup);
|
||||||
|
bool lookupDriverInfo(string& driver, DriverInfo *di);
|
||||||
|
// Return the best driver match for a telegram.
|
||||||
|
DriverInfo pickMeterDriver(Telegram *t);
|
||||||
|
|
||||||
DriverInfo addDriver(string n,
|
vector<DriverInfo>& allRegisteredDrivers();
|
||||||
LinkModeSet lms,
|
|
||||||
MeterType t,
|
|
||||||
function<shared_ptr<Meter>(MeterInfo&)> constructor,
|
|
||||||
vector<DriverDetect> d);
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
struct Print
|
enum class VifScaling
|
||||||
{
|
{
|
||||||
string vname; // Value name, like: total current previous target
|
None,
|
||||||
Quantity quantity; // Quantity: Energy, Volume
|
Auto
|
||||||
Unit default_unit; // Default unit for above quantity: KWH, M3
|
};
|
||||||
function<double(Unit)> getValueDouble; // Callback to fetch the value from the meter.
|
|
||||||
function<string()> getValueString; // Callback to fetch the value from the meter.
|
struct FieldInfo
|
||||||
string help; // Helpful information on this meters use of this value.
|
{
|
||||||
bool field; // If true, print in hr/fields output.
|
FieldInfo(string vname,
|
||||||
bool json; // If true, print in json and shell env variables.
|
Quantity xuantity,
|
||||||
string field_name; // Field name for default unit.
|
Unit default_unit,
|
||||||
|
DifVifKey dif_vif_key,
|
||||||
|
VifScaling vif_scaling,
|
||||||
|
MeasurementType measurement_type,
|
||||||
|
ValueInformation value_information,
|
||||||
|
StorageNr storage_nr,
|
||||||
|
TariffNr tariff_nr,
|
||||||
|
IndexNr index_nr,
|
||||||
|
string help,
|
||||||
|
bool field,
|
||||||
|
bool json,
|
||||||
|
bool important,
|
||||||
|
string field_name,
|
||||||
|
function<double(Unit)> get_value_double,
|
||||||
|
function<string()> get_value_string,
|
||||||
|
function<void(Unit,double)> set_value_double,
|
||||||
|
function<void(string)> set_value_string,
|
||||||
|
function<bool(FieldInfo*, Meter *mi, Telegram *t)> extract_double,
|
||||||
|
function<bool(FieldInfo*, Meter *mi, Telegram *t)> extract_string
|
||||||
|
) :
|
||||||
|
vname_(vname),
|
||||||
|
xuantity_(xuantity),
|
||||||
|
default_unit_(default_unit),
|
||||||
|
dif_vif_key_(dif_vif_key),
|
||||||
|
vif_scaling_(vif_scaling),
|
||||||
|
measurement_type_(measurement_type),
|
||||||
|
value_information_(value_information),
|
||||||
|
storage_nr_(storage_nr),
|
||||||
|
tariff_nr_(tariff_nr),
|
||||||
|
index_nr_(index_nr),
|
||||||
|
help_(help),
|
||||||
|
field_(field),
|
||||||
|
json_(json),
|
||||||
|
important_(important),
|
||||||
|
field_name_(field_name),
|
||||||
|
get_value_double_(get_value_double),
|
||||||
|
get_value_string_(get_value_string),
|
||||||
|
set_value_double_(set_value_double),
|
||||||
|
set_value_string_(set_value_string),
|
||||||
|
extract_double_(extract_double),
|
||||||
|
extract_string_(extract_string)
|
||||||
|
{}
|
||||||
|
|
||||||
|
string vname() { return vname_; }
|
||||||
|
Quantity xuantity() { return xuantity_; }
|
||||||
|
Unit defaultUnit() { return default_unit_; }
|
||||||
|
DifVifKey difVifKey() { return dif_vif_key_; }
|
||||||
|
VifScaling vifScaling() { return vif_scaling_; }
|
||||||
|
MeasurementType measurementType() { return measurement_type_; }
|
||||||
|
ValueInformation valueInformation() { return value_information_; }
|
||||||
|
StorageNr storageNr() { return storage_nr_; }
|
||||||
|
TariffNr tariffNr() { return tariff_nr_; }
|
||||||
|
IndexNr indexNr() { return index_nr_; }
|
||||||
|
string help() { return help_; }
|
||||||
|
bool field() { return field_; }
|
||||||
|
bool json() { return json_; }
|
||||||
|
bool important() { return important_; }
|
||||||
|
string fieldName() { return field_name_; }
|
||||||
|
|
||||||
|
double getValueDouble(Unit u) { if (get_value_double_) return get_value_double_(u); else return -12345678; }
|
||||||
|
bool hasGetValueDouble() { return get_value_double_ != NULL; }
|
||||||
|
string getValueString() { if (get_value_string_) return get_value_string_(); else return "?"; }
|
||||||
|
bool hasGetValueString() { return get_value_string_ != NULL; }
|
||||||
|
|
||||||
|
void setValueDouble(Unit u, double d) { if (set_value_double_) set_value_double_(u, d); }
|
||||||
|
void setValueString(string s) { if (set_value_string_) set_value_string_(s); }
|
||||||
|
|
||||||
|
void performExtraction(Meter *m, Telegram *t);
|
||||||
|
|
||||||
|
string renderJsonOnlyDefaultUnit();
|
||||||
|
string renderJson(vector<Unit> *additional_conversions);
|
||||||
|
string renderJsonText();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
string vname_; // Value name, like: total current previous target
|
||||||
|
Quantity xuantity_; // Quantity: Energy, Volume
|
||||||
|
Unit default_unit_; // Default unit for above quantity: KWH, M3
|
||||||
|
DifVifKey dif_vif_key_; // Hardcoded difvif key, if empty string then search for mt,vi,s,t,i instead.
|
||||||
|
VifScaling vif_scaling_;
|
||||||
|
MeasurementType measurement_type_;
|
||||||
|
ValueInformation value_information_;
|
||||||
|
StorageNr storage_nr_;
|
||||||
|
TariffNr tariff_nr_;
|
||||||
|
IndexNr index_nr_;
|
||||||
|
string help_; // Helpful information on this meters use of this value.
|
||||||
|
bool field_; // If true, print in hr/fields output.
|
||||||
|
bool json_; // If true, print in json and shell env variables.
|
||||||
|
bool important_; // If true, then print this for --format=hr and in the summary when listening to all.
|
||||||
|
string field_name_; // Field name for default unit.
|
||||||
|
|
||||||
|
function<double(Unit)> get_value_double_; // Callback to fetch the value from the meter.
|
||||||
|
function<string()> get_value_string_; // Callback to fetch the value from the meter.
|
||||||
|
function<void(Unit,double)> set_value_double_; // Call back to set the value in the c++ object
|
||||||
|
function<void(string)> set_value_string_; // Call back to set the value string in the c++ object
|
||||||
|
function<bool(FieldInfo*, Meter *mi, Telegram *t)> extract_double_; // Extract field from telegram and insert into meter.
|
||||||
|
function<bool(FieldInfo*, Meter *mi, Telegram *t)> extract_string_; // Extract field from telegram and insert into meter.
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BusManager;
|
struct BusManager;
|
||||||
|
@ -269,10 +396,11 @@ struct Meter
|
||||||
virtual string idsc() = 0;
|
virtual string idsc() = 0;
|
||||||
// This meter can report these fields, like total_m3, temp_c.
|
// This meter can report these fields, like total_m3, temp_c.
|
||||||
virtual vector<string> fields() = 0;
|
virtual vector<string> fields() = 0;
|
||||||
virtual vector<Print> prints() = 0;
|
virtual vector<FieldInfo> prints() = 0;
|
||||||
virtual string meterDriver() = 0;
|
virtual string meterDriver() = 0;
|
||||||
virtual string name() = 0;
|
virtual string name() = 0;
|
||||||
virtual MeterDriver driver() = 0;
|
virtual MeterDriver driver() = 0;
|
||||||
|
virtual DriverName driverName() = 0;
|
||||||
|
|
||||||
virtual string datetimeOfUpdateHumanReadable() = 0;
|
virtual string datetimeOfUpdateHumanReadable() = 0;
|
||||||
virtual string datetimeOfUpdateRobot() = 0;
|
virtual string datetimeOfUpdateRobot() = 0;
|
||||||
|
@ -296,16 +424,15 @@ struct Meter
|
||||||
bool simulated, string *id, bool *id_match, Telegram *out_t = NULL) = 0;
|
bool simulated, string *id, bool *id_match, Telegram *out_t = NULL) = 0;
|
||||||
virtual MeterKeys *meterKeys() = 0;
|
virtual MeterKeys *meterKeys() = 0;
|
||||||
|
|
||||||
// Dynamically access all data received for the meter.
|
|
||||||
virtual std::vector<std::string> getRecords() = 0;
|
|
||||||
virtual double getRecordAsDouble(std::string record) = 0;
|
|
||||||
virtual uint16_t getRecordAsUInt16(std::string record) = 0;
|
|
||||||
|
|
||||||
virtual void addConversions(std::vector<Unit> cs) = 0;
|
virtual void addConversions(std::vector<Unit> cs) = 0;
|
||||||
|
virtual vector<Unit>& conversions() = 0;
|
||||||
virtual void addShell(std::string cmdline) = 0;
|
virtual void addShell(std::string cmdline) = 0;
|
||||||
virtual vector<string> &shellCmdlines() = 0;
|
virtual vector<string> &shellCmdlines() = 0;
|
||||||
virtual void poll(shared_ptr<BusManager> bus) = 0;
|
virtual void poll(shared_ptr<BusManager> bus) = 0;
|
||||||
|
|
||||||
|
virtual FieldInfo *findFieldInfo(string vname) = 0;
|
||||||
|
virtual string renderJsonOnlyDefaultUnit(string vname) = 0;
|
||||||
|
|
||||||
virtual ~Meter() = default;
|
virtual ~Meter() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -330,7 +457,9 @@ struct MeterManager
|
||||||
|
|
||||||
shared_ptr<MeterManager> createMeterManager(bool daemon);
|
shared_ptr<MeterManager> createMeterManager(bool daemon);
|
||||||
|
|
||||||
|
const char *toString(MeterType type);
|
||||||
string toString(MeterDriver driver);
|
string toString(MeterDriver driver);
|
||||||
|
string toString(DriverInfo &driver);
|
||||||
MeterDriver toMeterDriver(string& driver);
|
MeterDriver toMeterDriver(string& driver);
|
||||||
LinkModeSet toMeterLinkModeSet(string& driver);
|
LinkModeSet toMeterLinkModeSet(string& driver);
|
||||||
LinkModeSet toMeterLinkModeSet(MeterDriver driver);
|
LinkModeSet toMeterLinkModeSet(MeterDriver driver);
|
||||||
|
|
|
@ -18,12 +18,20 @@
|
||||||
#ifndef METERS_COMMON_IMPLEMENTATION_H_
|
#ifndef METERS_COMMON_IMPLEMENTATION_H_
|
||||||
#define METERS_COMMON_IMPLEMENTATION_H_
|
#define METERS_COMMON_IMPLEMENTATION_H_
|
||||||
|
|
||||||
|
#include"dvparser.h"
|
||||||
#include"meters.h"
|
#include"meters.h"
|
||||||
#include"units.h"
|
#include"units.h"
|
||||||
|
|
||||||
#include<map>
|
#include<map>
|
||||||
#include<set>
|
#include<set>
|
||||||
|
|
||||||
|
enum PrintProperty
|
||||||
|
{
|
||||||
|
JSON = 1, // This field should be printed when using --format=json
|
||||||
|
FIELD = 2, // This field should be printed when using --format=field
|
||||||
|
IMPORTANT = 4, // The most important field.
|
||||||
|
};
|
||||||
|
|
||||||
struct MeterCommonImplementation : public virtual Meter
|
struct MeterCommonImplementation : public virtual Meter
|
||||||
{
|
{
|
||||||
int index();
|
int index();
|
||||||
|
@ -32,9 +40,10 @@ struct MeterCommonImplementation : public virtual Meter
|
||||||
vector<string>& ids();
|
vector<string>& ids();
|
||||||
string idsc();
|
string idsc();
|
||||||
vector<string> fields();
|
vector<string> fields();
|
||||||
vector<Print> prints();
|
vector<FieldInfo> prints();
|
||||||
string name();
|
string name();
|
||||||
MeterDriver driver();
|
MeterDriver driver();
|
||||||
|
DriverName driverName();
|
||||||
|
|
||||||
ELLSecurityMode expectedELLSecurityMode();
|
ELLSecurityMode expectedELLSecurityMode();
|
||||||
TPLSecurityMode expectedTPLSecurityMode();
|
TPLSecurityMode expectedTPLSecurityMode();
|
||||||
|
@ -49,11 +58,8 @@ struct MeterCommonImplementation : public virtual Meter
|
||||||
static bool isTelegramForMeter(Telegram *t, Meter *meter, MeterInfo *mi);
|
static bool isTelegramForMeter(Telegram *t, Meter *meter, MeterInfo *mi);
|
||||||
MeterKeys *meterKeys();
|
MeterKeys *meterKeys();
|
||||||
|
|
||||||
std::vector<std::string> getRecords();
|
|
||||||
double getRecordAsDouble(std::string record);
|
|
||||||
uint16_t getRecordAsUInt16(std::string record);
|
|
||||||
|
|
||||||
MeterCommonImplementation(MeterInfo &mi, string driver);
|
MeterCommonImplementation(MeterInfo &mi, string driver);
|
||||||
|
MeterCommonImplementation(MeterInfo &mi, DriverInfo &di);
|
||||||
|
|
||||||
~MeterCommonImplementation() = default;
|
~MeterCommonImplementation() = default;
|
||||||
|
|
||||||
|
@ -65,6 +71,7 @@ protected:
|
||||||
void setExpectedELLSecurityMode(ELLSecurityMode dsm);
|
void setExpectedELLSecurityMode(ELLSecurityMode dsm);
|
||||||
void setExpectedTPLSecurityMode(TPLSecurityMode tsm);
|
void setExpectedTPLSecurityMode(TPLSecurityMode tsm);
|
||||||
void addConversions(std::vector<Unit> cs);
|
void addConversions(std::vector<Unit> cs);
|
||||||
|
std::vector<Unit>& conversions() { return conversions_; }
|
||||||
void addShell(std::string cmdline);
|
void addShell(std::string cmdline);
|
||||||
void addExtraConstantField(std::string ecf);
|
void addExtraConstantField(std::string ecf);
|
||||||
std::vector<std::string> &shellCmdlines();
|
std::vector<std::string> &shellCmdlines();
|
||||||
|
@ -81,6 +88,47 @@ protected:
|
||||||
void addPrint(string vname, Quantity vquantity,
|
void addPrint(string vname, Quantity vquantity,
|
||||||
function<std::string()> getValueFunc, string help, bool field, bool json);
|
function<std::string()> getValueFunc, string help, bool field, bool json);
|
||||||
|
|
||||||
|
#define SET_FUNC(varname,to_unit) {[&](Unit from_unit, double d){varname = convert(d, from_unit, to_unit);}}
|
||||||
|
#define GET_FUNC(varname,from_unit) {[&](Unit to_unit){return convert(varname, from_unit, to_unit);}}
|
||||||
|
|
||||||
|
void addFieldWithExtractor(
|
||||||
|
string vname, // Name of value without unit, eg total
|
||||||
|
Quantity vquantity, // Value belongs to this quantity.
|
||||||
|
DifVifKey dif_vif_key, // You can hardocde a dif vif header here or use NoDifVifKey
|
||||||
|
VifScaling vif_scaling,
|
||||||
|
MeasurementType mt, // If not using a hardcoded key, search for mt,vi,s,t and i instead.
|
||||||
|
ValueInformation vi,
|
||||||
|
StorageNr s,
|
||||||
|
TariffNr t,
|
||||||
|
IndexNr i,
|
||||||
|
int print_properties, // Should this be printed by default in fields,json and hr.
|
||||||
|
string help,
|
||||||
|
function<void(Unit,double)> setValueFunc, // Use the SET macro above.
|
||||||
|
function<double(Unit)> getValueFunc); // Use the GET macro above.
|
||||||
|
|
||||||
|
#define SET_STRING_FUNC(varname) {[&](string s){varname = s;}}
|
||||||
|
#define GET_STRING_FUNC(varname) {[&](){return varname; }}
|
||||||
|
|
||||||
|
void addStringFieldWithExtractor(
|
||||||
|
string vname, // Name of value without unit, eg total
|
||||||
|
Quantity vquantity, // Value belongs to this quantity.
|
||||||
|
DifVifKey dif_vif_key, // You can hardocde a dif vif header here or use NoDifVifKey
|
||||||
|
MeasurementType mt, // If not using a hardcoded key, search for mt,vi,s,t and i instead.
|
||||||
|
ValueInformation vi,
|
||||||
|
StorageNr s,
|
||||||
|
TariffNr t,
|
||||||
|
IndexNr i,
|
||||||
|
int print_properties, // Should this be printed by default in fields,json and hr.
|
||||||
|
string help,
|
||||||
|
function<void(string)> setValueFunc, // Use the SET_STRING macro above.
|
||||||
|
function<string()> getValueFunc); // Use the GET_STRING macro above.
|
||||||
|
|
||||||
|
// Decode a bit field and print
|
||||||
|
void addPrintTextFromBits(string vname, // Name of field, no suffix unit added since it is text.
|
||||||
|
string help, // An explanation of the field.
|
||||||
|
int props, // json,field,important
|
||||||
|
function<std::string(MeterCommonImplementation*)> getValueFunc);
|
||||||
|
|
||||||
// The default implementation of poll does nothing.
|
// The default implementation of poll does nothing.
|
||||||
// Override for mbus meters that need to be queried and likewise for C2/T2 wmbus-meters.
|
// Override for mbus meters that need to be queried and likewise for C2/T2 wmbus-meters.
|
||||||
void poll(shared_ptr<BusManager> bus);
|
void poll(shared_ptr<BusManager> bus);
|
||||||
|
@ -98,17 +146,18 @@ protected:
|
||||||
// since Json is assumed to be decoded by a program and the current timestamp which is the
|
// since Json is assumed to be decoded by a program and the current timestamp which is the
|
||||||
// same as timestamp_utc, can always be decoded/recoded into local time or a unix timestamp.
|
// same as timestamp_utc, can always be decoded/recoded into local time or a unix timestamp.
|
||||||
|
|
||||||
// Look a print using the vname and generate the single json key:value, eg "total_m3"=123.000
|
FieldInfo *findFieldInfo(string vname);
|
||||||
string renderJsonField(string vname);
|
string renderJsonOnlyDefaultUnit(string vname);
|
||||||
string renderJsonField(Print *p);
|
|
||||||
|
|
||||||
virtual void processContent(Telegram *t) = 0;
|
void processFieldExtractors(Telegram *t);
|
||||||
|
virtual void processContent(Telegram *t);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
int index_ {};
|
int index_ {};
|
||||||
MeterType type_ {};
|
MeterType type_ {};
|
||||||
string driver_ {};
|
string driver_ {};
|
||||||
|
DriverName driver_name_;
|
||||||
string bus_ {};
|
string bus_ {};
|
||||||
MeterKeys meter_keys_ {};
|
MeterKeys meter_keys_ {};
|
||||||
ELLSecurityMode expected_ell_sec_mode_ {};
|
ELLSecurityMode expected_ell_sec_mode_ {};
|
||||||
|
@ -126,7 +175,7 @@ private:
|
||||||
protected:
|
protected:
|
||||||
std::map<std::string,std::pair<int,std::string>> values_;
|
std::map<std::string,std::pair<int,std::string>> values_;
|
||||||
vector<Unit> conversions_;
|
vector<Unit> conversions_;
|
||||||
vector<Print> prints_;
|
vector<FieldInfo> prints_;
|
||||||
vector<string> fields_;
|
vector<string> fields_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
9
test.sh
9
test.sh
|
@ -24,8 +24,8 @@ if [ "$?" != "0" ]; then RC="1"; fi
|
||||||
tests/test_s1_meters.sh $PROG
|
tests/test_s1_meters.sh $PROG
|
||||||
if [ "$?" != "0" ]; then RC="1"; fi
|
if [ "$?" != "0" ]; then RC="1"; fi
|
||||||
|
|
||||||
tests/test_unknown.sh $PROG
|
#tests/test_unknown.sh $PROG
|
||||||
if [ "$?" != "0" ]; then RC="1"; fi
|
#if [ "$?" != "0" ]; then RC="1"; fi
|
||||||
|
|
||||||
tests/test_apas.sh $PROG
|
tests/test_apas.sh $PROG
|
||||||
if [ "$?" != "0" ]; then RC="1"; fi
|
if [ "$?" != "0" ]; then RC="1"; fi
|
||||||
|
@ -123,9 +123,12 @@ if [ "$?" != "0" ]; then RC="1"; fi
|
||||||
./tests/test_rtlwmbus_crc_errors.sh $PROG
|
./tests/test_rtlwmbus_crc_errors.sh $PROG
|
||||||
if [ "$?" != "0" ]; then RC="1"; fi
|
if [ "$?" != "0" ]; then RC="1"; fi
|
||||||
|
|
||||||
./tests/test_analyze.sh $PROG
|
./tests/test_drivers.sh $PROG
|
||||||
if [ "$?" != "0" ]; then RC="1"; fi
|
if [ "$?" != "0" ]; then RC="1"; fi
|
||||||
|
|
||||||
|
#./tests/test_analyze.sh $PROG
|
||||||
|
#if [ "$?" != "0" ]; then RC="1"; fi
|
||||||
|
|
||||||
if [ -x ../additional_tests.sh ]
|
if [ -x ../additional_tests.sh ]
|
||||||
then
|
then
|
||||||
(cd ..; ./additional_tests.sh $PROG)
|
(cd ..; ./additional_tests.sh $PROG)
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
PROG="$1"
|
||||||
|
|
||||||
|
mkdir -p testoutput
|
||||||
|
TEST=testoutput
|
||||||
|
|
||||||
|
echo "Testing drivers"
|
||||||
|
|
||||||
|
TESTNAME="Test driver tests"
|
||||||
|
TESTRESULT="ERROR"
|
||||||
|
|
||||||
|
ALL_DRIVERS=$(cd src; echo meter_*cc)
|
||||||
|
DRIVERS=
|
||||||
|
|
||||||
|
for i in $ALL_DRIVERS
|
||||||
|
do
|
||||||
|
if grep -q '// Test:' src/$i
|
||||||
|
then
|
||||||
|
DRIVERS="$DRIVERS $i"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for i in $DRIVERS
|
||||||
|
do
|
||||||
|
TESTNAME=$i
|
||||||
|
rm -f $TEST/*
|
||||||
|
METERS=$(cat src/$i | grep '// Test:' | cut -f 2 -d ':')
|
||||||
|
sed -n '/\/\/ Test:/,$p' src/$i | grep -e '// telegram' -e '// {' -e '// |' | sed 's|// ||g' > $TEST/simulation.txt
|
||||||
|
cat $TEST/simulation.txt | grep '^{' > $TEST/test_expected_json.txt
|
||||||
|
$PROG --format=json $TEST/simulation.txt $METERS > $TEST/test_output_json.txt 2> $TEST/test_stderr_json.txt
|
||||||
|
if [ "$?" = "0" ]
|
||||||
|
then
|
||||||
|
cat $TEST/test_output_json.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_response_json.txt
|
||||||
|
diff $TEST/test_expected_json.txt $TEST/test_response_json.txt
|
||||||
|
if [ "$?" = "0" ]
|
||||||
|
then
|
||||||
|
echo OK json: $TESTNAME
|
||||||
|
TESTRESULT="OK"
|
||||||
|
else
|
||||||
|
TESTRESULT="ERROR"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "wmbusmeters returned error code: $?"
|
||||||
|
cat $TEST/test_output_json.txt
|
||||||
|
cat $TEST/test_stderr_json.txt
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat $TEST/simulation.txt | grep '^|' | sed 's/^|//' > $TEST/test_expected_fields.txt
|
||||||
|
$PROG --format=fields $TEST/simulation.txt $METERS > $TEST/test_output_fields.txt 2> $TEST/test_stderr_fields.txt
|
||||||
|
if [ "$?" = "0" ]
|
||||||
|
then
|
||||||
|
cat $TEST/test_output_fields.txt | sed 's/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9].[0-9][0-9]$/1111-11-11 11:11.11/' > $TEST/test_response_fields.txt
|
||||||
|
diff $TEST/test_expected_fields.txt $TEST/test_response_fields.txt
|
||||||
|
if [ "$?" = "0" ]
|
||||||
|
then
|
||||||
|
echo OK fields: $TESTNAME
|
||||||
|
TESTRESULT="OK"
|
||||||
|
else
|
||||||
|
TESTRESULT="ERROR"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "wmbusmeters returned error code: $?"
|
||||||
|
cat $TEST/test_output_fields.txt
|
||||||
|
cat $TEST/test_stderr_fields.txt
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$TESTRESULT" = "ERROR" ]
|
||||||
|
then
|
||||||
|
echo ERROR: $TESTNAME
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
done
|
|
@ -8,9 +8,7 @@ TEST=testoutput
|
||||||
TESTNAME="Test meter with unknown driver"
|
TESTNAME="Test meter with unknown driver"
|
||||||
TESTRESULT="ERROR"
|
TESTRESULT="ERROR"
|
||||||
|
|
||||||
METERS="Dorren lansendw 00010205 NOKEY
|
METERS="Dorren lansendw 00010205 NOKEY Forren lansensm 00010206 NOKEY"
|
||||||
Forren lansensm 00010206 NOKEY
|
|
||||||
"
|
|
||||||
|
|
||||||
cat > $TEST/test_expected.txt <<EOF
|
cat > $TEST/test_expected.txt <<EOF
|
||||||
(meter) Dorren: meter detection did not match the selected driver lansendw! correct driver is: unknown!
|
(meter) Dorren: meter detection did not match the selected driver lansendw! correct driver is: unknown!
|
||||||
|
|
Ładowanie…
Reference in New Issue