kopia lustrzana https://github.com/weetmuts/wmbusmeters
Add formula for calculating new fields based on received fields. Refactor driver ebzwmbe.
rodzic
738ac16f3e
commit
b95c1feacb
4
Makefile
4
Makefile
|
@ -136,6 +136,7 @@ PROG_OBJS:=\
|
||||||
$(BUILD)/cmdline.o \
|
$(BUILD)/cmdline.o \
|
||||||
$(BUILD)/config.o \
|
$(BUILD)/config.o \
|
||||||
$(BUILD)/dvparser.o \
|
$(BUILD)/dvparser.o \
|
||||||
|
$(BUILD)/formula.o \
|
||||||
$(BUILD)/mbus_rawtty.o \
|
$(BUILD)/mbus_rawtty.o \
|
||||||
$(BUILD)/meters.o \
|
$(BUILD)/meters.o \
|
||||||
$(BUILD)/manufacturer_specificities.o \
|
$(BUILD)/manufacturer_specificities.o \
|
||||||
|
@ -289,6 +290,9 @@ testd:
|
||||||
testdriver:
|
testdriver:
|
||||||
@./tests/test_drivers.sh build/wmbusmeters driver_${DRIVER}.cc
|
@./tests/test_drivers.sh build/wmbusmeters driver_${DRIVER}.cc
|
||||||
|
|
||||||
|
testdriverd:
|
||||||
|
@./tests/test_drivers.sh build_debug/wmbusmeters driver_${DRIVER}.cc
|
||||||
|
|
||||||
update_manufacturers:
|
update_manufacturers:
|
||||||
iconv -f utf-8 -t ascii//TRANSLIT -c DLMS_Flagids.csv -o tmp.flags
|
iconv -f utf-8 -t ascii//TRANSLIT -c DLMS_Flagids.csv -o tmp.flags
|
||||||
cat tmp.flags | grep -v ^# | cut -f 1 > list.flags
|
cat tmp.flags | grep -v ^# | cut -f 1 > list.flags
|
||||||
|
|
|
@ -157,8 +157,8 @@ telegram=|5E4409077372727210077A710050052F2F_046D0110A92704130000000004933B00000
|
||||||
|
|
||||||
# Test electricity meter with eBZ wMB E01.
|
# Test electricity meter with eBZ wMB E01.
|
||||||
telegram=|5B445A149922992202378C20F6900F002C25BC9E0000BF48954821BC508D72992299225A140102F6003007102F2F040330F92A0004A9FF01FF24000004A9FF026A29000004A9FF03460600000DFD11063132333435362F2F2F2F2F2F|
|
telegram=|5B445A149922992202378C20F6900F002C25BC9E0000BF48954821BC508D72992299225A140102F6003007102F2F040330F92A0004A9FF01FF24000004A9FF026A29000004A9FF03460600000DFD11063132333435362F2F2F2F2F2F|
|
||||||
{"media":"electricity","meter":"ebzwmbe","name":"Elen1","id":"22992299","total_energy_consumption_kwh":2816.304,"current_power_consumption_kw":0.21679,"current_power_consumption_phase1_kw":0.09471,"current_power_consumption_phase2_kw":0.10602,"current_power_consumption_phase3_kw":0.01606,"customer":"123456","timestamp":"1111-11-11T11:11:11Z"}
|
{"media":"electricity","meter":"ebzwmbe","name":"Elen1","id":"22992299","total_energy_consumption_kwh":2816.304,"current_power_consumption_phase1_kw":0.09471,"current_power_consumption_phase2_kw":0.10602,"current_power_consumption_phase3_kw":0.01606,"customer":"654321","current_power_consumption_kw":0.21679,"timestamp":"1111-11-11T11:11:11Z"}
|
||||||
|Elen1;22992299;2816.304000;0.216790;0.094710;0.106020;0.016060;1111-11-11 11:11.11
|
|Elen1;22992299;2816.304;0.21679;0.09471;0.10602;0.01606;1111-11-11 11:11.11
|
||||||
|
|
||||||
# Test electricity meter with ESYS-WM20
|
# Test electricity meter with ESYS-WM20
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2020-2022 Fredrik Öhrström (gpl-3.0-or-later)
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include"meters_common_implementation.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
struct Driver : public virtual MeterCommonImplementation
|
||||||
|
{
|
||||||
|
Driver(MeterInfo &mi, DriverInfo &di);
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool ok = registerDriver([](DriverInfo&di)
|
||||||
|
{
|
||||||
|
di.setName("ebzwmbe");
|
||||||
|
di.setDefaultFields("name,id,total_energy_consumption_kwh,current_power_consumption_kw,current_power_consumption_phase1_kw,current_power_consumption_phase2_kw,current_power_consumption_phase3_kw,timestamp");
|
||||||
|
di.setMeterType(MeterType::ElectricityMeter);
|
||||||
|
di.addLinkMode(LinkMode::T1);
|
||||||
|
di.addDetection(MANUFACTURER_EBZ, 0x02, 0x01);
|
||||||
|
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
|
||||||
|
});
|
||||||
|
|
||||||
|
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||||
|
{
|
||||||
|
addNumericFieldWithExtractor(
|
||||||
|
"total_energy_consumption",
|
||||||
|
"The total energy consumption recorded by this meter.",
|
||||||
|
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||||
|
Quantity::Energy,
|
||||||
|
VifScaling::Auto,
|
||||||
|
FieldMatcher::build()
|
||||||
|
.set(MeasurementType::Instantaneous)
|
||||||
|
.set(VIFRange::EnergyWh)
|
||||||
|
);
|
||||||
|
|
||||||
|
addNumericFieldWithExtractor(
|
||||||
|
"current_power_consumption_phase1",
|
||||||
|
"Current power consumption at phase 1.",
|
||||||
|
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||||
|
Quantity::Power,
|
||||||
|
VifScaling::Auto,
|
||||||
|
FieldMatcher::build()
|
||||||
|
.set(DifVifKey("04A9FF01"))
|
||||||
|
);
|
||||||
|
|
||||||
|
addNumericFieldWithExtractor(
|
||||||
|
"current_power_consumption_phase2",
|
||||||
|
"Current power consumption at phase 2.",
|
||||||
|
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||||
|
Quantity::Power,
|
||||||
|
VifScaling::Auto,
|
||||||
|
FieldMatcher::build()
|
||||||
|
.set(DifVifKey("04A9FF02"))
|
||||||
|
);
|
||||||
|
|
||||||
|
addNumericFieldWithExtractor(
|
||||||
|
"current_power_consumption_phase3",
|
||||||
|
"Current power consumption at phase 3.",
|
||||||
|
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||||
|
Quantity::Power,
|
||||||
|
VifScaling::Auto,
|
||||||
|
FieldMatcher::build()
|
||||||
|
.set(DifVifKey("04A9FF03"))
|
||||||
|
);
|
||||||
|
|
||||||
|
addStringFieldWithExtractor(
|
||||||
|
"customer",
|
||||||
|
"Customer name.",
|
||||||
|
PrintProperty::JSON,
|
||||||
|
FieldMatcher::build()
|
||||||
|
.set(MeasurementType::Instantaneous)
|
||||||
|
.set(VIFRange::Customer)
|
||||||
|
);
|
||||||
|
|
||||||
|
addNumericFieldWithCalculator(
|
||||||
|
"current_power_consumption",
|
||||||
|
"Calculated sum of power consumption of all phases.",
|
||||||
|
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||||
|
Quantity::Power,
|
||||||
|
"current_power_consumption_phase1_kw + current_power_consumption_phase2_kw + current_power_consumption_phase3_kw"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Elen1 ebzwmbe 22992299 NOKEY
|
||||||
|
// telegram=|5B445A149922992202378C20F6900F002C25BC9E0000BF48954821BC508D72992299225A140102F6003007102F2F040330F92A0004A9FF01FF24000004A9FF026A29000004A9FF03460600000DFD11063132333435362F2F2F2F2F2F|
|
||||||
|
// {"media":"electricity","meter":"ebzwmbe","name":"Elen1","id":"22992299","total_energy_consumption_kwh":2816.304,"current_power_consumption_phase1_kw":0.09471,"current_power_consumption_phase2_kw":0.10602,"current_power_consumption_phase3_kw":0.01606,"customer":"654321","current_power_consumption_kw":0.21679,"timestamp":"1111-11-11T11:11:11Z"}
|
||||||
|
// |Elen1;22992299;2816.304;0.21679;0.09471;0.10602;0.01606;1111-11-11 11:11.11
|
|
@ -149,14 +149,14 @@ namespace
|
||||||
|
|
||||||
if (i+1 >= len) return;
|
if (i+1 >= len) return;
|
||||||
average_ambient_temperature_ = toTemperature(bytes[i+1], bytes[i]);
|
average_ambient_temperature_ = toTemperature(bytes[i+1], bytes[i]);
|
||||||
info = renderJsonOnlyDefaultUnit("average_ambient_temperature");
|
info = renderJsonOnlyDefaultUnit("average_ambient_temperature", Quantity::Temperature);
|
||||||
t->addSpecialExplanation(i+offset, 2, KindOfData::CONTENT, Understanding::FULL,
|
t->addSpecialExplanation(i+offset, 2, KindOfData::CONTENT, Understanding::FULL,
|
||||||
"*** %02X%02X (%s)", bytes[i], bytes[i+1], info.c_str());
|
"*** %02X%02X (%s)", bytes[i], bytes[i+1], info.c_str());
|
||||||
i+=2;
|
i+=2;
|
||||||
|
|
||||||
if (i+1 >= len) return;
|
if (i+1 >= len) return;
|
||||||
max_ambient_temperature_ = toTemperature(bytes[i+1], bytes[i]);
|
max_ambient_temperature_ = toTemperature(bytes[i+1], bytes[i]);
|
||||||
info = renderJsonOnlyDefaultUnit("max_ambient_temperature");
|
info = renderJsonOnlyDefaultUnit("max_ambient_temperature", Quantity::Temperature);
|
||||||
t->addSpecialExplanation(i+offset, 2, KindOfData::CONTENT, Understanding::FULL,
|
t->addSpecialExplanation(i+offset, 2, KindOfData::CONTENT, Understanding::FULL,
|
||||||
"*** %02X%02X (%s)", bytes[i], bytes[i+1], info.c_str());
|
"*** %02X%02X (%s)", bytes[i], bytes[i+1], info.c_str());
|
||||||
i+=2;
|
i+=2;
|
||||||
|
@ -175,14 +175,14 @@ namespace
|
||||||
|
|
||||||
if (i+1 >= len) return;
|
if (i+1 >= len) return;
|
||||||
average_ambient_temperature_last_month_ = toTemperature(bytes[i+1], bytes[i]);
|
average_ambient_temperature_last_month_ = toTemperature(bytes[i+1], bytes[i]);
|
||||||
info = renderJsonOnlyDefaultUnit("average_ambient_temperature_last_month");
|
info = renderJsonOnlyDefaultUnit("average_ambient_temperature_last_month", Quantity::Temperature);
|
||||||
t->addSpecialExplanation(i+offset, 2, KindOfData::CONTENT, Understanding::FULL,
|
t->addSpecialExplanation(i+offset, 2, KindOfData::CONTENT, Understanding::FULL,
|
||||||
"*** %02X%02X (%s)", bytes[i], bytes[i+1], info.c_str());
|
"*** %02X%02X (%s)", bytes[i], bytes[i+1], info.c_str());
|
||||||
i+=2;
|
i+=2;
|
||||||
|
|
||||||
if (i+1 >= len) return;
|
if (i+1 >= len) return;
|
||||||
average_heater_temperature_last_month_ = toTemperature(bytes[i+1], bytes[i]);
|
average_heater_temperature_last_month_ = toTemperature(bytes[i+1], bytes[i]);
|
||||||
info = renderJsonOnlyDefaultUnit("average_heater_temperature_last_month");
|
info = renderJsonOnlyDefaultUnit("average_heater_temperature_last_month", Quantity::Temperature);
|
||||||
t->addSpecialExplanation(i+offset, 2, KindOfData::CONTENT, Understanding::FULL,
|
t->addSpecialExplanation(i+offset, 2, KindOfData::CONTENT, Understanding::FULL,
|
||||||
"*** %02X%02X (%s)", bytes[i], bytes[i+1], info.c_str());
|
"*** %02X%02X (%s)", bytes[i], bytes[i+1], info.c_str());
|
||||||
i+=2;
|
i+=2;
|
||||||
|
|
|
@ -131,17 +131,17 @@ void MeterLansenDW::processContent(Telegram *t)
|
||||||
|
|
||||||
if (extractDVuint16(&t->dv_entries, "02FD1B", &offset, &info_codes_))
|
if (extractDVuint16(&t->dv_entries, "02FD1B", &offset, &info_codes_))
|
||||||
{
|
{
|
||||||
t->addMoreExplanation(offset, renderJsonOnlyDefaultUnit("status"));
|
t->addMoreExplanation(offset, renderJsonOnlyDefaultUnit("status", Quantity::Text));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extractDVdouble(&t->dv_entries, "0EFD3A", &offset, &pulse_counter_a_, false))
|
if (extractDVdouble(&t->dv_entries, "0EFD3A", &offset, &pulse_counter_a_, false))
|
||||||
{
|
{
|
||||||
t->addMoreExplanation(offset, renderJsonOnlyDefaultUnit("counter_a"));
|
t->addMoreExplanation(offset, renderJsonOnlyDefaultUnit("counter_a", Quantity::Counter));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extractDVdouble(&t->dv_entries, "8E40FD3A", &offset, &pulse_counter_b_, false))
|
if (extractDVdouble(&t->dv_entries, "8E40FD3A", &offset, &pulse_counter_b_, false))
|
||||||
{
|
{
|
||||||
t->addMoreExplanation(offset, renderJsonOnlyDefaultUnit("counter_b"));
|
t->addMoreExplanation(offset, renderJsonOnlyDefaultUnit("counter_b", Quantity::Counter));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -427,6 +427,8 @@ bool parseDV(Telegram *t,
|
||||||
if (variable_length) {
|
if (variable_length) {
|
||||||
DEBUG_PARSER("(dvparser debug) varlen %02x\n", *(data+0));
|
DEBUG_PARSER("(dvparser debug) varlen %02x\n", *(data+0));
|
||||||
datalen = *(data);
|
datalen = *(data);
|
||||||
|
t->addExplanationAndIncrementPos(data, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02X varlen=%d", *(data+0), datalen);
|
||||||
|
remaining--; // Drop the length byte.
|
||||||
}
|
}
|
||||||
DEBUG_PARSER("(dvparser debug) remaining data %d len=%d\n", remaining, datalen);
|
DEBUG_PARSER("(dvparser debug) remaining data %d len=%d\n", remaining, datalen);
|
||||||
if (remaining < datalen)
|
if (remaining < datalen)
|
||||||
|
@ -435,10 +437,6 @@ bool parseDV(Telegram *t,
|
||||||
datalen = remaining-1;
|
datalen = remaining-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip the length byte in the variable length data.
|
|
||||||
if (variable_length) {
|
|
||||||
t->addExplanationAndIncrementPos(data, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02X varlen=%d", *(data+0), datalen);
|
|
||||||
}
|
|
||||||
string value = bin2hex(data, data_end, datalen);
|
string value = bin2hex(data, data_end, datalen);
|
||||||
int offset = start_parse_here+data-data_start;
|
int offset = start_parse_here+data-data_start;
|
||||||
|
|
||||||
|
@ -454,10 +452,10 @@ bool parseDV(Telegram *t,
|
||||||
|
|
||||||
DVEntry *dve = &(*dv_entries)[key].second;
|
DVEntry *dve = &(*dv_entries)[key].second;
|
||||||
|
|
||||||
/*if (isDebugEnabled())
|
if (isTraceEnabled())
|
||||||
{
|
{
|
||||||
debug("(dvparser) entry %s\n", dve->str().c_str());
|
trace("[DVPARSER] entry %s\n", dve->str().c_str());
|
||||||
}*/
|
}
|
||||||
|
|
||||||
assert(key == dve->dif_vif_key.str());
|
assert(key == dve->dif_vif_key.str());
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@
|
||||||
X(ParameterSet,0x7D0B,0x7D0B, Quantity::Text, Unit::TXT) \
|
X(ParameterSet,0x7D0B,0x7D0B, Quantity::Text, Unit::TXT) \
|
||||||
X(ModelVersion,0x7D0C,0x7D0C, Quantity::Text, Unit::TXT) \
|
X(ModelVersion,0x7D0C,0x7D0C, Quantity::Text, Unit::TXT) \
|
||||||
X(SoftwareVersion,0x7D0F,0x7D0F, Quantity::Text, Unit::TXT) \
|
X(SoftwareVersion,0x7D0F,0x7D0F, Quantity::Text, Unit::TXT) \
|
||||||
|
X(Customer,0x7D11,0x7D11, Quantity::Text, Unit::TXT) \
|
||||||
X(ErrorFlags,0x7D17,0x7D17, Quantity::Text, Unit::TXT) \
|
X(ErrorFlags,0x7D17,0x7D17, Quantity::Text, Unit::TXT) \
|
||||||
X(DigitalInput,0x7D1B,0x7D1B, Quantity::Text, Unit::TXT) \
|
X(DigitalInput,0x7D1B,0x7D1B, Quantity::Text, Unit::TXT) \
|
||||||
X(DurationOfTariff,0x7D31,0x7D33, Quantity::Time, Unit::Hour) \
|
X(DurationOfTariff,0x7D31,0x7D33, Quantity::Time, Unit::Hour) \
|
||||||
|
|
|
@ -0,0 +1,562 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2022 Fredrik Öhrström (gpl-3.0-or-later)
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include"formula.h"
|
||||||
|
#include"formula_implementation.h"
|
||||||
|
#include"meters.h"
|
||||||
|
#include"units.h"
|
||||||
|
|
||||||
|
#include<cmath>
|
||||||
|
#include<string.h>
|
||||||
|
|
||||||
|
NumericFormula::~NumericFormula()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
NumericFormulaConstant::~NumericFormulaConstant()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
NumericFormulaField::~NumericFormulaField()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
NumericFormulaAddition::~NumericFormulaAddition()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
double NumericFormulaConstant::calculate(Unit to)
|
||||||
|
{
|
||||||
|
return convert(constant_, unit(), to);
|
||||||
|
}
|
||||||
|
|
||||||
|
double NumericFormulaField::calculate(Unit to)
|
||||||
|
{
|
||||||
|
return meter_->getNumericValue(field_info_, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
double NumericFormulaAddition::calculate(Unit to)
|
||||||
|
{
|
||||||
|
double sum = 0;
|
||||||
|
|
||||||
|
sum += left_->calculate(to);
|
||||||
|
sum += right_->calculate(to);
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *toString(TokenType tt)
|
||||||
|
{
|
||||||
|
switch (tt) {
|
||||||
|
case TokenType::SPACE: return "SPACE";
|
||||||
|
case TokenType::NUMBER: return "NUMBER";
|
||||||
|
case TokenType::LPAR: return "LPAR";
|
||||||
|
case TokenType::RPAR: return "RPAR";
|
||||||
|
case TokenType::PLUS: return "PLUS";
|
||||||
|
case TokenType::UNIT: return "UNIT";
|
||||||
|
case TokenType::FIELD: return "FIELD";
|
||||||
|
}
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
string Token::str(const string &s)
|
||||||
|
{
|
||||||
|
string v = s.substr(start, len);
|
||||||
|
return tostrprintf("%s(%s)", toString(type), v.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
double Token::val(const string &s)
|
||||||
|
{
|
||||||
|
string v = s.substr(start, len);
|
||||||
|
return atof(v.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
string Token::vals(const string &s)
|
||||||
|
{
|
||||||
|
return s.substr(start, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
Unit Token::unit(const string &s)
|
||||||
|
{
|
||||||
|
string v = s.substr(start, len);
|
||||||
|
return toUnit(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FormulaImplementation::clear()
|
||||||
|
{
|
||||||
|
valid_ = true;
|
||||||
|
while (!ops_.empty()) ops_.pop();
|
||||||
|
tokens_.clear();
|
||||||
|
formula_ = "";
|
||||||
|
meter_ = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t FormulaImplementation::findSpace(size_t i)
|
||||||
|
{
|
||||||
|
size_t len = 0;
|
||||||
|
while(isspace(formula_[i]))
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t FormulaImplementation::findNumber(size_t i)
|
||||||
|
{
|
||||||
|
size_t len = 0;
|
||||||
|
size_t start = i;
|
||||||
|
int num_dots = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (i >= formula_.length()) break;
|
||||||
|
char c = formula_[i];
|
||||||
|
if ((c < '0' || c > '9') && c != '.') break;
|
||||||
|
if (c == '.')
|
||||||
|
{
|
||||||
|
if (start == i) return 0; // Numbers do not start with a dot.
|
||||||
|
num_dots++;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num_dots > 1) return 0; // More than one decimal dot is an error.
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t FormulaImplementation::findPlus(size_t i)
|
||||||
|
{
|
||||||
|
if (i >= formula_.length()) return 0;
|
||||||
|
|
||||||
|
char c = formula_[i];
|
||||||
|
if (c == '+') return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t FormulaImplementation::findLPar(size_t i)
|
||||||
|
{
|
||||||
|
if (i >= formula_.length()) return 0;
|
||||||
|
|
||||||
|
char c = formula_[i];
|
||||||
|
if (c == '(') return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t FormulaImplementation::findRPar(size_t i)
|
||||||
|
{
|
||||||
|
if (i >= formula_.length()) return 0;
|
||||||
|
|
||||||
|
char c = formula_[i];
|
||||||
|
if (c == ')') return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_letter(char c)
|
||||||
|
{
|
||||||
|
return c >= 'a' && c <= 'z';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_letter_or_underscore(char c)
|
||||||
|
{
|
||||||
|
return c == '_' || (c >= 'a' && c <= 'z');
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_letter_digit_or_underscore(char c)
|
||||||
|
{
|
||||||
|
return c == '_' || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t FormulaImplementation::findUnit(size_t i)
|
||||||
|
{
|
||||||
|
if (i >= formula_.length()) return 0;
|
||||||
|
|
||||||
|
size_t len = formula_.length();
|
||||||
|
char c = formula_[i];
|
||||||
|
|
||||||
|
// All units start with a lower case a-z, followed by more letters and _ underscores.
|
||||||
|
if (!is_letter(c)) return 0;
|
||||||
|
|
||||||
|
#define X(cname,lcname,hrname,quantity,explanation) \
|
||||||
|
if ( (i+sizeof(#lcname)-1 <= len) && \
|
||||||
|
!is_letter_or_underscore(formula_[i+sizeof(#lcname)-1]) && \
|
||||||
|
!strncmp(#lcname, formula_.c_str()+i, sizeof(#lcname)-1)) return sizeof(#lcname)-1;
|
||||||
|
LIST_OF_UNITS
|
||||||
|
#undef X
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t FormulaImplementation::findField(size_t i)
|
||||||
|
{
|
||||||
|
size_t len = 0;
|
||||||
|
size_t start = i;
|
||||||
|
|
||||||
|
// All field names are lower case a-z.
|
||||||
|
if (!is_letter(formula_[start])) return 0;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (i >= formula_.length()) break;
|
||||||
|
char c = formula_[i];
|
||||||
|
// After first letter field names can contain more letters digits and underscores.
|
||||||
|
if (!is_letter_digit_or_underscore(c)) break;
|
||||||
|
i++;
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FormulaImplementation::tokenize()
|
||||||
|
{
|
||||||
|
size_t i = 0;
|
||||||
|
|
||||||
|
while (i<formula_.length())
|
||||||
|
{
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
len = findSpace(i);
|
||||||
|
if (len > 0) { i+=len; continue; } // No token added for whitespace.
|
||||||
|
|
||||||
|
len = findNumber(i);
|
||||||
|
if (len > 0) { tokens_.push_back(Token(TokenType::NUMBER, i, len)); i+=len; continue; }
|
||||||
|
|
||||||
|
len = findLPar(i);
|
||||||
|
if (len > 0) { tokens_.push_back(Token(TokenType::LPAR, i, len)); i+=len; continue; }
|
||||||
|
|
||||||
|
len = findRPar(i);
|
||||||
|
if (len > 0) { tokens_.push_back(Token(TokenType::RPAR, i, len)); i+=len; continue; }
|
||||||
|
|
||||||
|
len = findPlus(i);
|
||||||
|
if (len > 0) { tokens_.push_back(Token(TokenType::PLUS, i, len)); i+=len; continue; }
|
||||||
|
|
||||||
|
len = findUnit(i);
|
||||||
|
if (len > 0) { tokens_.push_back(Token(TokenType::UNIT, i, len)); i+=len; continue; }
|
||||||
|
|
||||||
|
len = findField(i);
|
||||||
|
if (len > 0) { tokens_.push_back(Token(TokenType::FIELD, i, len)); i+=len; continue; }
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interrupted early, thus there was an error tokenizing.
|
||||||
|
if (i < formula_.length()) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t FormulaImplementation::parseOps(size_t i)
|
||||||
|
{
|
||||||
|
Token *tok = LA(i);
|
||||||
|
Token *next= LA(i+1);
|
||||||
|
if (tok == NULL) return i;
|
||||||
|
|
||||||
|
if (tok->type == TokenType::FIELD)
|
||||||
|
{
|
||||||
|
handleField(tok);
|
||||||
|
return i+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tok->type == TokenType::PLUS)
|
||||||
|
{
|
||||||
|
size_t next = parseOps(i+1);
|
||||||
|
handleAddition();
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tok->type == TokenType::LPAR)
|
||||||
|
{
|
||||||
|
return parsePar(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tok->type == TokenType::RPAR)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next == NULL) return i;
|
||||||
|
|
||||||
|
if (tok->type == TokenType::NUMBER && next->type == TokenType::UNIT)
|
||||||
|
{
|
||||||
|
handleConstant(tok, next);
|
||||||
|
return i+2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t FormulaImplementation::parsePar(size_t i)
|
||||||
|
{
|
||||||
|
Token *tok = LA(i);
|
||||||
|
assert(tok->type == TokenType::LPAR);
|
||||||
|
|
||||||
|
i++;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
tok = LA(i);
|
||||||
|
if (tok == NULL) break;
|
||||||
|
if (tok->type == TokenType::RPAR) break;
|
||||||
|
size_t next = parseOps(i);
|
||||||
|
if (next == i) break;
|
||||||
|
i = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tok == NULL)
|
||||||
|
{
|
||||||
|
warning("No closing parenthesis found!\n");
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tok->type != TokenType::RPAR)
|
||||||
|
{
|
||||||
|
warning("Expected parenthesis but got xx intead.\n");
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return i+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FormulaImplementation::handleConstant(Token *number, Token *unit)
|
||||||
|
{
|
||||||
|
double c = number->val(formula_);
|
||||||
|
Unit u = unit->unit(formula_);
|
||||||
|
|
||||||
|
if (u == Unit::Unknown)
|
||||||
|
{
|
||||||
|
warning("Unknown unit \"%s\" in formula:\n%s\n", unit->vals(formula_).c_str(), formula_.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//debug("(formula) push constant %f %s\n", c, unitToStringLowerCase(u).c_str());
|
||||||
|
doConstant(u, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FormulaImplementation::handleAddition()
|
||||||
|
{
|
||||||
|
//debug("(formula) push addition\n");
|
||||||
|
doAddition();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FormulaImplementation::handleField(Token *field)
|
||||||
|
{
|
||||||
|
string field_name = field->vals(formula_); // Full field: total_m3
|
||||||
|
//debug("(formula) push field %s\n", field_name.c_str());
|
||||||
|
string vname; // Without unit: total
|
||||||
|
Unit unit;
|
||||||
|
bool ok = extractUnit(field_name, &vname, &unit);
|
||||||
|
|
||||||
|
debug("(formula) handle field %s into %s %s\n", field_name.c_str(), vname.c_str(), unitToStringLowerCase(unit).c_str());
|
||||||
|
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
warning("Could not extract a valid unit from field name \"%s\"\n", field_name.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Quantity q = toQuantity(unit);
|
||||||
|
FieldInfo *f = meter_->findFieldInfo(vname, q);
|
||||||
|
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
warning("No such field found \"%s\" (%s %s)\n", field_name.c_str(), vname.c_str(), toString(q));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = doField(unit, meter_, f);
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
warning("Could not use field \"%s\" (%s %s)\n", field_name.c_str(), vname.c_str(), toString(q));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FormulaImplementation::go()
|
||||||
|
{
|
||||||
|
size_t i = 0;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
size_t next = parseOps(i);
|
||||||
|
if (next == i) break;
|
||||||
|
i = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return i == tokens_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
Token *FormulaImplementation::LA(size_t i)
|
||||||
|
{
|
||||||
|
if (i < 0 || i >= tokens_.size()) return NULL;
|
||||||
|
return &tokens_[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FormulaImplementation::parse(Meter *m, const string &f)
|
||||||
|
{
|
||||||
|
bool ok;
|
||||||
|
|
||||||
|
meter_ = m;
|
||||||
|
formula_ = f;
|
||||||
|
|
||||||
|
debug("(formula) parsing \"%s\"\n", formula_.c_str());
|
||||||
|
|
||||||
|
ok = tokenize();
|
||||||
|
if (!ok) return false;
|
||||||
|
|
||||||
|
if (isDebugEnabled())
|
||||||
|
{
|
||||||
|
debug("(formula) tokens: ");
|
||||||
|
for (Token &t : tokens_)
|
||||||
|
{
|
||||||
|
debug("%s ", t.str(formula_).c_str());
|
||||||
|
}
|
||||||
|
debug("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = go();
|
||||||
|
if (!ok) return false;
|
||||||
|
|
||||||
|
if (isDebugEnabled())
|
||||||
|
{
|
||||||
|
debug("(formula) %s\n", tree().c_str());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FormulaImplementation::valid()
|
||||||
|
{
|
||||||
|
return valid_ == true && ops_.size() == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
double FormulaImplementation::calculate(Unit to)
|
||||||
|
{
|
||||||
|
if (!valid_)
|
||||||
|
{
|
||||||
|
warning("(formula) not valid returning nan!\n");
|
||||||
|
return std::nan("");
|
||||||
|
}
|
||||||
|
if (ops_.size() != 1)
|
||||||
|
{
|
||||||
|
warning("(formula) does not have a single op (has %zu), not valid returning nan!\n", ops_.size());
|
||||||
|
return std::nan("");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ops_.top().get()->calculate(to);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FormulaImplementation::doConstant(Unit u, double c)
|
||||||
|
{
|
||||||
|
ops_.push(unique_ptr<NumericFormula>(new NumericFormulaConstant(u, c)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FormulaImplementation::doAddition()
|
||||||
|
{
|
||||||
|
if (ops_.size() < 2) { valid_ = false; return false;}
|
||||||
|
|
||||||
|
Unit right_unit = ops_.top()->unit();
|
||||||
|
|
||||||
|
unique_ptr<NumericFormula> right_node = std::move(ops_.top());
|
||||||
|
ops_.pop();
|
||||||
|
|
||||||
|
Unit left_unit = ops_.top()->unit();
|
||||||
|
|
||||||
|
unique_ptr<NumericFormula> left_node = std::move(ops_.top());
|
||||||
|
ops_.pop();
|
||||||
|
|
||||||
|
ops_.push(unique_ptr<NumericFormula>(new NumericFormulaAddition(left_unit, left_node, right_node)));
|
||||||
|
|
||||||
|
if (!canConvert(left_unit, right_unit))
|
||||||
|
{
|
||||||
|
valid_ = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FormulaImplementation::doField(Unit u, Meter *m, FieldInfo *fi)
|
||||||
|
{
|
||||||
|
if (!canConvert(u, fi->defaultUnit()))
|
||||||
|
{
|
||||||
|
valid_ = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ops_.push(unique_ptr<NumericFormula>(new NumericFormulaField(u, m, fi)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Formula *newFormula()
|
||||||
|
{
|
||||||
|
return new FormulaImplementation();
|
||||||
|
}
|
||||||
|
|
||||||
|
Formula::~Formula()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FormulaImplementation::~FormulaImplementation()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
string FormulaImplementation::str()
|
||||||
|
{
|
||||||
|
return ops_.top()->str();
|
||||||
|
}
|
||||||
|
|
||||||
|
string FormulaImplementation::tree()
|
||||||
|
{
|
||||||
|
string t = ops_.top()->tree();
|
||||||
|
if (t.back() == ' ') t.pop_back();
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
string NumericFormulaConstant::str()
|
||||||
|
{
|
||||||
|
return tostrprintf("%.15g %s", constant_, unit());
|
||||||
|
}
|
||||||
|
|
||||||
|
string NumericFormulaConstant::tree()
|
||||||
|
{
|
||||||
|
return tostrprintf("<CONST %.15g %s> ", constant_, unitToStringLowerCase(unit()).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
string NumericFormulaAddition::str()
|
||||||
|
{
|
||||||
|
string left = left_->tree();
|
||||||
|
string right = right_->tree();
|
||||||
|
return left+" + "+right;
|
||||||
|
}
|
||||||
|
|
||||||
|
string NumericFormulaAddition::tree()
|
||||||
|
{
|
||||||
|
string left = left_->tree();
|
||||||
|
string right = right_->tree();
|
||||||
|
return "<ADD "+left+right+"> ";
|
||||||
|
}
|
||||||
|
|
||||||
|
string NumericFormulaField::str()
|
||||||
|
{
|
||||||
|
return field_info_->vname()+"_"+unitToStringLowerCase(field_info_->defaultUnit());
|
||||||
|
}
|
||||||
|
|
||||||
|
string NumericFormulaField::tree()
|
||||||
|
{
|
||||||
|
return "<FIELD "+field_info_->vname()+"_"+unitToStringLowerCase(field_info_->defaultUnit())+"> ";
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2022 Fredrik Öhrström (gpl-3.0-or-later)
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FORMULA_H
|
||||||
|
#define FORMULA_H
|
||||||
|
|
||||||
|
#include<memory>
|
||||||
|
#include<vector>
|
||||||
|
|
||||||
|
#include"units.h"
|
||||||
|
|
||||||
|
struct Meter;
|
||||||
|
struct FieldInfo;
|
||||||
|
|
||||||
|
struct Formula
|
||||||
|
{
|
||||||
|
virtual bool parse(Meter *m, const std::string &f) = 0;
|
||||||
|
virtual bool valid() = 0;
|
||||||
|
virtual double calculate(Unit to) = 0;
|
||||||
|
virtual void clear() = 0;
|
||||||
|
// Return a regenerated formula string.
|
||||||
|
virtual std::string str() = 0;
|
||||||
|
// Return the formula in a format where the tree structure is explicit.
|
||||||
|
virtual std::string tree() = 0;
|
||||||
|
|
||||||
|
virtual ~Formula() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Formula *newFormula();
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,160 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2022 Fredrik Öhrström (gpl-3.0-or-later)
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FORMULA_IMPLEMENTATION
|
||||||
|
#define FORMULA_IMPLEMENTATION
|
||||||
|
|
||||||
|
#include"formula.h"
|
||||||
|
#include"meters.h"
|
||||||
|
|
||||||
|
#include<stack>
|
||||||
|
|
||||||
|
struct NumericFormula
|
||||||
|
{
|
||||||
|
NumericFormula(Unit u) : unit_(u) { }
|
||||||
|
Unit unit() { return unit_; }
|
||||||
|
virtual double calculate(Unit to) = 0;
|
||||||
|
virtual string str() = 0;
|
||||||
|
virtual string tree() = 0;
|
||||||
|
virtual ~NumericFormula() = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Unit unit_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NumericFormulaConstant : public NumericFormula
|
||||||
|
{
|
||||||
|
NumericFormulaConstant(Unit u, double c) : NumericFormula(u), constant_(c) {}
|
||||||
|
double calculate(Unit to);
|
||||||
|
string str();
|
||||||
|
string tree();
|
||||||
|
~NumericFormulaConstant();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
double constant_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NumericFormulaField : public NumericFormula
|
||||||
|
{
|
||||||
|
NumericFormulaField(Unit u, Meter *m, FieldInfo *fi) : NumericFormula(u), meter_(m), field_info_(fi) {}
|
||||||
|
double calculate(Unit to);
|
||||||
|
string str();
|
||||||
|
string tree();
|
||||||
|
~NumericFormulaField();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Meter *meter_;
|
||||||
|
FieldInfo *field_info_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NumericFormulaAddition : public NumericFormula
|
||||||
|
{
|
||||||
|
NumericFormulaAddition(Unit u,
|
||||||
|
unique_ptr<NumericFormula> &a,
|
||||||
|
unique_ptr<NumericFormula> &b)
|
||||||
|
: NumericFormula(u), left_(std::move(a)), right_(std::move(b)) {}
|
||||||
|
|
||||||
|
double calculate(Unit to);
|
||||||
|
string str();
|
||||||
|
string tree();
|
||||||
|
|
||||||
|
~NumericFormulaAddition();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::unique_ptr<NumericFormula> left_;
|
||||||
|
std::unique_ptr<NumericFormula> right_;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class TokenType
|
||||||
|
{
|
||||||
|
SPACE,
|
||||||
|
LPAR,
|
||||||
|
RPAR,
|
||||||
|
NUMBER,
|
||||||
|
PLUS,
|
||||||
|
UNIT,
|
||||||
|
FIELD
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *toString(TokenType tt);
|
||||||
|
|
||||||
|
struct Token
|
||||||
|
{
|
||||||
|
Token(TokenType t, size_t s, size_t l) : type(t), start(s), len(l) {}
|
||||||
|
|
||||||
|
TokenType type;
|
||||||
|
size_t start;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
string str(const string &s);
|
||||||
|
string vals(const string &s);
|
||||||
|
double val(const string &s);
|
||||||
|
Unit unit(const string &s);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FormulaImplementation : public Formula
|
||||||
|
{
|
||||||
|
bool parse(Meter *m, const string &f);
|
||||||
|
bool valid();
|
||||||
|
double calculate(Unit to);
|
||||||
|
void clear();
|
||||||
|
string str();
|
||||||
|
string tree();
|
||||||
|
|
||||||
|
// Pushes a constant on the stack.
|
||||||
|
bool doConstant(Unit u, double c);
|
||||||
|
// Pushes a field read on the stack. Returns false if the field is not found in the meter.
|
||||||
|
bool doField(Unit u, Meter *m, FieldInfo *fi);
|
||||||
|
// Pops the two top nodes of the stack and pushes an addition (using these members) on the stack.
|
||||||
|
// The target unit will be the first unit of the two operands. If incompatible units, then it will
|
||||||
|
// return false.
|
||||||
|
bool doAddition();
|
||||||
|
|
||||||
|
~FormulaImplementation();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool tokenize();
|
||||||
|
bool go();
|
||||||
|
size_t findSpace(size_t i);
|
||||||
|
size_t findNumber(size_t i);
|
||||||
|
size_t findUnit(size_t i);
|
||||||
|
size_t findPlus(size_t i);
|
||||||
|
size_t findLPar(size_t i);
|
||||||
|
size_t findRPar(size_t i);
|
||||||
|
size_t findField(size_t i);
|
||||||
|
|
||||||
|
Token *LA(size_t i);
|
||||||
|
size_t parseOps(size_t i);
|
||||||
|
size_t parsePar(size_t i);
|
||||||
|
|
||||||
|
void handleConstant(Token *number, Token *unit);
|
||||||
|
void handleAddition();
|
||||||
|
void handleField(Token *field);
|
||||||
|
|
||||||
|
bool valid_ = true;
|
||||||
|
std::stack<std::unique_ptr<NumericFormula>> ops_;
|
||||||
|
std::vector<Token> tokens_;
|
||||||
|
std::string formula_; // To be parsed.
|
||||||
|
Meter *meter_; // To be referenced when parsing.
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -28,7 +28,6 @@
|
||||||
//
|
//
|
||||||
#define METER_DETECTION \
|
#define METER_DETECTION \
|
||||||
X(CCx01, MANUFACTURER_GSS, 0x02, 0x01) \
|
X(CCx01, MANUFACTURER_GSS, 0x02, 0x01) \
|
||||||
X(EBZWMBE, MANUFACTURER_EBZ, 0x37, 0x02) \
|
|
||||||
X(EURISII, MANUFACTURER_INE, 0x08, 0x55) \
|
X(EURISII, MANUFACTURER_INE, 0x08, 0x55) \
|
||||||
X(EHZP, MANUFACTURER_EMH, 0x02, 0x02) \
|
X(EHZP, MANUFACTURER_EMH, 0x02, 0x02) \
|
||||||
X(ESYSWM, MANUFACTURER_ESY, 0x37, 0x30) \
|
X(ESYSWM, MANUFACTURER_ESY, 0x37, 0x30) \
|
||||||
|
|
|
@ -1,180 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2020 Fredrik Öhrström (gpl-3.0-or-later)
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include"dvparser.h"
|
|
||||||
#include"meters.h"
|
|
||||||
#include"meters_common_implementation.h"
|
|
||||||
#include"wmbus.h"
|
|
||||||
#include"wmbus_utils.h"
|
|
||||||
#include"util.h"
|
|
||||||
|
|
||||||
struct MeterEBZWMBE : public virtual MeterCommonImplementation
|
|
||||||
{
|
|
||||||
MeterEBZWMBE(MeterInfo &mi);
|
|
||||||
|
|
||||||
double totalEnergyConsumption(Unit u);
|
|
||||||
double currentPowerConsumption(Unit u);
|
|
||||||
double currentPowerConsumptionPhase1(Unit u);
|
|
||||||
double currentPowerConsumptionPhase2(Unit u);
|
|
||||||
double currentPowerConsumptionPhase3(Unit u);
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
void processContent(Telegram *t);
|
|
||||||
|
|
||||||
double total_energy_kwh_ {};
|
|
||||||
double current_power_kw_ {};
|
|
||||||
double current_power_phase1_kw_ {};
|
|
||||||
double current_power_phase2_kw_ {};
|
|
||||||
double current_power_phase3_kw_ {};
|
|
||||||
string customer_;
|
|
||||||
};
|
|
||||||
|
|
||||||
MeterEBZWMBE::MeterEBZWMBE(MeterInfo &mi) :
|
|
||||||
MeterCommonImplementation(mi, "ebzwmbe")
|
|
||||||
{
|
|
||||||
setMeterType(MeterType::ElectricityMeter);
|
|
||||||
|
|
||||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_NO_IV);
|
|
||||||
|
|
||||||
// The eBZ wWMB E01 is an addons to the electricity meters
|
|
||||||
// media 0x37 Radio converter (meter side)
|
|
||||||
|
|
||||||
addLinkMode(LinkMode::T1);
|
|
||||||
|
|
||||||
addPrint("total_energy_consumption", Quantity::Energy,
|
|
||||||
[&](Unit u){ return totalEnergyConsumption(u); },
|
|
||||||
"The total energy consumption recorded by this meter.",
|
|
||||||
PrintProperty::FIELD | PrintProperty::JSON);
|
|
||||||
|
|
||||||
addPrint("current_power_consumption", Quantity::Power,
|
|
||||||
[&](Unit u){ return currentPowerConsumption(u); },
|
|
||||||
"Current power consumption.",
|
|
||||||
PrintProperty::FIELD | PrintProperty::JSON);
|
|
||||||
|
|
||||||
addPrint("current_power_consumption_phase1", Quantity::Power,
|
|
||||||
[&](Unit u){ return currentPowerConsumptionPhase1(u); },
|
|
||||||
"Current power consumption phase 1.",
|
|
||||||
PrintProperty::FIELD | PrintProperty::JSON);
|
|
||||||
|
|
||||||
addPrint("current_power_consumption_phase2", Quantity::Power,
|
|
||||||
[&](Unit u){ return currentPowerConsumptionPhase2(u); },
|
|
||||||
"Current power consumption phase 2.",
|
|
||||||
PrintProperty::FIELD | PrintProperty::JSON);
|
|
||||||
|
|
||||||
addPrint("current_power_consumption_phase3", Quantity::Power,
|
|
||||||
[&](Unit u){ return currentPowerConsumptionPhase3(u); },
|
|
||||||
"Current power consumption phase 3.",
|
|
||||||
PrintProperty::FIELD | PrintProperty::JSON);
|
|
||||||
|
|
||||||
addPrint("customer", Quantity::Text,
|
|
||||||
[&](){ return customer_; },
|
|
||||||
"Customer name.",
|
|
||||||
PrintProperty::JSON);
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_ptr<Meter> createEBZWMBE(MeterInfo &mi)
|
|
||||||
{
|
|
||||||
return shared_ptr<Meter>(new MeterEBZWMBE(mi));
|
|
||||||
}
|
|
||||||
|
|
||||||
double MeterEBZWMBE::totalEnergyConsumption(Unit u)
|
|
||||||
{
|
|
||||||
assertQuantity(u, Quantity::Energy);
|
|
||||||
return convert(total_energy_kwh_, Unit::KWH, u);
|
|
||||||
}
|
|
||||||
|
|
||||||
double MeterEBZWMBE::currentPowerConsumption(Unit u)
|
|
||||||
{
|
|
||||||
assertQuantity(u, Quantity::Power);
|
|
||||||
return convert(current_power_kw_, Unit::KW, u);
|
|
||||||
}
|
|
||||||
|
|
||||||
double MeterEBZWMBE::currentPowerConsumptionPhase1(Unit u)
|
|
||||||
{
|
|
||||||
assertQuantity(u, Quantity::Power);
|
|
||||||
return convert(current_power_phase1_kw_, Unit::KW, u);
|
|
||||||
}
|
|
||||||
|
|
||||||
double MeterEBZWMBE::currentPowerConsumptionPhase2(Unit u)
|
|
||||||
{
|
|
||||||
assertQuantity(u, Quantity::Power);
|
|
||||||
return convert(current_power_phase2_kw_, Unit::KW, u);
|
|
||||||
}
|
|
||||||
|
|
||||||
double MeterEBZWMBE::currentPowerConsumptionPhase3(Unit u)
|
|
||||||
{
|
|
||||||
assertQuantity(u, Quantity::Power);
|
|
||||||
return convert(current_power_phase3_kw_, Unit::KW, u);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MeterEBZWMBE::processContent(Telegram *t)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
(ebzwmbe) 2e: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
|
||||||
(ebzwmbe) 2f: 03 vif (Energy Wh)
|
|
||||||
(ebzwmbe) 30: * 30F92A00 total energy (2816.304000 kwh)
|
|
||||||
(ebzwmbe) 34: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
|
||||||
(ebzwmbe) 35: A9 vif (Power 10⁻² W)
|
|
||||||
(ebzwmbe) 36: FF vife (additive correction constant: unit of VIF * 10^0)
|
|
||||||
(ebzwmbe) 37: 01 vife (?)
|
|
||||||
(ebzwmbe) 38: * FF240000 current power phase 1 (0.094710 kwh)
|
|
||||||
(ebzwmbe) 3c: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
|
||||||
(ebzwmbe) 3d: A9 vif (Power 10⁻² W)
|
|
||||||
(ebzwmbe) 3e: FF vife (additive correction constant: unit of VIF * 10^0)
|
|
||||||
(ebzwmbe) 3f: 02 vife (?)
|
|
||||||
(ebzwmbe) 40: * 6A290000 current power phase 2 (0.000000 kwh)
|
|
||||||
(ebzwmbe) 44: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
|
||||||
(ebzwmbe) 45: A9 vif (Power 10⁻² W)
|
|
||||||
(ebzwmbe) 46: FF vife (additive correction constant: unit of VIF * 10^0)
|
|
||||||
(ebzwmbe) 47: 03 vife (?)
|
|
||||||
(ebzwmbe) 48: * 46060000 current power phase 3 (0.000000 kwh)
|
|
||||||
(ebzwmbe) 4c: 0D dif (variable length Instantaneous value)
|
|
||||||
(ebzwmbe) 4d: FD vif (Second extension of VIF-codes)
|
|
||||||
(ebzwmbe) 4e: 11 vife (Customer)
|
|
||||||
(ebzwmbe) 4f: 06 varlen=6
|
|
||||||
(ebzwmbe) 50: * 313233343536 customer (123456)
|
|
||||||
*/
|
|
||||||
int offset;
|
|
||||||
string key;
|
|
||||||
|
|
||||||
if (findKey(MeasurementType::Instantaneous, VIFRange::EnergyWh, 0, 0, &key, &t->dv_entries)) {
|
|
||||||
extractDVdouble(&t->dv_entries, key, &offset, &total_energy_kwh_);
|
|
||||||
t->addMoreExplanation(offset, " total energy (%f kwh)", total_energy_kwh_);
|
|
||||||
}
|
|
||||||
|
|
||||||
extractDVdouble(&t->dv_entries, "04A9FF01", &offset, ¤t_power_phase1_kw_);
|
|
||||||
t->addMoreExplanation(offset, " current power phase 1 (%f kwh)", current_power_phase1_kw_);
|
|
||||||
|
|
||||||
extractDVdouble(&t->dv_entries, "04A9FF02", &offset, ¤t_power_phase2_kw_);
|
|
||||||
t->addMoreExplanation(offset, " current power phase 2 (%f kwh)", current_power_phase2_kw_);
|
|
||||||
|
|
||||||
extractDVdouble(&t->dv_entries, "04A9FF03", &offset, ¤t_power_phase3_kw_);
|
|
||||||
t->addMoreExplanation(offset, " current power phase 3 (%f kwh)", current_power_phase3_kw_);
|
|
||||||
|
|
||||||
current_power_kw_ = current_power_phase1_kw_ + current_power_phase2_kw_ + current_power_phase3_kw_;
|
|
||||||
t->addMoreExplanation(offset, " current power (%f kw)", current_power_kw_);
|
|
||||||
|
|
||||||
string tmp;
|
|
||||||
extractDVHexString(&t->dv_entries, "0DFD11", &offset, &tmp);
|
|
||||||
if (tmp.length() > 0) {
|
|
||||||
vector<uchar> bin;
|
|
||||||
hex2bin(tmp, &bin);
|
|
||||||
customer_ = safeString(bin);
|
|
||||||
}
|
|
||||||
t->addMoreExplanation(offset, " customer (%s)", customer_.c_str());
|
|
||||||
}
|
|
110
src/meters.cc
110
src/meters.cc
|
@ -846,7 +846,8 @@ void MeterCommonImplementation::addPrint(string vname, Quantity vquantity,
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
NoLookup
|
NoLookup, /* Lookup table */
|
||||||
|
NULL /* Formula */
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -866,7 +867,8 @@ void MeterCommonImplementation::addPrint(string vname, Quantity vquantity, Unit
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
NoLookup
|
NoLookup, /* Lookup table */
|
||||||
|
NULL /* Formula */
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -887,7 +889,8 @@ void MeterCommonImplementation::addPrint(string vname, Quantity vquantity,
|
||||||
getValueFunc,
|
getValueFunc,
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
NoLookup
|
NoLookup, /* Lookup table */
|
||||||
|
NULL /* Formula */
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -919,7 +922,8 @@ void MeterCommonImplementation::addNumericFieldWithExtractor(
|
||||||
NULL,
|
NULL,
|
||||||
setValueFunc,
|
setValueFunc,
|
||||||
NULL,
|
NULL,
|
||||||
NoLookup
|
NoLookup, /* Lookup table */
|
||||||
|
NULL /* Formula */
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -944,7 +948,37 @@ void MeterCommonImplementation::addNumericFieldWithExtractor(string vname,
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
NoLookup
|
NoLookup, /* Lookup table */
|
||||||
|
NULL /* Formula */
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeterCommonImplementation::addNumericFieldWithCalculator(string vname,
|
||||||
|
string help,
|
||||||
|
PrintProperties print_properties,
|
||||||
|
Quantity vquantity,
|
||||||
|
string formula,
|
||||||
|
Unit use_unit)
|
||||||
|
{
|
||||||
|
Formula *f = newFormula();
|
||||||
|
bool ok = f->parse(this, formula);
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
field_infos_.push_back(
|
||||||
|
FieldInfo(field_infos_.size(),
|
||||||
|
vname,
|
||||||
|
vquantity,
|
||||||
|
use_unit == Unit::Unknown ? defaultUnitForQuantity(vquantity) : use_unit,
|
||||||
|
VifScaling::Auto,
|
||||||
|
NULL,
|
||||||
|
help,
|
||||||
|
print_properties,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NoLookup, /* Lookup table */
|
||||||
|
f /* Formula */
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -969,7 +1003,8 @@ void MeterCommonImplementation::addNumericField(
|
||||||
NULL,
|
NULL,
|
||||||
setValueFunc,
|
setValueFunc,
|
||||||
NULL,
|
NULL,
|
||||||
NoLookup
|
NoLookup, /* Lookup table */
|
||||||
|
NULL /* Formula */
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1000,7 +1035,8 @@ void MeterCommonImplementation::addStringFieldWithExtractor(
|
||||||
getValueFunc,
|
getValueFunc,
|
||||||
NULL,
|
NULL,
|
||||||
setValueFunc,
|
setValueFunc,
|
||||||
NoLookup
|
NoLookup, /* Lookup table */
|
||||||
|
NULL /* Formula */
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1022,7 +1058,8 @@ void MeterCommonImplementation::addStringFieldWithExtractor(string vname,
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
NoLookup
|
NoLookup, /* Lookup table */
|
||||||
|
NULL /* Formula */
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1054,7 +1091,8 @@ void MeterCommonImplementation::addStringFieldWithExtractorAndLookup(
|
||||||
getValueFunc,
|
getValueFunc,
|
||||||
NULL,
|
NULL,
|
||||||
setValueFunc,
|
setValueFunc,
|
||||||
lookup
|
lookup, /* Lookup table */
|
||||||
|
NULL /* Formula */
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1077,7 +1115,8 @@ void MeterCommonImplementation::addStringFieldWithExtractorAndLookup(string vnam
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
lookup
|
lookup,
|
||||||
|
NULL /* Formula */
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1098,7 +1137,8 @@ void MeterCommonImplementation::addStringField(string vname,
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
NoLookup
|
NoLookup, /* Lookup table */
|
||||||
|
NULL /* Formula */
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1761,6 +1801,8 @@ bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<ucha
|
||||||
|
|
||||||
// Invoke standardized field extractors!
|
// Invoke standardized field extractors!
|
||||||
processFieldExtractors(&t);
|
processFieldExtractors(&t);
|
||||||
|
// Invoke any calculators working on the extracted fields.
|
||||||
|
processFieldCalculators();
|
||||||
// Invoke tailor made meter specific parsing!
|
// Invoke tailor made meter specific parsing!
|
||||||
processContent(&t);
|
processContent(&t);
|
||||||
// All done....
|
// All done....
|
||||||
|
@ -1848,6 +1890,18 @@ void MeterCommonImplementation::processFieldExtractors(Telegram *t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MeterCommonImplementation::processFieldCalculators()
|
||||||
|
{
|
||||||
|
// Iterate over the fields with formulas.
|
||||||
|
for (FieldInfo &fi : field_infos_)
|
||||||
|
{
|
||||||
|
if (fi.hasFormula())
|
||||||
|
{
|
||||||
|
fi.performCalculation(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MeterCommonImplementation::processContent(Telegram *t)
|
void MeterCommonImplementation::processContent(Telegram *t)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -1962,12 +2016,13 @@ string MeterCommonImplementation::decodeTPLStatusByte(uchar sts)
|
||||||
return ::decodeTPLStatusByteWithMfct(sts, mfct_tpl_status_bits_);
|
return ::decodeTPLStatusByteWithMfct(sts, mfct_tpl_status_bits_);
|
||||||
}
|
}
|
||||||
|
|
||||||
FieldInfo *MeterCommonImplementation::findFieldInfo(string vname)
|
FieldInfo *MeterCommonImplementation::findFieldInfo(string vname, Quantity xuantity)
|
||||||
{
|
{
|
||||||
FieldInfo *found = NULL;
|
FieldInfo *found = NULL;
|
||||||
for (FieldInfo &p : field_infos_)
|
for (FieldInfo &p : field_infos_)
|
||||||
{
|
{
|
||||||
if (p.vname() == vname)
|
if (p.vname() == vname &&
|
||||||
|
p.xuantity() == xuantity)
|
||||||
{
|
{
|
||||||
found = &p;
|
found = &p;
|
||||||
break;
|
break;
|
||||||
|
@ -1977,9 +2032,9 @@ FieldInfo *MeterCommonImplementation::findFieldInfo(string vname)
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
string MeterCommonImplementation::renderJsonOnlyDefaultUnit(string vname)
|
string MeterCommonImplementation::renderJsonOnlyDefaultUnit(string vname, Quantity xuantity)
|
||||||
{
|
{
|
||||||
FieldInfo *fi = findFieldInfo(vname);
|
FieldInfo *fi = findFieldInfo(vname, xuantity);
|
||||||
|
|
||||||
if (fi == NULL) return "unknown field "+vname;
|
if (fi == NULL) return "unknown field "+vname;
|
||||||
return fi->renderJsonOnlyDefaultUnit(this);
|
return fi->renderJsonOnlyDefaultUnit(this);
|
||||||
|
@ -1995,7 +2050,7 @@ string FieldInfo::renderJsonText(Meter *m)
|
||||||
return renderJson(m, NULL);
|
return renderJson(m, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
string FieldInfo::generateFieldName(DVEntry *dve)
|
string FieldInfo::generateFieldName()
|
||||||
{
|
{
|
||||||
if (xuantity_ == Quantity::Text)
|
if (xuantity_ == Quantity::Text)
|
||||||
{
|
{
|
||||||
|
@ -2566,24 +2621,38 @@ bool isValidKey(string& key, MeterDriver mt)
|
||||||
return hex2bin(key, &tmp);
|
return hex2bin(key, &tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void FieldInfo::performExtraction(Meter *m, Telegram *t, DVEntry *dve)
|
void FieldInfo::performExtraction(Meter *m, Telegram *t, DVEntry *dve)
|
||||||
{
|
{
|
||||||
if (xuantity_ == Quantity::Text)
|
if (xuantity_ == Quantity::Text)
|
||||||
{
|
{
|
||||||
|
// Extract a string.
|
||||||
extractString(m, t, dve);
|
extractString(m, t, dve);
|
||||||
}
|
}
|
||||||
else
|
else if (!hasFormula())
|
||||||
{
|
{
|
||||||
|
// Extract a numeric.
|
||||||
extractNumeric(m, t, dve);
|
extractNumeric(m, t, dve);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FieldInfo::performCalculation(Meter *m)
|
||||||
|
{
|
||||||
|
assert(hasFormula());
|
||||||
|
|
||||||
|
double value = formula_->calculate(defaultUnit());
|
||||||
|
m->setNumericValue(this, defaultUnit(), value);
|
||||||
|
}
|
||||||
|
|
||||||
bool FieldInfo::hasMatcher()
|
bool FieldInfo::hasMatcher()
|
||||||
{
|
{
|
||||||
return matcher_.active == true;
|
return matcher_.active == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FieldInfo::hasFormula()
|
||||||
|
{
|
||||||
|
return formula_ != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
bool FieldInfo::matches(DVEntry *dve)
|
bool FieldInfo::matches(DVEntry *dve)
|
||||||
{
|
{
|
||||||
return matcher_.matches(*dve);
|
return matcher_.matches(*dve);
|
||||||
|
@ -2638,7 +2707,7 @@ bool FieldInfo::extractNumeric(Meter *m, Telegram *t, DVEntry *dve)
|
||||||
assert(key == "" || dve->dif_vif_key.str() == key);
|
assert(key == "" || dve->dif_vif_key.str() == key);
|
||||||
|
|
||||||
// Generate the json field name:
|
// Generate the json field name:
|
||||||
string field_name = generateFieldName(dve);
|
string field_name = generateFieldName();
|
||||||
|
|
||||||
double extracted_double_value = NAN;
|
double extracted_double_value = NAN;
|
||||||
if (dve->extractDouble(&extracted_double_value,
|
if (dve->extractDouble(&extracted_double_value,
|
||||||
|
@ -2748,7 +2817,7 @@ bool FieldInfo::extractString(Meter *m, Telegram *t, DVEntry *dve)
|
||||||
assert(key == "" || dve->dif_vif_key.str() == key);
|
assert(key == "" || dve->dif_vif_key.str() == key);
|
||||||
|
|
||||||
// Generate the json field name:
|
// Generate the json field name:
|
||||||
string field_name = generateFieldName(dve);
|
string field_name = generateFieldName();
|
||||||
|
|
||||||
uint64_t extracted_bits {};
|
uint64_t extracted_bits {};
|
||||||
if (lookup_.hasLookups() || (print_properties_.hasJOINTPLSTATUS()))
|
if (lookup_.hasLookups() || (print_properties_.hasJOINTPLSTATUS()))
|
||||||
|
@ -2795,6 +2864,7 @@ bool FieldInfo::extractString(Meter *m, Telegram *t, DVEntry *dve)
|
||||||
matcher_.vif_range == VIFRange::FabricationNo ||
|
matcher_.vif_range == VIFRange::FabricationNo ||
|
||||||
matcher_.vif_range == VIFRange::ModelVersion ||
|
matcher_.vif_range == VIFRange::ModelVersion ||
|
||||||
matcher_.vif_range == VIFRange::SoftwareVersion ||
|
matcher_.vif_range == VIFRange::SoftwareVersion ||
|
||||||
|
matcher_.vif_range == VIFRange::Customer ||
|
||||||
matcher_.vif_range == VIFRange::ParameterSet)
|
matcher_.vif_range == VIFRange::ParameterSet)
|
||||||
{
|
{
|
||||||
string extracted_id;
|
string extracted_id;
|
||||||
|
|
21
src/meters.h
21
src/meters.h
|
@ -19,6 +19,7 @@
|
||||||
#define METER_H_
|
#define METER_H_
|
||||||
|
|
||||||
#include"dvparser.h"
|
#include"dvparser.h"
|
||||||
|
#include"formula.h"
|
||||||
#include"util.h"
|
#include"util.h"
|
||||||
#include"units.h"
|
#include"units.h"
|
||||||
#include"translatebits.h"
|
#include"translatebits.h"
|
||||||
|
@ -60,7 +61,6 @@ LIST_OF_METER_TYPES
|
||||||
#define LIST_OF_METERS \
|
#define LIST_OF_METERS \
|
||||||
X(auto, 0, AutoMeter, AUTO, Auto) \
|
X(auto, 0, AutoMeter, AUTO, Auto) \
|
||||||
X(unknown, 0, UnknownMeter, UNKNOWN, Unknown) \
|
X(unknown, 0, UnknownMeter, UNKNOWN, Unknown) \
|
||||||
X(ebzwmbe, T1_bit, ElectricityMeter, EBZWMBE, EBZWMBE) \
|
|
||||||
X(eurisii, T1_bit, HeatCostAllocationMeter, EURISII, EurisII) \
|
X(eurisii, T1_bit, HeatCostAllocationMeter, EURISII, EurisII) \
|
||||||
X(ehzp, T1_bit, ElectricityMeter, EHZP, EHZP) \
|
X(ehzp, T1_bit, ElectricityMeter, EHZP, EHZP) \
|
||||||
X(esyswm, T1_bit, ElectricityMeter, ESYSWM, ESYSWM) \
|
X(esyswm, T1_bit, ElectricityMeter, ESYSWM, ESYSWM) \
|
||||||
|
@ -328,7 +328,8 @@ struct FieldInfo
|
||||||
function<string()> get_string_value_override,
|
function<string()> get_string_value_override,
|
||||||
function<void(Unit,double)> set_numeric_value_override,
|
function<void(Unit,double)> set_numeric_value_override,
|
||||||
function<void(string)> set_string_value_override,
|
function<void(string)> set_string_value_override,
|
||||||
Translate::Lookup lookup
|
Translate::Lookup lookup,
|
||||||
|
Formula *formula
|
||||||
) :
|
) :
|
||||||
index_(index),
|
index_(index),
|
||||||
vname_(vname),
|
vname_(vname),
|
||||||
|
@ -342,7 +343,8 @@ struct FieldInfo
|
||||||
get_string_value_override_(get_string_value_override),
|
get_string_value_override_(get_string_value_override),
|
||||||
set_numeric_value_override_(set_numeric_value_override),
|
set_numeric_value_override_(set_numeric_value_override),
|
||||||
set_string_value_override_(set_string_value_override),
|
set_string_value_override_(set_string_value_override),
|
||||||
lookup_(lookup)
|
lookup_(lookup),
|
||||||
|
formula_(formula)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
int index() { return index_; }
|
int index() { return index_; }
|
||||||
|
@ -367,9 +369,12 @@ struct FieldInfo
|
||||||
bool extractNumeric(Meter *m, Telegram *t, DVEntry *dve = NULL);
|
bool extractNumeric(Meter *m, Telegram *t, DVEntry *dve = NULL);
|
||||||
bool extractString(Meter *m, Telegram *t, DVEntry *dve = NULL);
|
bool extractString(Meter *m, Telegram *t, DVEntry *dve = NULL);
|
||||||
bool hasMatcher();
|
bool hasMatcher();
|
||||||
|
bool hasFormula();
|
||||||
bool matches(DVEntry *dve);
|
bool matches(DVEntry *dve);
|
||||||
void performExtraction(Meter *m, Telegram *t, DVEntry *dve);
|
void performExtraction(Meter *m, Telegram *t, DVEntry *dve);
|
||||||
|
|
||||||
|
void performCalculation(Meter *m);
|
||||||
|
|
||||||
string renderJsonOnlyDefaultUnit(Meter *m);
|
string renderJsonOnlyDefaultUnit(Meter *m);
|
||||||
string renderJson(Meter *m, vector<Unit> *additional_conversions);
|
string renderJson(Meter *m, vector<Unit> *additional_conversions);
|
||||||
string renderJsonText(Meter *m);
|
string renderJsonText(Meter *m);
|
||||||
|
@ -377,7 +382,7 @@ struct FieldInfo
|
||||||
// A FieldInfo can be declared to handle any number of storage fields of a certain range.
|
// A FieldInfo can be declared to handle any number of storage fields of a certain range.
|
||||||
// The vname is then a pattern total_at_month_{storagenr-32} that gets translated into
|
// The vname is then a pattern total_at_month_{storagenr-32} that gets translated into
|
||||||
// total_at_month_2 (for the dventry with storage nr 34.)
|
// total_at_month_2 (for the dventry with storage nr 34.)
|
||||||
string generateFieldName(DVEntry *dve);
|
string generateFieldName();
|
||||||
// Check if the meter object stores a value for this field.
|
// Check if the meter object stores a value for this field.
|
||||||
bool hasValue(Meter *m);
|
bool hasValue(Meter *m);
|
||||||
|
|
||||||
|
@ -403,7 +408,11 @@ private:
|
||||||
function<void(Unit,double)> set_numeric_value_override_; // Call back to set the value in the c++ object
|
function<void(Unit,double)> set_numeric_value_override_; // Call back to set the value in the c++ object
|
||||||
function<void(string)> set_string_value_override_; // Call back to set the value string in the c++ object
|
function<void(string)> set_string_value_override_; // Call back to set the value string in the c++ object
|
||||||
|
|
||||||
|
// Lookup bits to strings.
|
||||||
Translate::Lookup lookup_;
|
Translate::Lookup lookup_;
|
||||||
|
|
||||||
|
// For calculated fields.
|
||||||
|
unique_ptr<Formula> formula_;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BusManager;
|
struct BusManager;
|
||||||
|
@ -469,8 +478,8 @@ struct Meter
|
||||||
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 FieldInfo *findFieldInfo(string vname, Quantity xuantity) = 0;
|
||||||
virtual string renderJsonOnlyDefaultUnit(string vname) = 0;
|
virtual string renderJsonOnlyDefaultUnit(string vname, Quantity xuantity) = 0;
|
||||||
|
|
||||||
virtual ~Meter() = default;
|
virtual ~Meter() = default;
|
||||||
};
|
};
|
||||||
|
|
|
@ -142,7 +142,15 @@ protected:
|
||||||
Quantity vquantity, // Value belongs to this quantity, this quantity determines the default unit.
|
Quantity vquantity, // Value belongs to this quantity, this quantity determines the default unit.
|
||||||
VifScaling vif_scaling, // How should any Vif value be scaled.
|
VifScaling vif_scaling, // How should any Vif value be scaled.
|
||||||
FieldMatcher matcher,
|
FieldMatcher matcher,
|
||||||
Unit use_unit = Unit::Unknown); // If specified use this unit instead.
|
Unit use_unit = Unit::Unknown); // If specified use this unit for the json field instead instead of the default unit.
|
||||||
|
|
||||||
|
void addNumericFieldWithCalculator(
|
||||||
|
string vname, // Name of value without unit, eg "total" "total_month{storagenr}"
|
||||||
|
string help, // Information about this field.
|
||||||
|
PrintProperties print_properties, // Should this be printed by default in fields,json and hr.
|
||||||
|
Quantity vquantity, // Value belongs to this quantity, this quantity determines the default unit.
|
||||||
|
string formula, // The formula can reference the other fields and + them together.
|
||||||
|
Unit use_unit = Unit::Unknown); // If specified use this unit for the json field instead instead of the default unit.
|
||||||
|
|
||||||
void addNumericField(
|
void addNumericField(
|
||||||
string vname, // Name of value without unit, eg total
|
string vname, // Name of value without unit, eg total
|
||||||
|
@ -221,10 +229,12 @@ 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.
|
||||||
|
|
||||||
FieldInfo *findFieldInfo(string vname);
|
FieldInfo *findFieldInfo(string vname, Quantity xuantity);
|
||||||
string renderJsonOnlyDefaultUnit(string vname);
|
string renderJsonOnlyDefaultUnit(string vname, Quantity xuantity);
|
||||||
|
|
||||||
void processFieldExtractors(Telegram *t);
|
void processFieldExtractors(Telegram *t);
|
||||||
|
void processFieldCalculators();
|
||||||
|
|
||||||
virtual void processContent(Telegram *t);
|
virtual void processContent(Telegram *t);
|
||||||
|
|
||||||
void setNumericValue(FieldInfo *fi, Unit u, double v);
|
void setNumericValue(FieldInfo *fi, Unit u, double v);
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include"aescmac.h"
|
#include"aescmac.h"
|
||||||
#include"cmdline.h"
|
#include"cmdline.h"
|
||||||
#include"config.h"
|
#include"config.h"
|
||||||
|
#include"formula_implementation.h"
|
||||||
#include"meters.h"
|
#include"meters.h"
|
||||||
#include"printer.h"
|
#include"printer.h"
|
||||||
#include"serial.h"
|
#include"serial.h"
|
||||||
|
@ -33,13 +34,13 @@ using namespace std;
|
||||||
|
|
||||||
int test_crc();
|
int test_crc();
|
||||||
int test_dvparser();
|
int test_dvparser();
|
||||||
int test_test();
|
int test_devices();
|
||||||
int test_linkmodes();
|
int test_linkmodes();
|
||||||
void test_ids();
|
void test_ids();
|
||||||
void test_addresses();
|
void test_addresses();
|
||||||
void test_kdf();
|
void test_kdf();
|
||||||
void test_periods();
|
void test_periods();
|
||||||
void test_devices();
|
void test_device_parsing();
|
||||||
void test_meters();
|
void test_meters();
|
||||||
void test_months();
|
void test_months();
|
||||||
void test_aes();
|
void test_aes();
|
||||||
|
@ -52,44 +53,66 @@ void test_ascii_detection();
|
||||||
void test_status_join();
|
void test_status_join();
|
||||||
void test_status_sort();
|
void test_status_sort();
|
||||||
void test_field_matcher();
|
void test_field_matcher();
|
||||||
|
void test_units();
|
||||||
|
void test_formulas_building();
|
||||||
|
void test_formulas_parsing();
|
||||||
|
|
||||||
|
bool test(const char *test_name, const char *pattern)
|
||||||
|
{
|
||||||
|
if (pattern == NULL) return true;
|
||||||
|
bool ok = strstr(test_name, pattern) != NULL;
|
||||||
|
if (ok) printf("Test %s\n", test_name);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
if (argc > 1) {
|
const char *pattern = NULL;
|
||||||
if (!strcmp(argv[1], "--debug"))
|
|
||||||
|
int i = 1;
|
||||||
|
while (i < argc) {
|
||||||
|
if (!strcmp(argv[i], "--debug"))
|
||||||
{
|
{
|
||||||
debugEnabled(true);
|
debugEnabled(true);
|
||||||
}
|
}
|
||||||
if (!strcmp(argv[1], "--trace"))
|
else
|
||||||
|
if (!strcmp(argv[i], "--trace"))
|
||||||
{
|
{
|
||||||
debugEnabled(true);
|
debugEnabled(true);
|
||||||
traceEnabled(true);
|
traceEnabled(true);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pattern = argv[i];
|
||||||
|
}
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
onExit([](){});
|
onExit([](){});
|
||||||
|
|
||||||
test_crc();
|
if (test("crc", pattern)) test_crc();
|
||||||
test_dvparser();
|
if (test("dvparser", pattern)) test_dvparser();
|
||||||
test_test();
|
if (test("devices", pattern)) test_devices();
|
||||||
test_devices();
|
if (test("device_parsing", pattern)) test_device_parsing();
|
||||||
test_meters();
|
if (test("meters", pattern)) test_meters();
|
||||||
/*
|
// test_linkmodes();
|
||||||
test_linkmodes();*/
|
if (test("ids", pattern)) test_ids();
|
||||||
test_ids();
|
|
||||||
// test_addresses();
|
// test_addresses();
|
||||||
test_kdf();
|
if (test("kdf", pattern)) test_kdf();
|
||||||
test_periods();
|
if (test("periods", pattern)) test_periods();
|
||||||
test_months();
|
if (test("months", pattern)) test_months();
|
||||||
test_aes();
|
if (test("aes", pattern)) test_aes();
|
||||||
test_sbc();
|
if (test("sbc", pattern)) test_sbc();
|
||||||
test_hex();
|
if (test("hex", pattern)) test_hex();
|
||||||
test_translate();
|
if (test("translate", pattern)) test_translate();
|
||||||
test_slip();
|
if (test("slip", pattern)) test_slip();
|
||||||
test_dvs();
|
if (test("dvs", pattern)) test_dvs();
|
||||||
test_ascii_detection();
|
if (test("ascii_detection", pattern)) test_ascii_detection();
|
||||||
test_status_join();
|
if (test("status_join", pattern)) test_status_join();
|
||||||
test_status_sort();
|
if (test("status_sort", pattern)) test_status_sort();
|
||||||
test_field_matcher();
|
if (test("field_matcher", pattern)) test_field_matcher();
|
||||||
|
if (test("units", pattern)) test_units();
|
||||||
|
if (test("formulas_building", pattern)) test_formulas_building();
|
||||||
|
if (test("formulas_parsing", pattern)) test_formulas_parsing();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -242,7 +265,7 @@ int test_dvparser()
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int test_test()
|
int test_devices()
|
||||||
{
|
{
|
||||||
shared_ptr<SerialCommunicationManager> manager = createSerialCommunicationManager(0, false);
|
shared_ptr<SerialCommunicationManager> manager = createSerialCommunicationManager(0, false);
|
||||||
|
|
||||||
|
@ -663,7 +686,7 @@ void testd(string arg, bool xok, string xalias, string xfile, string xtype, stri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_devices()
|
void test_device_parsing()
|
||||||
{
|
{
|
||||||
testd("Bus_4711=/dev/ttyUSB0:im871a[12345678]:9600:868.95M:c1,t1", true,
|
testd("Bus_4711=/dev/ttyUSB0:im871a[12345678]:9600:868.95M:c1,t1", true,
|
||||||
"Bus_4711", // alias
|
"Bus_4711", // alias
|
||||||
|
@ -1537,3 +1560,248 @@ void test_field_matcher()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_unit(string in, bool expected_ok, string expected_vname, Unit expected_unit)
|
||||||
|
{
|
||||||
|
Unit unit;
|
||||||
|
string vname;
|
||||||
|
|
||||||
|
bool ok = extractUnit(in, &vname, &unit);
|
||||||
|
|
||||||
|
if (ok != expected_ok ||
|
||||||
|
vname != expected_vname ||
|
||||||
|
unit != expected_unit)
|
||||||
|
{
|
||||||
|
printf("ERROR expected ok=%d vname=%s unit=%s but got\n"
|
||||||
|
" but got ok=%d vname=%s unit=%s\n",
|
||||||
|
expected_ok, expected_vname.c_str(), unitToStringUpperCase(expected_unit).c_str(),
|
||||||
|
ok, vname.c_str(), unitToStringUpperCase(unit).c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_units()
|
||||||
|
{
|
||||||
|
test_unit("total_kwh", true, "total", Unit::KWH);
|
||||||
|
test_unit("total_", false, "", Unit::Unknown);
|
||||||
|
test_unit("total", false, "", Unit::Unknown);
|
||||||
|
test_unit("", false, "", Unit::Unknown);
|
||||||
|
test_unit("_c", false, "", Unit::Unknown);
|
||||||
|
|
||||||
|
test_unit("work__c", true, "work_", Unit::C);
|
||||||
|
|
||||||
|
test_unit("water_c", true, "water", Unit::C);
|
||||||
|
test_unit("walk_counter", true, "walk", Unit::COUNTER);
|
||||||
|
test_unit("work_kvarh", true, "work", Unit::KVARH);
|
||||||
|
|
||||||
|
test_unit("current_power_consumption_phase1_kw", true, "current_power_consumption_phase1", Unit::KW);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void test_formulas_building()
|
||||||
|
{
|
||||||
|
unique_ptr<FormulaImplementation> f = unique_ptr<FormulaImplementation>(new FormulaImplementation());
|
||||||
|
|
||||||
|
assert(f->doConstant(Unit::KWH, 17));
|
||||||
|
assert(f->doConstant(Unit::KWH, 1));
|
||||||
|
assert(f->doAddition());
|
||||||
|
double v = f->calculate(Unit::KWH);
|
||||||
|
if (v != 18.0)
|
||||||
|
{
|
||||||
|
printf("ERROR in test formula 1 expected 18.0 but got %lf\n", v);
|
||||||
|
}
|
||||||
|
|
||||||
|
f->clear();
|
||||||
|
assert(f->doConstant(Unit::KWH, 10));
|
||||||
|
v = f->calculate(Unit::MJ);
|
||||||
|
if (v != 36.0)
|
||||||
|
{
|
||||||
|
printf("ERROR in test formula 2 expected 36.0 but got %lf\n", v);
|
||||||
|
}
|
||||||
|
|
||||||
|
f->clear();
|
||||||
|
assert(f->doConstant(Unit::GJ, 10));
|
||||||
|
assert(f->doConstant(Unit::MJ, 10));
|
||||||
|
assert(f->doAddition());
|
||||||
|
v = f->calculate(Unit::GJ);
|
||||||
|
if (v != 10.01)
|
||||||
|
{
|
||||||
|
printf("ERROR in test formula 3 expected 10.01 but got %lf\n", v);
|
||||||
|
}
|
||||||
|
|
||||||
|
f->clear();
|
||||||
|
assert(f->doConstant(Unit::C, 10));
|
||||||
|
assert(f->doConstant(Unit::C, 20));
|
||||||
|
assert(f->doAddition());
|
||||||
|
assert(f->doConstant(Unit::C, 22));
|
||||||
|
assert(f->doAddition());
|
||||||
|
v = f->calculate(Unit::C);
|
||||||
|
if (v != 52)
|
||||||
|
{
|
||||||
|
printf("ERROR in test formula 4 expected 52 but got %lf\n", v);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
{
|
||||||
|
MeterInfo mi;
|
||||||
|
mi.parse("testur", "multical21", "12345678", "");
|
||||||
|
shared_ptr<Meter> meter = createMeter(&mi);
|
||||||
|
FieldInfo *fi_flow = meter->findFieldInfo("flow_temperature", Quantity::Temperature);
|
||||||
|
FieldInfo *fi_ext = meter->findFieldInfo("external_temperature", Quantity::Temperature);
|
||||||
|
assert(fi_flow != NULL);
|
||||||
|
assert(fi_ext != NULL);
|
||||||
|
|
||||||
|
vector<uchar> frame;
|
||||||
|
hex2bin("2a442d2c785634121B168d2091d37cac217f2d7802ff207100041308190000441308190000615B1f616713", &frame);
|
||||||
|
|
||||||
|
Telegram t;
|
||||||
|
MeterKeys mk;
|
||||||
|
t.parse(frame, &mk, true);
|
||||||
|
|
||||||
|
string id;
|
||||||
|
bool match;
|
||||||
|
meter->handleTelegram(t.about, frame, true, &id, &match, &t);
|
||||||
|
|
||||||
|
f->clear();
|
||||||
|
|
||||||
|
assert(f->doField(Unit::C, meter.get(), fi_flow));
|
||||||
|
v = f->calculate(Unit::C);
|
||||||
|
if (v != 31)
|
||||||
|
{
|
||||||
|
printf("ERROR in test formula 5 expected 31 but got %lf\n", v);
|
||||||
|
}
|
||||||
|
|
||||||
|
f->clear();
|
||||||
|
|
||||||
|
assert(f->doField(Unit::C, meter.get(), fi_flow));
|
||||||
|
assert(f->doField(Unit::C, meter.get(), fi_ext));
|
||||||
|
assert(f->doAddition());
|
||||||
|
v = f->calculate(Unit::C);
|
||||||
|
if (v != 50)
|
||||||
|
{
|
||||||
|
printf("ERROR in test formula 6 expected 50 but got %lf\n", v);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Check that trying to add a field reference expecting a non-convertible unit, will fail!
|
||||||
|
f->clear();
|
||||||
|
assert(false == f->doField(Unit::M3, meter.get(), fi_flow));
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
{
|
||||||
|
MeterInfo mi;
|
||||||
|
mi.parse("testur", "ebzwmbe", "22992299", "");
|
||||||
|
shared_ptr<Meter> meter = createMeter(&mi);
|
||||||
|
FieldInfo *fi_p1 = meter->findFieldInfo("current_power_consumption_phase1", Quantity::Power);
|
||||||
|
FieldInfo *fi_p2 = meter->findFieldInfo("current_power_consumption_phase2", Quantity::Power);
|
||||||
|
FieldInfo *fi_p3 = meter->findFieldInfo("current_power_consumption_phase3", Quantity::Power);
|
||||||
|
assert(fi_p1 != NULL);
|
||||||
|
assert(fi_p2 != NULL);
|
||||||
|
assert(fi_p3 != NULL);
|
||||||
|
|
||||||
|
vector<uchar> frame;
|
||||||
|
hex2bin("5B445a149922992202378c20f6900f002c25Bc9e0000BBBBBBBBBBBBBBBB72992299225a140102f6003007102f2f040330f92a0004a9ff01ff24000004a9ff026a29000004a9ff03460600000dfd11063132333435362f2f2f2f2f2f", &frame);
|
||||||
|
|
||||||
|
Telegram t;
|
||||||
|
MeterKeys mk;
|
||||||
|
t.parse(frame, &mk, true);
|
||||||
|
|
||||||
|
string id;
|
||||||
|
bool match;
|
||||||
|
meter->handleTelegram(t.about, frame, true, &id, &match, &t);
|
||||||
|
|
||||||
|
unique_ptr<FormulaImplementation> f = unique_ptr<FormulaImplementation>(new FormulaImplementation());
|
||||||
|
|
||||||
|
f->clear();
|
||||||
|
|
||||||
|
assert(f->doField(Unit::KW, meter.get(), fi_p1));
|
||||||
|
assert(f->doField(Unit::KW, meter.get(), fi_p2));
|
||||||
|
assert(f->doAddition());
|
||||||
|
assert(f->doField(Unit::KW, meter.get(), fi_p3));
|
||||||
|
assert(f->doAddition());
|
||||||
|
|
||||||
|
v = f->calculate(Unit::KW);
|
||||||
|
if (v != 0.21679)
|
||||||
|
{
|
||||||
|
printf("ERROR in test formula 7 expected 0.21679 but got %lf\n", v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_formula_tree(FormulaImplementation *f, Meter *m, string formula, string tree)
|
||||||
|
{
|
||||||
|
f->clear();
|
||||||
|
f->parse(m, formula);
|
||||||
|
string t = f->tree();
|
||||||
|
if (t != tree)
|
||||||
|
{
|
||||||
|
printf("ERROR when parsing \"%s\" expected tree to be \"%s\"\nbut got \"%s\"\n",
|
||||||
|
formula.c_str(), tree.c_str(), t.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_formula_value(FormulaImplementation *f, Meter *m, string formula, double val, Unit unit)
|
||||||
|
{
|
||||||
|
f->clear();
|
||||||
|
|
||||||
|
bool ok = f->parse(m, formula);
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
double v = f->calculate(unit);
|
||||||
|
debug("(formula) %s\n", f->tree().c_str());
|
||||||
|
|
||||||
|
if (v != val)
|
||||||
|
{
|
||||||
|
printf("ERROR when evaluating \"%s\"\nERROR expected %.15g but got %.15g\n", formula.c_str(), val, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_formulas_parsing()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
MeterInfo mi;
|
||||||
|
mi.parse("testur", "ebzwmbe", "22992299", "");
|
||||||
|
shared_ptr<Meter> meter = createMeter(&mi);
|
||||||
|
|
||||||
|
vector<uchar> frame;
|
||||||
|
hex2bin("5B445a149922992202378c20f6900f002c25Bc9e0000BBBBBBBBBBBBBBBB72992299225a140102f6003007102f2f040330f92a0004a9ff01ff24000004a9ff026a29000004a9ff03460600000dfd11063132333435362f2f2f2f2f2f", &frame);
|
||||||
|
|
||||||
|
Telegram t;
|
||||||
|
MeterKeys mk;
|
||||||
|
t.parse(frame, &mk, true);
|
||||||
|
|
||||||
|
string id;
|
||||||
|
bool match;
|
||||||
|
meter->handleTelegram(t.about, frame, true, &id, &match, &t);
|
||||||
|
|
||||||
|
unique_ptr<FormulaImplementation> f = unique_ptr<FormulaImplementation>(new FormulaImplementation());
|
||||||
|
|
||||||
|
test_formula_value(f.get(), meter.get(),
|
||||||
|
"10 kwh + 100 kwh",
|
||||||
|
110,
|
||||||
|
Unit::KWH);
|
||||||
|
|
||||||
|
test_formula_value(f.get(), meter.get(),
|
||||||
|
"current_power_consumption_phase1_kw + "
|
||||||
|
"current_power_consumption_phase2_kw + "
|
||||||
|
"current_power_consumption_phase3_kw + "
|
||||||
|
"100 kw",
|
||||||
|
100.21679,
|
||||||
|
Unit::KW);
|
||||||
|
|
||||||
|
test_formula_tree(f.get(), meter.get(),
|
||||||
|
"5 c + 7 c + 10 c",
|
||||||
|
"<ADD <ADD <CONST 5 c> <CONST 7 c> > <CONST 10 c> >");
|
||||||
|
|
||||||
|
test_formula_tree(f.get(), meter.get(),
|
||||||
|
"(5 c + 7 c) + 10 c",
|
||||||
|
"<ADD <ADD <CONST 5 c> <CONST 7 c> > <CONST 10 c> >");
|
||||||
|
|
||||||
|
test_formula_tree(f.get(), meter.get(),
|
||||||
|
"5 c + (7 c + 10 c)",
|
||||||
|
"<ADD <CONST 5 c> <ADD <CONST 7 c> <CONST 10 c> > >");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
49
src/units.cc
49
src/units.cc
|
@ -19,6 +19,7 @@
|
||||||
#include"util.h"
|
#include"util.h"
|
||||||
#include<assert.h>
|
#include<assert.h>
|
||||||
#include<math.h>
|
#include<math.h>
|
||||||
|
#include<string.h>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
@ -81,6 +82,16 @@ LIST_OF_CONVERSIONS
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Unit whenMultiplied(Unit left, Unit right)
|
||||||
|
{
|
||||||
|
return Unit::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
double multiply(double l, Unit left, double r, Unit right)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool isQuantity(Unit u, Quantity q)
|
bool isQuantity(Unit u, Quantity q)
|
||||||
{
|
{
|
||||||
#define X(cname,lcname,hrname,quantity,explanation) if (u == Unit::cname) return Quantity::quantity == q;
|
#define X(cname,lcname,hrname,quantity,explanation) if (u == Unit::cname) return Quantity::quantity == q;
|
||||||
|
@ -90,6 +101,15 @@ LIST_OF_UNITS
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Quantity toQuantity(Unit u)
|
||||||
|
{
|
||||||
|
#define X(cname,lcname,hrname,quantity,explanation) if (u == Unit::cname) return Quantity::quantity;
|
||||||
|
LIST_OF_UNITS
|
||||||
|
#undef X
|
||||||
|
|
||||||
|
return Quantity::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
void assertQuantity(Unit u, Quantity q)
|
void assertQuantity(Unit u, Quantity q)
|
||||||
{
|
{
|
||||||
if (!isQuantity(u, q))
|
if (!isQuantity(u, q))
|
||||||
|
@ -117,7 +137,7 @@ LIST_OF_QUANTITIES
|
||||||
|
|
||||||
Unit toUnit(string s)
|
Unit toUnit(string s)
|
||||||
{
|
{
|
||||||
#define X(cname,lcname,hrname,quantity,explanation) if (s == #cname) return Unit::cname;
|
#define X(cname,lcname,hrname,quantity,explanation) if (s == #cname || s == #lcname) return Unit::cname;
|
||||||
LIST_OF_UNITS
|
LIST_OF_UNITS
|
||||||
#undef X
|
#undef X
|
||||||
|
|
||||||
|
@ -190,3 +210,30 @@ string valueToString(double v, Unit u)
|
||||||
if (s.length() == 0) return "0";
|
if (s.length() == 0) return "0";
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool extractUnit(const string &s, string *vname, Unit *u)
|
||||||
|
{
|
||||||
|
size_t pos;
|
||||||
|
string vn;
|
||||||
|
const char *c;
|
||||||
|
|
||||||
|
if (s.length() < 3) goto err;
|
||||||
|
|
||||||
|
pos = s.rfind('_');
|
||||||
|
|
||||||
|
if (pos == string::npos) goto err;
|
||||||
|
if (pos+1 >= s.length()) goto err;
|
||||||
|
vn = s.substr(0,pos);
|
||||||
|
pos++;
|
||||||
|
|
||||||
|
c = s.c_str()+pos;
|
||||||
|
|
||||||
|
#define X(cname,lcname,hrname,quantity,explanation) if (!strcmp(c, #lcname)) { *u = Unit::cname; *vname = vn; return true; }
|
||||||
|
LIST_OF_UNITS
|
||||||
|
#undef X
|
||||||
|
|
||||||
|
err:
|
||||||
|
*vname = "";
|
||||||
|
*u = Unit::Unknown;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
|
@ -90,9 +90,14 @@ LIST_OF_QUANTITIES
|
||||||
|
|
||||||
bool canConvert(Unit from, Unit to);
|
bool canConvert(Unit from, Unit to);
|
||||||
double convert(double v, Unit from, Unit to);
|
double convert(double v, Unit from, Unit to);
|
||||||
|
Unit whenMultiplied(Unit left, Unit right);
|
||||||
|
double multiply(double l, Unit left, double r, Unit right);
|
||||||
|
|
||||||
|
// Either uppercase KWH or lowercase kwh works here.
|
||||||
Unit toUnit(std::string s);
|
Unit toUnit(std::string s);
|
||||||
const char *toString(Quantity q);
|
const char *toString(Quantity q);
|
||||||
bool isQuantity(Unit u, Quantity q);
|
bool isQuantity(Unit u, Quantity q);
|
||||||
|
Quantity toQuantity(Unit u);
|
||||||
void assertQuantity(Unit u, Quantity q);
|
void assertQuantity(Unit u, Quantity q);
|
||||||
Unit defaultUnitForQuantity(Quantity q);
|
Unit defaultUnitForQuantity(Quantity q);
|
||||||
std::string unitToStringHR(Unit u);
|
std::string unitToStringHR(Unit u);
|
||||||
|
@ -102,4 +107,6 @@ std::string valueToString(double v, Unit u);
|
||||||
|
|
||||||
Unit replaceWithConversionUnit(Unit u, std::vector<Unit> cs);
|
Unit replaceWithConversionUnit(Unit u, std::vector<Unit> cs);
|
||||||
|
|
||||||
|
bool extractUnit(const std::string &s, std::string *vname, Unit *u);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -453,6 +453,10 @@ bool isDebugEnabled() {
|
||||||
return debug_enabled_;
|
return debug_enabled_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isTraceEnabled() {
|
||||||
|
return trace_enabled_;
|
||||||
|
}
|
||||||
|
|
||||||
bool isLogTelegramsEnabled() {
|
bool isLogTelegramsEnabled() {
|
||||||
return log_telegrams_enabled_;
|
return log_telegrams_enabled_;
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,6 +115,7 @@ bool isInternalTestingEnabled();
|
||||||
|
|
||||||
bool isVerboseEnabled();
|
bool isVerboseEnabled();
|
||||||
bool isDebugEnabled();
|
bool isDebugEnabled();
|
||||||
|
bool isTraceEnabled();
|
||||||
bool isLogTelegramsEnabled();
|
bool isLogTelegramsEnabled();
|
||||||
|
|
||||||
void debugPayload(std::string intro, std::vector<uchar> &payload);
|
void debugPayload(std::string intro, std::vector<uchar> &payload);
|
||||||
|
|
|
@ -789,7 +789,6 @@ void Telegram::addExplanationAndIncrementPos(vector<uchar>::iterator &pos, int l
|
||||||
vsnprintf(buf, 1023, fmt, args);
|
vsnprintf(buf, 1023, fmt, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
||||||
|
|
||||||
Explanation e(parsed.size(), len, buf, k, u);
|
Explanation e(parsed.size(), len, buf, k, u);
|
||||||
explanations.push_back(e);
|
explanations.push_back(e);
|
||||||
parsed.insert(parsed.end(), pos, pos+len);
|
parsed.insert(parsed.end(), pos, pos+len);
|
||||||
|
|
Ładowanie…
Reference in New Issue