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)/config.o \
|
||||
$(BUILD)/dvparser.o \
|
||||
$(BUILD)/formula.o \
|
||||
$(BUILD)/mbus_rawtty.o \
|
||||
$(BUILD)/meters.o \
|
||||
$(BUILD)/manufacturer_specificities.o \
|
||||
|
@ -289,6 +290,9 @@ testd:
|
|||
testdriver:
|
||||
@./tests/test_drivers.sh build/wmbusmeters driver_${DRIVER}.cc
|
||||
|
||||
testdriverd:
|
||||
@./tests/test_drivers.sh build_debug/wmbusmeters driver_${DRIVER}.cc
|
||||
|
||||
update_manufacturers:
|
||||
iconv -f utf-8 -t ascii//TRANSLIT -c DLMS_Flagids.csv -o tmp.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.
|
||||
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"}
|
||||
|Elen1;22992299;2816.304000;0.216790;0.094710;0.106020;0.016060;1111-11-11 11:11.11
|
||||
{"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
|
||||
|
||||
# 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;
|
||||
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,
|
||||
"*** %02X%02X (%s)", bytes[i], bytes[i+1], info.c_str());
|
||||
i+=2;
|
||||
|
||||
if (i+1 >= len) return;
|
||||
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,
|
||||
"*** %02X%02X (%s)", bytes[i], bytes[i+1], info.c_str());
|
||||
i+=2;
|
||||
|
@ -175,14 +175,14 @@ namespace
|
|||
|
||||
if (i+1 >= len) return;
|
||||
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,
|
||||
"*** %02X%02X (%s)", bytes[i], bytes[i+1], info.c_str());
|
||||
i+=2;
|
||||
|
||||
if (i+1 >= len) return;
|
||||
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,
|
||||
"*** %02X%02X (%s)", bytes[i], bytes[i+1], info.c_str());
|
||||
i+=2;
|
||||
|
|
|
@ -131,17 +131,17 @@ void MeterLansenDW::processContent(Telegram *t)
|
|||
|
||||
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))
|
||||
{
|
||||
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))
|
||||
{
|
||||
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) {
|
||||
DEBUG_PARSER("(dvparser debug) varlen %02x\n", *(data+0));
|
||||
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);
|
||||
if (remaining < datalen)
|
||||
|
@ -435,10 +437,6 @@ bool parseDV(Telegram *t,
|
|||
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);
|
||||
int offset = start_parse_here+data-data_start;
|
||||
|
||||
|
@ -454,10 +452,10 @@ bool parseDV(Telegram *t,
|
|||
|
||||
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());
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
X(ParameterSet,0x7D0B,0x7D0B, Quantity::Text, Unit::TXT) \
|
||||
X(ModelVersion,0x7D0C,0x7D0C, 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(DigitalInput,0x7D1B,0x7D1B, Quantity::Text, Unit::TXT) \
|
||||
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 \
|
||||
X(CCx01, MANUFACTURER_GSS, 0x02, 0x01) \
|
||||
X(EBZWMBE, MANUFACTURER_EBZ, 0x37, 0x02) \
|
||||
X(EURISII, MANUFACTURER_INE, 0x08, 0x55) \
|
||||
X(EHZP, MANUFACTURER_EMH, 0x02, 0x02) \
|
||||
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,
|
||||
NoLookup
|
||||
NoLookup, /* Lookup table */
|
||||
NULL /* Formula */
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -866,7 +867,8 @@ void MeterCommonImplementation::addPrint(string vname, Quantity vquantity, Unit
|
|||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NoLookup
|
||||
NoLookup, /* Lookup table */
|
||||
NULL /* Formula */
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -887,7 +889,8 @@ void MeterCommonImplementation::addPrint(string vname, Quantity vquantity,
|
|||
getValueFunc,
|
||||
NULL,
|
||||
NULL,
|
||||
NoLookup
|
||||
NoLookup, /* Lookup table */
|
||||
NULL /* Formula */
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -919,7 +922,8 @@ void MeterCommonImplementation::addNumericFieldWithExtractor(
|
|||
NULL,
|
||||
setValueFunc,
|
||||
NULL,
|
||||
NoLookup
|
||||
NoLookup, /* Lookup table */
|
||||
NULL /* Formula */
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -944,7 +948,37 @@ void MeterCommonImplementation::addNumericFieldWithExtractor(string vname,
|
|||
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,
|
||||
setValueFunc,
|
||||
NULL,
|
||||
NoLookup
|
||||
NoLookup, /* Lookup table */
|
||||
NULL /* Formula */
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -1000,7 +1035,8 @@ void MeterCommonImplementation::addStringFieldWithExtractor(
|
|||
getValueFunc,
|
||||
NULL,
|
||||
setValueFunc,
|
||||
NoLookup
|
||||
NoLookup, /* Lookup table */
|
||||
NULL /* Formula */
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -1022,7 +1058,8 @@ void MeterCommonImplementation::addStringFieldWithExtractor(string vname,
|
|||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NoLookup
|
||||
NoLookup, /* Lookup table */
|
||||
NULL /* Formula */
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -1054,7 +1091,8 @@ void MeterCommonImplementation::addStringFieldWithExtractorAndLookup(
|
|||
getValueFunc,
|
||||
NULL,
|
||||
setValueFunc,
|
||||
lookup
|
||||
lookup, /* Lookup table */
|
||||
NULL /* Formula */
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -1077,7 +1115,8 @@ void MeterCommonImplementation::addStringFieldWithExtractorAndLookup(string vnam
|
|||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
lookup
|
||||
lookup,
|
||||
NULL /* Formula */
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -1098,7 +1137,8 @@ void MeterCommonImplementation::addStringField(string vname,
|
|||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NoLookup
|
||||
NoLookup, /* Lookup table */
|
||||
NULL /* Formula */
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -1761,6 +1801,8 @@ bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<ucha
|
|||
|
||||
// Invoke standardized field extractors!
|
||||
processFieldExtractors(&t);
|
||||
// Invoke any calculators working on the extracted fields.
|
||||
processFieldCalculators();
|
||||
// Invoke tailor made meter specific parsing!
|
||||
processContent(&t);
|
||||
// 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)
|
||||
{
|
||||
}
|
||||
|
@ -1962,12 +2016,13 @@ string MeterCommonImplementation::decodeTPLStatusByte(uchar sts)
|
|||
return ::decodeTPLStatusByteWithMfct(sts, mfct_tpl_status_bits_);
|
||||
}
|
||||
|
||||
FieldInfo *MeterCommonImplementation::findFieldInfo(string vname)
|
||||
FieldInfo *MeterCommonImplementation::findFieldInfo(string vname, Quantity xuantity)
|
||||
{
|
||||
FieldInfo *found = NULL;
|
||||
for (FieldInfo &p : field_infos_)
|
||||
{
|
||||
if (p.vname() == vname)
|
||||
if (p.vname() == vname &&
|
||||
p.xuantity() == xuantity)
|
||||
{
|
||||
found = &p;
|
||||
break;
|
||||
|
@ -1977,9 +2032,9 @@ FieldInfo *MeterCommonImplementation::findFieldInfo(string vname)
|
|||
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;
|
||||
return fi->renderJsonOnlyDefaultUnit(this);
|
||||
|
@ -1995,7 +2050,7 @@ string FieldInfo::renderJsonText(Meter *m)
|
|||
return renderJson(m, NULL);
|
||||
}
|
||||
|
||||
string FieldInfo::generateFieldName(DVEntry *dve)
|
||||
string FieldInfo::generateFieldName()
|
||||
{
|
||||
if (xuantity_ == Quantity::Text)
|
||||
{
|
||||
|
@ -2566,24 +2621,38 @@ bool isValidKey(string& key, MeterDriver mt)
|
|||
return hex2bin(key, &tmp);
|
||||
}
|
||||
|
||||
|
||||
void FieldInfo::performExtraction(Meter *m, Telegram *t, DVEntry *dve)
|
||||
{
|
||||
if (xuantity_ == Quantity::Text)
|
||||
{
|
||||
// Extract a string.
|
||||
extractString(m, t, dve);
|
||||
}
|
||||
else
|
||||
else if (!hasFormula())
|
||||
{
|
||||
// Extract a numeric.
|
||||
extractNumeric(m, t, dve);
|
||||
}
|
||||
}
|
||||
|
||||
void FieldInfo::performCalculation(Meter *m)
|
||||
{
|
||||
assert(hasFormula());
|
||||
|
||||
double value = formula_->calculate(defaultUnit());
|
||||
m->setNumericValue(this, defaultUnit(), value);
|
||||
}
|
||||
|
||||
bool FieldInfo::hasMatcher()
|
||||
{
|
||||
return matcher_.active == true;
|
||||
}
|
||||
|
||||
bool FieldInfo::hasFormula()
|
||||
{
|
||||
return formula_ != NULL;
|
||||
}
|
||||
|
||||
bool FieldInfo::matches(DVEntry *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);
|
||||
|
||||
// Generate the json field name:
|
||||
string field_name = generateFieldName(dve);
|
||||
string field_name = generateFieldName();
|
||||
|
||||
double extracted_double_value = NAN;
|
||||
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);
|
||||
|
||||
// Generate the json field name:
|
||||
string field_name = generateFieldName(dve);
|
||||
string field_name = generateFieldName();
|
||||
|
||||
uint64_t extracted_bits {};
|
||||
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::ModelVersion ||
|
||||
matcher_.vif_range == VIFRange::SoftwareVersion ||
|
||||
matcher_.vif_range == VIFRange::Customer ||
|
||||
matcher_.vif_range == VIFRange::ParameterSet)
|
||||
{
|
||||
string extracted_id;
|
||||
|
|
21
src/meters.h
21
src/meters.h
|
@ -19,6 +19,7 @@
|
|||
#define METER_H_
|
||||
|
||||
#include"dvparser.h"
|
||||
#include"formula.h"
|
||||
#include"util.h"
|
||||
#include"units.h"
|
||||
#include"translatebits.h"
|
||||
|
@ -60,7 +61,6 @@ LIST_OF_METER_TYPES
|
|||
#define LIST_OF_METERS \
|
||||
X(auto, 0, AutoMeter, AUTO, Auto) \
|
||||
X(unknown, 0, UnknownMeter, UNKNOWN, Unknown) \
|
||||
X(ebzwmbe, T1_bit, ElectricityMeter, EBZWMBE, EBZWMBE) \
|
||||
X(eurisii, T1_bit, HeatCostAllocationMeter, EURISII, EurisII) \
|
||||
X(ehzp, T1_bit, ElectricityMeter, EHZP, EHZP) \
|
||||
X(esyswm, T1_bit, ElectricityMeter, ESYSWM, ESYSWM) \
|
||||
|
@ -328,7 +328,8 @@ struct FieldInfo
|
|||
function<string()> get_string_value_override,
|
||||
function<void(Unit,double)> set_numeric_value_override,
|
||||
function<void(string)> set_string_value_override,
|
||||
Translate::Lookup lookup
|
||||
Translate::Lookup lookup,
|
||||
Formula *formula
|
||||
) :
|
||||
index_(index),
|
||||
vname_(vname),
|
||||
|
@ -342,7 +343,8 @@ struct FieldInfo
|
|||
get_string_value_override_(get_string_value_override),
|
||||
set_numeric_value_override_(set_numeric_value_override),
|
||||
set_string_value_override_(set_string_value_override),
|
||||
lookup_(lookup)
|
||||
lookup_(lookup),
|
||||
formula_(formula)
|
||||
{}
|
||||
|
||||
int index() { return index_; }
|
||||
|
@ -367,9 +369,12 @@ struct FieldInfo
|
|||
bool extractNumeric(Meter *m, Telegram *t, DVEntry *dve = NULL);
|
||||
bool extractString(Meter *m, Telegram *t, DVEntry *dve = NULL);
|
||||
bool hasMatcher();
|
||||
bool hasFormula();
|
||||
bool matches(DVEntry *dve);
|
||||
void performExtraction(Meter *m, Telegram *t, DVEntry *dve);
|
||||
|
||||
void performCalculation(Meter *m);
|
||||
|
||||
string renderJsonOnlyDefaultUnit(Meter *m);
|
||||
string renderJson(Meter *m, vector<Unit> *additional_conversions);
|
||||
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.
|
||||
// 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.)
|
||||
string generateFieldName(DVEntry *dve);
|
||||
string generateFieldName();
|
||||
// Check if the meter object stores a value for this field.
|
||||
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(string)> set_string_value_override_; // Call back to set the value string in the c++ object
|
||||
|
||||
// Lookup bits to strings.
|
||||
Translate::Lookup lookup_;
|
||||
|
||||
// For calculated fields.
|
||||
unique_ptr<Formula> formula_;
|
||||
};
|
||||
|
||||
struct BusManager;
|
||||
|
@ -469,8 +478,8 @@ struct Meter
|
|||
virtual vector<string> &shellCmdlines() = 0;
|
||||
virtual void poll(shared_ptr<BusManager> bus) = 0;
|
||||
|
||||
virtual FieldInfo *findFieldInfo(string vname) = 0;
|
||||
virtual string renderJsonOnlyDefaultUnit(string vname) = 0;
|
||||
virtual FieldInfo *findFieldInfo(string vname, Quantity xuantity) = 0;
|
||||
virtual string renderJsonOnlyDefaultUnit(string vname, Quantity xuantity) = 0;
|
||||
|
||||
virtual ~Meter() = default;
|
||||
};
|
||||
|
|
|
@ -142,7 +142,15 @@ protected:
|
|||
Quantity vquantity, // Value belongs to this quantity, this quantity determines the default unit.
|
||||
VifScaling vif_scaling, // How should any Vif value be scaled.
|
||||
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(
|
||||
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
|
||||
// same as timestamp_utc, can always be decoded/recoded into local time or a unix timestamp.
|
||||
|
||||
FieldInfo *findFieldInfo(string vname);
|
||||
string renderJsonOnlyDefaultUnit(string vname);
|
||||
FieldInfo *findFieldInfo(string vname, Quantity xuantity);
|
||||
string renderJsonOnlyDefaultUnit(string vname, Quantity xuantity);
|
||||
|
||||
void processFieldExtractors(Telegram *t);
|
||||
void processFieldCalculators();
|
||||
|
||||
virtual void processContent(Telegram *t);
|
||||
|
||||
void setNumericValue(FieldInfo *fi, Unit u, double v);
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include"aescmac.h"
|
||||
#include"cmdline.h"
|
||||
#include"config.h"
|
||||
#include"formula_implementation.h"
|
||||
#include"meters.h"
|
||||
#include"printer.h"
|
||||
#include"serial.h"
|
||||
|
@ -33,13 +34,13 @@ using namespace std;
|
|||
|
||||
int test_crc();
|
||||
int test_dvparser();
|
||||
int test_test();
|
||||
int test_devices();
|
||||
int test_linkmodes();
|
||||
void test_ids();
|
||||
void test_addresses();
|
||||
void test_kdf();
|
||||
void test_periods();
|
||||
void test_devices();
|
||||
void test_device_parsing();
|
||||
void test_meters();
|
||||
void test_months();
|
||||
void test_aes();
|
||||
|
@ -52,44 +53,66 @@ void test_ascii_detection();
|
|||
void test_status_join();
|
||||
void test_status_sort();
|
||||
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)
|
||||
{
|
||||
if (argc > 1) {
|
||||
if (!strcmp(argv[1], "--debug"))
|
||||
const char *pattern = NULL;
|
||||
|
||||
int i = 1;
|
||||
while (i < argc) {
|
||||
if (!strcmp(argv[i], "--debug"))
|
||||
{
|
||||
debugEnabled(true);
|
||||
}
|
||||
if (!strcmp(argv[1], "--trace"))
|
||||
else
|
||||
if (!strcmp(argv[i], "--trace"))
|
||||
{
|
||||
debugEnabled(true);
|
||||
traceEnabled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
pattern = argv[i];
|
||||
}
|
||||
i++;
|
||||
}
|
||||
onExit([](){});
|
||||
|
||||
test_crc();
|
||||
test_dvparser();
|
||||
test_test();
|
||||
test_devices();
|
||||
test_meters();
|
||||
/*
|
||||
test_linkmodes();*/
|
||||
test_ids();
|
||||
if (test("crc", pattern)) test_crc();
|
||||
if (test("dvparser", pattern)) test_dvparser();
|
||||
if (test("devices", pattern)) test_devices();
|
||||
if (test("device_parsing", pattern)) test_device_parsing();
|
||||
if (test("meters", pattern)) test_meters();
|
||||
// test_linkmodes();
|
||||
if (test("ids", pattern)) test_ids();
|
||||
// test_addresses();
|
||||
test_kdf();
|
||||
test_periods();
|
||||
test_months();
|
||||
test_aes();
|
||||
test_sbc();
|
||||
test_hex();
|
||||
test_translate();
|
||||
test_slip();
|
||||
test_dvs();
|
||||
test_ascii_detection();
|
||||
test_status_join();
|
||||
test_status_sort();
|
||||
test_field_matcher();
|
||||
if (test("kdf", pattern)) test_kdf();
|
||||
if (test("periods", pattern)) test_periods();
|
||||
if (test("months", pattern)) test_months();
|
||||
if (test("aes", pattern)) test_aes();
|
||||
if (test("sbc", pattern)) test_sbc();
|
||||
if (test("hex", pattern)) test_hex();
|
||||
if (test("translate", pattern)) test_translate();
|
||||
if (test("slip", pattern)) test_slip();
|
||||
if (test("dvs", pattern)) test_dvs();
|
||||
if (test("ascii_detection", pattern)) test_ascii_detection();
|
||||
if (test("status_join", pattern)) test_status_join();
|
||||
if (test("status_sort", pattern)) test_status_sort();
|
||||
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;
|
||||
}
|
||||
|
@ -242,7 +265,7 @@ int test_dvparser()
|
|||
return 0;
|
||||
}
|
||||
|
||||
int test_test()
|
||||
int test_devices()
|
||||
{
|
||||
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,
|
||||
"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<assert.h>
|
||||
#include<math.h>
|
||||
#include<string.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -81,6 +82,16 @@ LIST_OF_CONVERSIONS
|
|||
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)
|
||||
{
|
||||
#define X(cname,lcname,hrname,quantity,explanation) if (u == Unit::cname) return Quantity::quantity == q;
|
||||
|
@ -90,6 +101,15 @@ LIST_OF_UNITS
|
|||
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)
|
||||
{
|
||||
if (!isQuantity(u, q))
|
||||
|
@ -117,7 +137,7 @@ LIST_OF_QUANTITIES
|
|||
|
||||
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
|
||||
#undef X
|
||||
|
||||
|
@ -190,3 +210,30 @@ string valueToString(double v, Unit u)
|
|||
if (s.length() == 0) return "0";
|
||||
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);
|
||||
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);
|
||||
const char *toString(Quantity q);
|
||||
bool isQuantity(Unit u, Quantity q);
|
||||
Quantity toQuantity(Unit u);
|
||||
void assertQuantity(Unit u, Quantity q);
|
||||
Unit defaultUnitForQuantity(Quantity q);
|
||||
std::string unitToStringHR(Unit u);
|
||||
|
@ -102,4 +107,6 @@ std::string valueToString(double v, Unit u);
|
|||
|
||||
Unit replaceWithConversionUnit(Unit u, std::vector<Unit> cs);
|
||||
|
||||
bool extractUnit(const std::string &s, std::string *vname, Unit *u);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -453,6 +453,10 @@ bool isDebugEnabled() {
|
|||
return debug_enabled_;
|
||||
}
|
||||
|
||||
bool isTraceEnabled() {
|
||||
return trace_enabled_;
|
||||
}
|
||||
|
||||
bool isLogTelegramsEnabled() {
|
||||
return log_telegrams_enabled_;
|
||||
}
|
||||
|
|
|
@ -115,6 +115,7 @@ bool isInternalTestingEnabled();
|
|||
|
||||
bool isVerboseEnabled();
|
||||
bool isDebugEnabled();
|
||||
bool isTraceEnabled();
|
||||
bool isLogTelegramsEnabled();
|
||||
|
||||
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);
|
||||
va_end(args);
|
||||
|
||||
|
||||
Explanation e(parsed.size(), len, buf, k, u);
|
||||
explanations.push_back(e);
|
||||
parsed.insert(parsed.end(), pos, pos+len);
|
||||
|
|
Ładowanie…
Reference in New Issue