Add formula for calculating new fields based on received fields. Refactor driver ebzwmbe.

pull/637/head
Fredrik Öhrström 2022-10-10 21:43:11 +02:00
rodzic 738ac16f3e
commit b95c1feacb
21 zmienionych plików z 1362 dodań i 256 usunięć

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

562
src/formula.cc 100644
Wyświetl plik

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

45
src/formula.h 100644
Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -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, &current_power_phase1_kw_);
t->addMoreExplanation(offset, " current power phase 1 (%f kwh)", current_power_phase1_kw_);
extractDVdouble(&t->dv_entries, "04A9FF02", &offset, &current_power_phase2_kw_);
t->addMoreExplanation(offset, " current power phase 2 (%f kwh)", current_power_phase2_kw_);
extractDVdouble(&t->dv_entries, "04A9FF03", &offset, &current_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());
}

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -453,6 +453,10 @@ bool isDebugEnabled() {
return debug_enabled_;
}
bool isTraceEnabled() {
return trace_enabled_;
}
bool isLogTelegramsEnabled() {
return log_telegrams_enabled_;
}

Wyświetl plik

@ -115,6 +115,7 @@ bool isInternalTestingEnabled();
bool isVerboseEnabled();
bool isDebugEnabled();
bool isTraceEnabled();
bool isLogTelegramsEnabled();
void debugPayload(std::string intro, std::vector<uchar> &payload);

Wyświetl plik

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