Removed --addconversions=GJ replaced with --calculate_total_gj=total_kwh

pull/708/head
Fredrik Öhrström 2022-11-23 18:42:59 +01:00
rodzic d831d9cc87
commit bccee1a170
22 zmienionych plików z 796 dodań i 714 usunięć

10
CHANGES
Wyświetl plik

@ -1,4 +1,14 @@
Important change! The feature --addconversions=GJ has been removed!!! But there is a replacement!
It was used to add conversions to all energy values into gj, eg you have
total_kwh and add total_gj.
This feature was too broad and the conversion happened too late in the processing code.
You can now achieved the same thing but you have to specify each field you want to convert
using the calculation feature.
E.g. --calculate_total_gj=total_kwh
ATTENTION! The fhkvdataiv driver has been refactored to the new driver format.
The field consumption_at_set_date_17 was wrong and has been renamed to field consumption_at_set_date_8.

Wyświetl plik

@ -138,6 +138,7 @@ PROG_OBJS:=\
$(BUILD)/dvparser.o \
$(BUILD)/formula.o \
$(BUILD)/mbus_rawtty.o \
$(BUILD)/metermanager.o \
$(BUILD)/meters.o \
$(BUILD)/manufacturer_specificities.o \
$(BUILD)/printer.o \

Wyświetl plik

@ -282,17 +282,6 @@ static shared_ptr<Configuration> parseNormalCommandLine(Configuration *c, int ar
i++;
continue;
}
if (!strncmp(argv[i], "--addconversions=", 17)) {
if (strlen(argv[i]) > 16)
{
string s = string(argv[i]+17);
handleConversions(c, s);
} else {
error("You must supply conversion units.\n");
}
i++;
continue;
}
if (!strncmp(argv[i], "--selectfields=", 15)) {
if (strlen(argv[i]) > 15)
{

Wyświetl plik

@ -605,24 +605,6 @@ void handleSeparator(Configuration *c, string s)
}
}
void handleConversions(Configuration *c, string s)
{
char buf[s.length()+1];
strcpy(buf, s.c_str());
char *saveptr {};
const char *tok = strtok_r(buf, ",", &saveptr);
while (tok != NULL)
{
Unit u = toUnit(tok);
if (u == Unit::Unknown)
{
warning("(warning) not a valid conversion unit: %s\n", tok);
}
c->conversions.push_back(u);
tok = strtok_r(NULL, ",", &saveptr);
}
}
void handleLogTimestamps(Configuration *c, string ts)
{
if (ts == "never")
@ -733,7 +715,6 @@ shared_ptr<Configuration> loadConfiguration(string root, ConfigOverrides overrid
else if (p.first == "alarmtimeout") handleAlarmTimeout(c, p.second);
else if (p.first == "alarmexpectedactivity") handleAlarmExpectedActivity(c, p.second);
else if (p.first == "separator") handleSeparator(c, p.second);
else if (p.first == "addconversions") handleConversions(c, p.second);
else if (p.first == "logtimestamps") handleLogTimestamps(c, p.second);
else if (p.first == "selectfields") handleSelectedFields(c, p.second);
else if (p.first == "shell") handleShell(c, p.second);

Wyświetl plik

@ -132,7 +132,6 @@ struct Configuration
string telegram_reader;
// A set of all link modes (union) that the user requests the wmbus dongle to listen to.
bool no_init {};
std::vector<Unit> conversions;
std::vector<std::string> selected_fields;
std::vector<MeterInfo> meters;
std::vector<std::string> extra_constant_fields; // Additional constant fields to always add to json.
@ -146,7 +145,6 @@ struct Configuration
shared_ptr<Configuration> loadConfiguration(string root, ConfigOverrides overrides);
void parseMeterConfig(Configuration *c, vector<char> &buf, string file);
void handleConversions(Configuration *c, string s);
void handleSelectedFields(Configuration *c, string s);
void handleAddedFields(Configuration *c, string s);
bool handleDeviceOrHex(Configuration *c, string devicefilehex);

Wyświetl plik

@ -24,10 +24,6 @@ namespace
Driver(MeterInfo &mi, DriverInfo &di);
void processContent(Telegram *t);
double total_energy_gj_ {};
double curr_energy_gj_ {};
double prev_energy_gj_ {};
};
static bool ok = registerDriver([](DriverInfo&di)
@ -46,24 +42,17 @@ namespace
addNumericField("total",
Quantity::Energy,
PrintProperty::FIELD | PrintProperty::JSON,
"The total energy consumption recorded by this meter.",
SET_FUNC(total_energy_gj_, Unit::GJ),
GET_FUNC(total_energy_gj_, Unit::GJ));
"The total energy consumption recorded by this meter.");
addNumericField("current",
Quantity::Energy,
PrintProperty::FIELD | PrintProperty::JSON,
"Energy consumption so far in this billing period.",
SET_FUNC(curr_energy_gj_, Unit::GJ),
GET_FUNC(curr_energy_gj_, Unit::GJ));
"Energy consumption so far in this billing period.");
addNumericField("previous",
Quantity::Energy,
PrintProperty::FIELD | PrintProperty::JSON,
"Energy consumption in previous billing period.",
SET_FUNC(prev_energy_gj_, Unit::GJ),
GET_FUNC(prev_energy_gj_, Unit::GJ));
"Energy consumption in previous billing period.");
}
void Driver::processContent(Telegram *t)
@ -80,29 +69,29 @@ namespace
uchar prev_lo = content[3];
uchar prev_hi = content[4];
double prev = (256.0*prev_hi+prev_lo)/1000;
double prev_gj = (256.0*prev_hi+prev_lo)/1000;
string prevs;
strprintf(&prevs, "%02x%02x", prev_lo, prev_hi);
int offset = t->parsed.size()+3;
vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, 0, 0, 0, prevs) };
t->explanations.push_back(Explanation(offset, 2, prevs, KindOfData::CONTENT, Understanding::FULL));
t->addMoreExplanation(offset, " energy used in previous billing period (%f GJ)", prev);
t->addMoreExplanation(offset, " energy used in previous billing period (%f GJ)", prev_gj);
uchar curr_lo = content[7];
uchar curr_hi = content[8];
double curr = (256.0*curr_hi+curr_lo)/1000;
double curr_gj = (256.0*curr_hi+curr_lo)/1000;
string currs;
strprintf(&currs, "%02x%02x", curr_lo, curr_hi);
offset = t->parsed.size()+7;
vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, 0, 0, 0, currs) };
t->explanations.push_back(Explanation(offset, 2, currs, KindOfData::CONTENT, Understanding::FULL));
t->addMoreExplanation(offset, " energy used in current billing period (%f GJ)", curr);
t->addMoreExplanation(offset, " energy used in current billing period (%f GJ)", curr_gj);
total_energy_gj_ = prev+curr;
curr_energy_gj_ = curr;
prev_energy_gj_ = prev;
setNumericValue("total", Unit::GJ, curr_gj+prev_gj);
setNumericValue("current", Unit::GJ, curr_gj);
setNumericValue("previous", Unit::GJ, prev_gj);
}
}

Wyświetl plik

@ -407,6 +407,7 @@ struct FieldMatcher
FieldMatcher() : active(false) { }
FieldMatcher(bool act) : active(act) { }
static FieldMatcher build() { return FieldMatcher(true); }
static FieldMatcher noMatcher() { return FieldMatcher(false); }
FieldMatcher &set(DifVifKey k) {
dif_vif_key = k;
match_dif_vif_key = (k.str() != ""); return *this; }

Wyświetl plik

@ -45,8 +45,10 @@ double NumericFormulaMeterField::calculate(SIUnit to_si_unit)
{
if (formula()->meter() == NULL) return std::numeric_limits<double>::quiet_NaN();
Unit field_unit = field_info_->defaultUnit();
double val = formula()->meter()->getNumericValue(field_info_, field_unit);
FieldInfo *fi = formula()->meter()->findFieldInfo(vname_, quantity_);
Unit field_unit = fi->defaultUnit();
double val = formula()->meter()->getNumericValue(fi, field_unit);
const SIUnit& field_si_unit = toSIUnit(field_unit);
@ -862,7 +864,7 @@ void FormulaImplementation::doMeterField(Unit u, FieldInfo *fi)
SIUnit from_si_unit = toSIUnit(fi->defaultUnit());
SIUnit to_si_unit = toSIUnit(u);
assert(from_si_unit.canConvertTo(to_si_unit));
pushOp(new NumericFormulaMeterField(this, u, fi));
pushOp(new NumericFormulaMeterField(this, u, fi->vname(), fi->xuantity()));
}
void FormulaImplementation::doDVEntryField(Unit u, DVEntryCounterType ct)
@ -953,12 +955,17 @@ string NumericFormulaSquareRoot::tree()
string NumericFormulaMeterField::str()
{
return field_info_->vname()+"_"+unitToStringLowerCase(field_info_->defaultUnit());
if (formula()->meter() == NULL) return "<?"+vname_+"?>";
FieldInfo *fi = formula()->meter()->findFieldInfo(vname_, quantity_);
return fi->vname()+"_"+unitToStringLowerCase(fi->defaultUnit());
}
string NumericFormulaMeterField::tree()
{
return "<FIELD "+field_info_->vname()+"_"+unitToStringLowerCase(field_info_->defaultUnit())+"> ";
if (formula()->meter() == NULL) return "<?"+vname_+"?>";
FieldInfo *fi = formula()->meter()->findFieldInfo(vname_, quantity_);
return "<FIELD "+fi->vname()+"_"+unitToStringLowerCase(fi->defaultUnit())+"> ";
}
string NumericFormulaDVEntryField::str()

Wyświetl plik

@ -58,7 +58,8 @@ struct NumericFormulaConstant : public NumericFormula
struct NumericFormulaMeterField : public NumericFormula
{
NumericFormulaMeterField(FormulaImplementation *f, Unit u, FieldInfo *fi) : NumericFormula(f, u), field_info_(fi) {}
NumericFormulaMeterField(FormulaImplementation *f, Unit u, string v, Quantity q)
: NumericFormula(f, u), vname_(v), quantity_(q) {}
double calculate(SIUnit to);
string str();
@ -67,7 +68,8 @@ struct NumericFormulaMeterField : public NumericFormula
private:
FieldInfo *field_info_;
string vname_;
Quantity quantity_;
};
struct NumericFormulaDVEntryField : public NumericFormula

Wyświetl plik

@ -456,7 +456,6 @@ void setup_meters(Configuration *config, MeterManager *manager)
{
for (MeterInfo &m : config->meters)
{
m.conversions = config->conversions;
m.extra_calculated_fields.insert(m.extra_calculated_fields.end(),
config->extra_calculated_fields.begin(),
config->extra_calculated_fields.end());

596
src/metermanager.cc 100644
Wyświetl plik

@ -0,0 +1,596 @@
/*
Copyright (C) 2017-2022 Fredrik Öhrström (gpl-3.0-or-later)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include"bus.h"
#include"config.h"
#include"meters.h"
#include"meter_detection.h"
#include"meters_common_implementation.h"
#include"units.h"
#include"wmbus.h"
#include"wmbus_utils.h"
#include<algorithm>
#include<cmath>
#include<limits>
#include<memory.h>
#include<numeric>
#include<stdexcept>
#include<time.h>
struct MeterManagerImplementation : public virtual MeterManager
{
private:
bool is_daemon_ {};
bool should_analyze_ {};
OutputFormat analyze_format_ {};
string analyze_driver_;
string analyze_key_;
bool analyze_verbose_;
vector<MeterInfo> meter_templates_;
vector<shared_ptr<Meter>> meters_;
vector<function<bool(AboutTelegram&,vector<uchar>)>> telegram_listeners_;
function<void(Telegram*t,Meter*)> on_meter_updated_;
public:
void addMeterTemplate(MeterInfo &mi)
{
meter_templates_.push_back(mi);
}
void addMeter(shared_ptr<Meter> meter)
{
meters_.push_back(meter);
meter->setIndex(meters_.size());
meter->onUpdate(on_meter_updated_);
}
Meter *lastAddedMeter()
{
return meters_.back().get();
}
void removeAllMeters()
{
meters_.clear();
}
void forEachMeter(std::function<void(Meter*)> cb)
{
for (auto &meter : meters_)
{
cb(meter.get());
}
}
bool hasAllMetersReceivedATelegram()
{
if (meters_.size() < meter_templates_.size()) return false;
for (auto &meter : meters_)
{
if (meter->numUpdates() == 0) return false;
}
return true;
}
bool hasMeters()
{
return meters_.size() != 0 || meter_templates_.size() != 0;
}
void warnForUnknownDriver(string name, Telegram *t)
{
int mfct = t->dll_mfct;
int media = t->dll_type;
int version = t->dll_version;
uchar *id_b = t->dll_id_b;
if (t->tpl_id_found)
{
mfct = t->tpl_mfct;
media = t->tpl_type;
version = t->tpl_version;
id_b = t->tpl_id_b;
}
warning("(meter) %s: meter detection could not find driver for "
"id: %02x%02x%02x%02x mfct: (%s) %s (0x%02x) type: %s (0x%02x) ver: 0x%02x\n",
name.c_str(),
id_b[3], id_b[2], id_b[1], id_b[0],
manufacturerFlag(mfct).c_str(),
manufacturer(mfct).c_str(),
mfct,
mediaType(media, mfct).c_str(), media,
version);
warning("(meter) please consider opening an issue at https://github.com/weetmuts/wmbusmeters/\n");
warning("(meter) to add support for this unknown mfct,media,version combination\n");
}
bool handleTelegram(AboutTelegram &about, vector<uchar> input_frame, bool simulated)
{
if (should_analyze_)
{
analyzeTelegram(about, input_frame, simulated);
return true;
}
bool handled = false;
bool exact_id_match = false;
string ids;
for (auto &m : meters_)
{
bool h = m->handleTelegram(about, input_frame, simulated, &ids, &exact_id_match);
if (h) handled = true;
}
// If not properly handled, and there was no exact id match.
// then lets check if there is a template that can create a meter for it.
if (!handled && !exact_id_match)
{
debug("(meter) no meter handled %s checking %d templates.\n", ids.c_str(), meter_templates_.size());
// Not handled, maybe we have a template to create a new meter instance for this telegram?
Telegram t;
t.about = about;
bool ok = t.parseHeader(input_frame);
if (simulated) t.markAsSimulated();
if (ok)
{
ids = t.idsc;
for (auto &mi : meter_templates_)
{
if (MeterCommonImplementation::isTelegramForMeter(&t, NULL, &mi))
{
// We found a match, make a copy of the meter info.
MeterInfo meter_info = mi;
// Overwrite the wildcard pattern with the highest level id.
// The last id in the t.ids is the highest level id.
// For example: a telegram can have dll_id,tpl_id
// This will pick the tpl_id.
// Or a telegram can have a single dll_id,
// then the dll_id will be picked.
vector<string> tmp_ids;
tmp_ids.push_back(t.ids.back());
meter_info.ids = tmp_ids;
meter_info.idsc = t.ids.back();
if (meter_info.driver == MeterDriver::AUTO)
{
// Look up the proper meter driver!
DriverInfo di = pickMeterDriver(&t);
if (di.driver() == MeterDriver::UNKNOWN && di.name().str() == "")
{
if (should_analyze_ == false)
{
// We are not analyzing, so warn here.
warnForUnknownDriver(mi.name, &t);
}
}
else
{
meter_info.driver = di.driver();
meter_info.driver_name = di.name();
}
}
// Now build a meter object with for this exact id.
auto meter = createMeter(&meter_info);
addMeter(meter);
string idsc = toIdsCommaSeparated(t.ids);
verbose("(meter) used meter template %s %s %s to match %s\n",
mi.name.c_str(),
mi.idsc.c_str(),
toString(mi.driver).c_str(),
idsc.c_str());
if (is_daemon_)
{
notice("(wmbusmeters) started meter %d (%s %s %s)\n",
meter->index(),
mi.name.c_str(),
meter_info.idsc.c_str(),
toString(mi.driver).c_str());
}
else
{
verbose("(meter) started meter %d (%s %s %s)\n",
meter->index(),
mi.name.c_str(),
meter_info.idsc.c_str(),
toString(mi.driver).c_str());
}
bool match = false;
bool h = meter->handleTelegram(about, input_frame, simulated, &ids, &match);
if (!match)
{
// Oups, we added a new meter object tailored for this telegram
// but it still did not match! This is probably an error in wmbusmeters!
warning("(meter) newly created meter (%s %s %s) did not match telegram! ",
"Please open an issue at https://github.com/weetmuts/wmbusmeters/\n",
meter->name().c_str(), meter->idsc().c_str(), meter->driverName().str().c_str());
}
else if (!h)
{
// Oups, we added a new meter object tailored for this telegram
// but it still did not handle it! This can happen if the wrong
// decryption key was used.
warning("(meter) newly created meter (%s %s %s) did not handle telegram!\n",
meter->name().c_str(), meter->idsc().c_str(), meter->driverName().str().c_str());
}
else
{
handled = true;
}
}
}
}
}
for (auto f : telegram_listeners_)
{
f(about, input_frame);
}
if (isVerboseEnabled() && !handled)
{
verbose("(wmbus) telegram from %s ignored by all configured meters!\n", ids.c_str());
}
return handled;
}
void onTelegram(function<bool(AboutTelegram &about, vector<uchar>)> cb)
{
telegram_listeners_.push_back(cb);
}
void whenMeterUpdated(std::function<void(Telegram*t,Meter*)> cb)
{
on_meter_updated_ = cb;
}
void pollMeters(shared_ptr<BusManager> bus)
{
for (auto &m : meters_)
{
m->poll(bus);
}
}
void analyzeEnabled(bool b, OutputFormat f, string force_driver, string key, bool verbose)
{
should_analyze_ = b;
analyze_format_ = f;
if (force_driver != "auto")
{
analyze_driver_ = force_driver;
}
analyze_key_ = key;
analyze_verbose_ = verbose;
}
string findBestOldStyleDriver(MeterInfo &mi,
int *best_length,
int *best_understood,
Telegram &t,
AboutTelegram &about,
vector<uchar> &input_frame,
bool simulated,
string force)
{
vector<MeterDriver> old_drivers;
#define X(mname,linkmode,info,type,cname) old_drivers.push_back(MeterDriver::type);
LIST_OF_METERS
#undef X
string best_driver = "";
for (MeterDriver odr : old_drivers)
{
if (odr == MeterDriver::AUTO) continue;
if (odr == MeterDriver::UNKNOWN) continue;
string driver_name = toString(odr);
if (force != "")
{
if (driver_name != force) continue;
return driver_name;
}
if (force == "" &&
!isMeterDriverReasonableForMedia(odr, "", t.dll_type) &&
!isMeterDriverReasonableForMedia(odr, "", t.tpl_type))
{
// Sanity check, skip this driver since it is not relevant for this media.
continue;
}
debug("Testing old style driver %s...\n", driver_name.c_str());
mi.driver = odr;
mi.driver_name = DriverName("");
auto meter = createMeter(&mi);
bool match = false;
string id;
bool h = meter->handleTelegram(about, input_frame, simulated, &id, &match, &t);
if (!match)
{
debug("no match!\n");
}
else if (!h)
{
// Oups, we added a new meter object tailored for this telegram
// but it still did not handle it! This can happen if the wrong
// decryption key was used. But it is ok if analyzing....
debug("Newly created meter (%s %s %s) did not handle telegram!\n",
meter->name().c_str(), meter->idsc().c_str(), meter->driverName().str().c_str());
}
else
{
int l = 0;
int u = 0;
t.analyzeParse(OutputFormat::NONE, &l, &u);
if (analyze_verbose_ && force == "") printf("(verbose) old %02d/%02d %s\n", u, l, driver_name.c_str());
if (u > *best_understood)
{
*best_understood = u;
*best_length = l;
best_driver = driver_name;
if (analyze_verbose_ && force == "") printf("(verbose) old best so far: %s %02d/%02d\n", best_driver.c_str(), u, l);
}
}
}
return best_driver;
}
string findBestNewStyleDriver(MeterInfo &mi,
int *best_length,
int *best_understood,
Telegram &t,
AboutTelegram &about,
vector<uchar> &input_frame,
bool simulated,
string only)
{
string best_driver = "";
for (DriverInfo *ndr : allDrivers())
{
string driver_name = toString(*ndr);
if (only != "")
{
if (driver_name != only) continue;
return driver_name;
}
if (only == "" &&
!isMeterDriverReasonableForMedia(MeterDriver::AUTO, driver_name, t.dll_type) &&
!isMeterDriverReasonableForMedia(MeterDriver::AUTO, driver_name, t.tpl_type))
{
// Sanity check, skip this driver since it is not relevant for this media.
continue;
}
debug("Testing new style driver %s...\n", driver_name.c_str());
mi.driver = MeterDriver::UNKNOWN;
mi.driver_name = driver_name;
auto meter = createMeter(&mi);
bool match = false;
string id;
bool h = meter->handleTelegram(about, input_frame, simulated, &id, &match, &t);
if (!match)
{
debug("no match!\n");
}
else if (!h)
{
// Oups, we added a new meter object tailored for this telegram
// but it still did not handle it! This can happen if the wrong
// decryption key was used. But it is ok if analyzing....
debug("Newly created meter (%s %s %s) did not handle telegram!\n",
meter->name().c_str(), meter->idsc().c_str(), meter->driverName().str().c_str());
}
else
{
int l = 0;
int u = 0;
t.analyzeParse(OutputFormat::NONE, &l, &u);
if (analyze_verbose_ && only == "") printf("(verbose) new %02d/%02d %s\n", u, l, driver_name.c_str());
if (u > *best_understood)
{
*best_understood = u;
*best_length = l;
best_driver = ndr->name().str();
if (analyze_verbose_ && only == "") printf("(verbose) new best so far: %s %02d/%02d\n", best_driver.c_str(), u, l);
}
}
}
return best_driver;
}
void analyzeTelegram(AboutTelegram &about, vector<uchar> &input_frame, bool simulated)
{
Telegram t;
t.about = about;
bool ok = t.parseHeader(input_frame);
if (simulated) t.markAsSimulated();
t.markAsBeingAnalyzed();
if (!ok)
{
printf("Could not even analyze header, giving up.\n");
return;
}
if (meter_templates_.size() > 0)
{
error("You cannot specify a meter quadruple when analyzing.\n"
"Instead use --analyze=<format>:<driver>:<key>\n"
"where <formt> <driver> <key> are all optional.\n"
"E.g. --analyze=terminal:multical21:001122334455667788001122334455667788\n"
" --analyze=001122334455667788001122334455667788\n"
" --analyze\n");
}
// Overwrite the id with the id from the telegram to be analyzed.
MeterInfo mi;
mi.key = analyze_key_;
mi.ids.clear();
mi.ids.push_back(t.ids.back());
mi.idsc = t.ids.back();
// This will be the driver that will actually decode and print with.
string using_driver = "";
int using_length = 0;
int using_understood = 0;
// Driver that understands most of the telegram content.
string best_driver = "";
int best_length = 0;
int best_understood = 0;
int old_best_length = 0;
int old_best_understood = 0;
string best_old_driver = findBestOldStyleDriver(mi, &old_best_length, &old_best_understood, t, about, input_frame, simulated, "");
int new_best_length = 0;
int new_best_understood = 0;
string best_new_driver = findBestNewStyleDriver(mi, &new_best_length, &new_best_understood, t, about, input_frame, simulated, "");
mi.driver = MeterDriver::UNKNOWN;
mi.driver_name = DriverName("");
// Use the existing mapping from mfct/media/version to driver.
DriverInfo auto_di = pickMeterDriver(&t);
string auto_driver = auto_di.name().str();
if (auto_driver == "")
{
auto_driver = toString(auto_di.driver());
if (auto_driver == "unknown")
{
auto_driver = "";
}
}
// Will be non-empty if an explicit driver has been selected.
string force_driver = analyze_driver_;
int force_length = 0;
int force_understood = 0;
// If an auto driver is found and no other driver has been forced, use the auto driver.
if (force_driver == "" && auto_driver != "")
{
force_driver = auto_driver;
}
if (force_driver != "")
{
using_driver = findBestOldStyleDriver(mi, &force_length, &force_understood, t, about, input_frame, simulated,
force_driver);
if (using_driver != "")
{
mi.driver = toMeterDriver(using_driver);
mi.driver_name = DriverName("");
using_driver += "(driver should be upgraded)";
}
else
{
using_driver = findBestNewStyleDriver(mi, &force_length, &force_understood, t, about, input_frame, simulated,
force_driver);
mi.driver_name = using_driver;
mi.driver = MeterDriver::UNKNOWN;
}
using_length = force_length;
using_understood = force_understood;
}
if (old_best_understood > new_best_understood)
{
best_length = old_best_length;
best_understood = old_best_understood;
best_driver = best_old_driver+"(driver should be upgraded)";
if (using_driver == "")
{
mi.driver = toMeterDriver(best_old_driver);
mi.driver_name = DriverName("");
using_driver = best_driver;
using_length = best_length;
using_understood = best_understood;
}
}
else if (new_best_understood >= old_best_understood)
{
best_length = new_best_length;
best_understood = new_best_understood;
best_driver = best_new_driver;
if (using_driver == "")
{
mi.driver_name = best_new_driver;
mi.driver = MeterDriver::UNKNOWN;
using_driver = best_new_driver;
using_length = best_length;
using_understood = best_understood;
}
}
auto meter = createMeter(&mi);
bool match = false;
string id;
meter->handleTelegram(about, input_frame, simulated, &id, &match, &t);
int u = 0;
int l = 0;
string output = t.analyzeParse(analyze_format_, &u, &l);
string hr, fields, json;
vector<string> envs, more_json, selected_fields;
meter->printMeter(&t, &hr, &fields, '\t', &json,
&envs, &more_json, &selected_fields, true);
if (auto_driver == "")
{
auto_driver = "not found!";
}
printf("Auto driver : %s\n", auto_driver.c_str());
printf("Best driver : %s %02d/%02d\n", best_driver.c_str(), best_understood, best_length);
printf("Using driver : %s %02d/%02d\n", using_driver.c_str(), using_understood, using_length);
printf("%s\n", output.c_str());
printf("%s\n", json.c_str());
}
MeterManagerImplementation(bool daemon) : is_daemon_(daemon) {}
~MeterManagerImplementation() {}
};
shared_ptr<MeterManager> createMeterManager(bool daemon)
{
return shared_ptr<MeterManager>(new MeterManagerImplementation(daemon));
}

Wyświetl plik

@ -164,567 +164,6 @@ bool lookupDriverInfo(const string& driver, DriverInfo *out_di)
return true;
}
struct MeterManagerImplementation : public virtual MeterManager
{
private:
bool is_daemon_ {};
bool should_analyze_ {};
OutputFormat analyze_format_ {};
string analyze_driver_;
string analyze_key_;
bool analyze_verbose_;
vector<MeterInfo> meter_templates_;
vector<shared_ptr<Meter>> meters_;
vector<function<bool(AboutTelegram&,vector<uchar>)>> telegram_listeners_;
function<void(Telegram*t,Meter*)> on_meter_updated_;
public:
void addMeterTemplate(MeterInfo &mi)
{
meter_templates_.push_back(mi);
}
void addMeter(shared_ptr<Meter> meter)
{
meters_.push_back(meter);
meter->setIndex(meters_.size());
meter->onUpdate(on_meter_updated_);
}
Meter *lastAddedMeter()
{
return meters_.back().get();
}
void removeAllMeters()
{
meters_.clear();
}
void forEachMeter(std::function<void(Meter*)> cb)
{
for (auto &meter : meters_)
{
cb(meter.get());
}
}
bool hasAllMetersReceivedATelegram()
{
if (meters_.size() < meter_templates_.size()) return false;
for (auto &meter : meters_)
{
if (meter->numUpdates() == 0) return false;
}
return true;
}
bool hasMeters()
{
return meters_.size() != 0 || meter_templates_.size() != 0;
}
void warnForUnknownDriver(string name, Telegram *t)
{
int mfct = t->dll_mfct;
int media = t->dll_type;
int version = t->dll_version;
uchar *id_b = t->dll_id_b;
if (t->tpl_id_found)
{
mfct = t->tpl_mfct;
media = t->tpl_type;
version = t->tpl_version;
id_b = t->tpl_id_b;
}
warning("(meter) %s: meter detection could not find driver for "
"id: %02x%02x%02x%02x mfct: (%s) %s (0x%02x) type: %s (0x%02x) ver: 0x%02x\n",
name.c_str(),
id_b[3], id_b[2], id_b[1], id_b[0],
manufacturerFlag(mfct).c_str(),
manufacturer(mfct).c_str(),
mfct,
mediaType(media, mfct).c_str(), media,
version);
warning("(meter) please consider opening an issue at https://github.com/weetmuts/wmbusmeters/\n");
warning("(meter) to add support for this unknown mfct,media,version combination\n");
}
bool handleTelegram(AboutTelegram &about, vector<uchar> input_frame, bool simulated)
{
if (should_analyze_)
{
analyzeTelegram(about, input_frame, simulated);
return true;
}
bool handled = false;
bool exact_id_match = false;
string ids;
for (auto &m : meters_)
{
bool h = m->handleTelegram(about, input_frame, simulated, &ids, &exact_id_match);
if (h) handled = true;
}
// If not properly handled, and there was no exact id match.
// then lets check if there is a template that can create a meter for it.
if (!handled && !exact_id_match)
{
debug("(meter) no meter handled %s checking %d templates.\n", ids.c_str(), meter_templates_.size());
// Not handled, maybe we have a template to create a new meter instance for this telegram?
Telegram t;
t.about = about;
bool ok = t.parseHeader(input_frame);
if (simulated) t.markAsSimulated();
if (ok)
{
ids = t.idsc;
for (auto &mi : meter_templates_)
{
if (MeterCommonImplementation::isTelegramForMeter(&t, NULL, &mi))
{
// We found a match, make a copy of the meter info.
MeterInfo meter_info = mi;
// Overwrite the wildcard pattern with the highest level id.
// The last id in the t.ids is the highest level id.
// For example: a telegram can have dll_id,tpl_id
// This will pick the tpl_id.
// Or a telegram can have a single dll_id,
// then the dll_id will be picked.
vector<string> tmp_ids;
tmp_ids.push_back(t.ids.back());
meter_info.ids = tmp_ids;
meter_info.idsc = t.ids.back();
if (meter_info.driver == MeterDriver::AUTO)
{
// Look up the proper meter driver!
DriverInfo di = pickMeterDriver(&t);
if (di.driver() == MeterDriver::UNKNOWN && di.name().str() == "")
{
if (should_analyze_ == false)
{
// We are not analyzing, so warn here.
warnForUnknownDriver(mi.name, &t);
}
}
else
{
meter_info.driver = di.driver();
meter_info.driver_name = di.name();
}
}
// Now build a meter object with for this exact id.
auto meter = createMeter(&meter_info);
addMeter(meter);
string idsc = toIdsCommaSeparated(t.ids);
verbose("(meter) used meter template %s %s %s to match %s\n",
mi.name.c_str(),
mi.idsc.c_str(),
toString(mi.driver).c_str(),
idsc.c_str());
if (is_daemon_)
{
notice("(wmbusmeters) started meter %d (%s %s %s)\n",
meter->index(),
mi.name.c_str(),
meter_info.idsc.c_str(),
toString(mi.driver).c_str());
}
else
{
verbose("(meter) started meter %d (%s %s %s)\n",
meter->index(),
mi.name.c_str(),
meter_info.idsc.c_str(),
toString(mi.driver).c_str());
}
bool match = false;
bool h = meter->handleTelegram(about, input_frame, simulated, &ids, &match);
if (!match)
{
// Oups, we added a new meter object tailored for this telegram
// but it still did not match! This is probably an error in wmbusmeters!
warning("(meter) newly created meter (%s %s %s) did not match telegram! ",
"Please open an issue at https://github.com/weetmuts/wmbusmeters/\n",
meter->name().c_str(), meter->idsc().c_str(), meter->driverName().str().c_str());
}
else if (!h)
{
// Oups, we added a new meter object tailored for this telegram
// but it still did not handle it! This can happen if the wrong
// decryption key was used.
warning("(meter) newly created meter (%s %s %s) did not handle telegram!\n",
meter->name().c_str(), meter->idsc().c_str(), meter->driverName().str().c_str());
}
else
{
handled = true;
}
}
}
}
}
for (auto f : telegram_listeners_)
{
f(about, input_frame);
}
if (isVerboseEnabled() && !handled)
{
verbose("(wmbus) telegram from %s ignored by all configured meters!\n", ids.c_str());
}
return handled;
}
void onTelegram(function<bool(AboutTelegram &about, vector<uchar>)> cb)
{
telegram_listeners_.push_back(cb);
}
void whenMeterUpdated(std::function<void(Telegram*t,Meter*)> cb)
{
on_meter_updated_ = cb;
}
void pollMeters(shared_ptr<BusManager> bus)
{
for (auto &m : meters_)
{
m->poll(bus);
}
}
void analyzeEnabled(bool b, OutputFormat f, string force_driver, string key, bool verbose)
{
should_analyze_ = b;
analyze_format_ = f;
if (force_driver != "auto")
{
analyze_driver_ = force_driver;
}
analyze_key_ = key;
analyze_verbose_ = verbose;
}
string findBestOldStyleDriver(MeterInfo &mi,
int *best_length,
int *best_understood,
Telegram &t,
AboutTelegram &about,
vector<uchar> &input_frame,
bool simulated,
string force)
{
vector<MeterDriver> old_drivers;
#define X(mname,linkmode,info,type,cname) old_drivers.push_back(MeterDriver::type);
LIST_OF_METERS
#undef X
string best_driver = "";
for (MeterDriver odr : old_drivers)
{
if (odr == MeterDriver::AUTO) continue;
if (odr == MeterDriver::UNKNOWN) continue;
string driver_name = toString(odr);
if (force != "")
{
if (driver_name != force) continue;
return driver_name;
}
if (force == "" &&
!isMeterDriverReasonableForMedia(odr, "", t.dll_type) &&
!isMeterDriverReasonableForMedia(odr, "", t.tpl_type))
{
// Sanity check, skip this driver since it is not relevant for this media.
continue;
}
debug("Testing old style driver %s...\n", driver_name.c_str());
mi.driver = odr;
mi.driver_name = DriverName("");
auto meter = createMeter(&mi);
bool match = false;
string id;
bool h = meter->handleTelegram(about, input_frame, simulated, &id, &match, &t);
if (!match)
{
debug("no match!\n");
}
else if (!h)
{
// Oups, we added a new meter object tailored for this telegram
// but it still did not handle it! This can happen if the wrong
// decryption key was used. But it is ok if analyzing....
debug("Newly created meter (%s %s %s) did not handle telegram!\n",
meter->name().c_str(), meter->idsc().c_str(), meter->driverName().str().c_str());
}
else
{
int l = 0;
int u = 0;
t.analyzeParse(OutputFormat::NONE, &l, &u);
if (analyze_verbose_ && force == "") printf("(verbose) old %02d/%02d %s\n", u, l, driver_name.c_str());
if (u > *best_understood)
{
*best_understood = u;
*best_length = l;
best_driver = driver_name;
if (analyze_verbose_ && force == "") printf("(verbose) old best so far: %s %02d/%02d\n", best_driver.c_str(), u, l);
}
}
}
return best_driver;
}
string findBestNewStyleDriver(MeterInfo &mi,
int *best_length,
int *best_understood,
Telegram &t,
AboutTelegram &about,
vector<uchar> &input_frame,
bool simulated,
string only)
{
string best_driver = "";
for (DriverInfo *ndr : allDrivers())
{
string driver_name = toString(*ndr);
if (only != "")
{
if (driver_name != only) continue;
return driver_name;
}
if (only == "" &&
!isMeterDriverReasonableForMedia(MeterDriver::AUTO, driver_name, t.dll_type) &&
!isMeterDriverReasonableForMedia(MeterDriver::AUTO, driver_name, t.tpl_type))
{
// Sanity check, skip this driver since it is not relevant for this media.
continue;
}
debug("Testing new style driver %s...\n", driver_name.c_str());
mi.driver = MeterDriver::UNKNOWN;
mi.driver_name = driver_name;
auto meter = createMeter(&mi);
bool match = false;
string id;
bool h = meter->handleTelegram(about, input_frame, simulated, &id, &match, &t);
if (!match)
{
debug("no match!\n");
}
else if (!h)
{
// Oups, we added a new meter object tailored for this telegram
// but it still did not handle it! This can happen if the wrong
// decryption key was used. But it is ok if analyzing....
debug("Newly created meter (%s %s %s) did not handle telegram!\n",
meter->name().c_str(), meter->idsc().c_str(), meter->driverName().str().c_str());
}
else
{
int l = 0;
int u = 0;
t.analyzeParse(OutputFormat::NONE, &l, &u);
if (analyze_verbose_ && only == "") printf("(verbose) new %02d/%02d %s\n", u, l, driver_name.c_str());
if (u > *best_understood)
{
*best_understood = u;
*best_length = l;
best_driver = ndr->name().str();
if (analyze_verbose_ && only == "") printf("(verbose) new best so far: %s %02d/%02d\n", best_driver.c_str(), u, l);
}
}
}
return best_driver;
}
void analyzeTelegram(AboutTelegram &about, vector<uchar> &input_frame, bool simulated)
{
Telegram t;
t.about = about;
bool ok = t.parseHeader(input_frame);
if (simulated) t.markAsSimulated();
t.markAsBeingAnalyzed();
if (!ok)
{
printf("Could not even analyze header, giving up.\n");
return;
}
if (meter_templates_.size() > 0)
{
error("You cannot specify a meter quadruple when analyzing.\n"
"Instead use --analyze=<format>:<driver>:<key>\n"
"where <formt> <driver> <key> are all optional.\n"
"E.g. --analyze=terminal:multical21:001122334455667788001122334455667788\n"
" --analyze=001122334455667788001122334455667788\n"
" --analyze\n");
}
// Overwrite the id with the id from the telegram to be analyzed.
MeterInfo mi;
mi.key = analyze_key_;
mi.ids.clear();
mi.ids.push_back(t.ids.back());
mi.idsc = t.ids.back();
// This will be the driver that will actually decode and print with.
string using_driver = "";
int using_length = 0;
int using_understood = 0;
// Driver that understands most of the telegram content.
string best_driver = "";
int best_length = 0;
int best_understood = 0;
int old_best_length = 0;
int old_best_understood = 0;
string best_old_driver = findBestOldStyleDriver(mi, &old_best_length, &old_best_understood, t, about, input_frame, simulated, "");
int new_best_length = 0;
int new_best_understood = 0;
string best_new_driver = findBestNewStyleDriver(mi, &new_best_length, &new_best_understood, t, about, input_frame, simulated, "");
mi.driver = MeterDriver::UNKNOWN;
mi.driver_name = DriverName("");
// Use the existing mapping from mfct/media/version to driver.
DriverInfo auto_di = pickMeterDriver(&t);
string auto_driver = auto_di.name().str();
if (auto_driver == "")
{
auto_driver = toString(auto_di.driver());
if (auto_driver == "unknown")
{
auto_driver = "";
}
}
// Will be non-empty if an explicit driver has been selected.
string force_driver = analyze_driver_;
int force_length = 0;
int force_understood = 0;
// If an auto driver is found and no other driver has been forced, use the auto driver.
if (force_driver == "" && auto_driver != "")
{
force_driver = auto_driver;
}
if (force_driver != "")
{
using_driver = findBestOldStyleDriver(mi, &force_length, &force_understood, t, about, input_frame, simulated,
force_driver);
if (using_driver != "")
{
mi.driver = toMeterDriver(using_driver);
mi.driver_name = DriverName("");
using_driver += "(driver should be upgraded)";
}
else
{
using_driver = findBestNewStyleDriver(mi, &force_length, &force_understood, t, about, input_frame, simulated,
force_driver);
mi.driver_name = using_driver;
mi.driver = MeterDriver::UNKNOWN;
}
using_length = force_length;
using_understood = force_understood;
}
if (old_best_understood > new_best_understood)
{
best_length = old_best_length;
best_understood = old_best_understood;
best_driver = best_old_driver+"(driver should be upgraded)";
if (using_driver == "")
{
mi.driver = toMeterDriver(best_old_driver);
mi.driver_name = DriverName("");
using_driver = best_driver;
using_length = best_length;
using_understood = best_understood;
}
}
else if (new_best_understood >= old_best_understood)
{
best_length = new_best_length;
best_understood = new_best_understood;
best_driver = best_new_driver;
if (using_driver == "")
{
mi.driver_name = best_new_driver;
mi.driver = MeterDriver::UNKNOWN;
using_driver = best_new_driver;
using_length = best_length;
using_understood = best_understood;
}
}
auto meter = createMeter(&mi);
bool match = false;
string id;
meter->handleTelegram(about, input_frame, simulated, &id, &match, &t);
int u = 0;
int l = 0;
string output = t.analyzeParse(analyze_format_, &u, &l);
string hr, fields, json;
vector<string> envs, more_json, selected_fields;
meter->printMeter(&t, &hr, &fields, '\t', &json,
&envs, &more_json, &selected_fields, true);
if (auto_driver == "")
{
auto_driver = "not found!";
}
printf("Auto driver : %s\n", auto_driver.c_str());
printf("Best driver : %s %02d/%02d\n", best_driver.c_str(), best_understood, best_length);
printf("Using driver : %s %02d/%02d\n", using_driver.c_str(), using_understood, using_length);
printf("%s\n", output.c_str());
printf("%s\n", json.c_str());
}
MeterManagerImplementation(bool daemon) : is_daemon_(daemon) {}
~MeterManagerImplementation() {}
};
shared_ptr<MeterManager> createMeterManager(bool daemon)
{
return shared_ptr<MeterManager>(new MeterManagerImplementation(daemon));
}
MeterCommonImplementation::MeterCommonImplementation(MeterInfo &mi,
string driver) :
@ -780,14 +219,6 @@ MeterCommonImplementation::MeterCommonImplementation(MeterInfo &mi,
force_mfct_index_ = di.forceMfctIndex();
}
void MeterCommonImplementation::addConversions(std::vector<Unit> cs)
{
for (Unit c : cs)
{
conversions_.push_back(c);
}
}
void MeterCommonImplementation::addShell(string cmdline)
{
shell_cmdlines_.push_back(cmdline);
@ -874,7 +305,7 @@ void MeterCommonImplementation::addMfctTPLStatusBits(Translate::Lookup lookup)
void MeterCommonImplementation::addPrint(string vname, Quantity vquantity,
function<double(Unit)> getValueFunc, string help, PrintProperties pprops)
{
field_infos_.push_back(
field_infos_.emplace_back(
FieldInfo(field_infos_.size(),
vname,
vquantity,
@ -895,7 +326,7 @@ void MeterCommonImplementation::addPrint(string vname, Quantity vquantity,
void MeterCommonImplementation::addPrint(string vname, Quantity vquantity, Unit unit,
function<double(Unit)> getValueFunc, string help, PrintProperties pprops)
{
field_infos_.push_back(
field_infos_.emplace_back(
FieldInfo(field_infos_.size(),
vname,
vquantity,
@ -917,7 +348,7 @@ void MeterCommonImplementation::addPrint(string vname, Quantity vquantity,
function<string()> getValueFunc,
string help, PrintProperties pprops)
{
field_infos_.push_back(
field_infos_.emplace_back(
FieldInfo(field_infos_.size(),
vname,
vquantity,
@ -950,7 +381,7 @@ void MeterCommonImplementation::addNumericFieldWithExtractor(
function<void(Unit,double)> setValueFunc,
function<double(Unit)> getValueFunc)
{
field_infos_.push_back(
field_infos_.emplace_back(
FieldInfo(field_infos_.size(),
vname,
vquantity,
@ -976,7 +407,7 @@ void MeterCommonImplementation::addNumericFieldWithExtractor(string vname,
FieldMatcher matcher,
Unit use_unit)
{
field_infos_.push_back(
field_infos_.emplace_back(
FieldInfo(field_infos_.size(),
vname,
vquantity,
@ -1020,7 +451,7 @@ void MeterCommonImplementation::addNumericFieldWithCalculator(string vname,
vquantity,
use_unit == Unit::Unknown ? defaultUnitForQuantity(vquantity) : use_unit,
VifScaling::Auto,
FieldMatcher::build(),
FieldMatcher::noMatcher(),
help,
print_properties,
NULL,
@ -1040,7 +471,7 @@ void MeterCommonImplementation::addNumericField(
function<void(Unit,double)> setValueFunc,
function<double(Unit)> getValueFunc)
{
field_infos_.push_back(
field_infos_.emplace_back(
FieldInfo(field_infos_.size(),
vname,
vquantity,
@ -1058,6 +489,31 @@ void MeterCommonImplementation::addNumericField(
));
}
void MeterCommonImplementation::addNumericField(
string vname,
Quantity vquantity,
PrintProperties print_properties,
string help,
Unit use_unit)
{
field_infos_.emplace_back(
FieldInfo(field_infos_.size(),
vname,
vquantity,
use_unit == Unit::Unknown ? defaultUnitForQuantity(vquantity) : use_unit,
VifScaling::None,
FieldMatcher::noMatcher(),
help,
print_properties,
NULL, // getValueFunc,
NULL,
NULL, // setValueFunc
NULL,
NoLookup, /* Lookup table */
NULL /* Formula */
));
}
void MeterCommonImplementation::addStringFieldWithExtractor(
string vname,
Quantity vquantity,
@ -1072,7 +528,7 @@ void MeterCommonImplementation::addStringFieldWithExtractor(
function<void(string)> setValueFunc,
function<string()> getValueFunc)
{
field_infos_.push_back(
field_infos_.emplace_back(
FieldInfo(field_infos_.size(),
vname,
vquantity,
@ -1095,7 +551,7 @@ void MeterCommonImplementation::addStringFieldWithExtractor(string vname,
PrintProperties print_properties,
FieldMatcher matcher)
{
field_infos_.push_back(
field_infos_.emplace_back(
FieldInfo(field_infos_.size(),
vname,
Quantity::Text,
@ -1128,7 +584,7 @@ void MeterCommonImplementation::addStringFieldWithExtractorAndLookup(
function<string()> getValueFunc,
Translate::Lookup lookup)
{
field_infos_.push_back(
field_infos_.emplace_back(
FieldInfo(field_infos_.size(),
vname,
vquantity,
@ -1152,7 +608,7 @@ void MeterCommonImplementation::addStringFieldWithExtractorAndLookup(string vnam
FieldMatcher matcher,
Translate::Lookup lookup)
{
field_infos_.push_back(
field_infos_.emplace_back(
FieldInfo(field_infos_.size(),
vname,
Quantity::Text,
@ -1174,7 +630,7 @@ void MeterCommonImplementation::addStringField(string vname,
string help,
PrintProperties print_properties)
{
field_infos_.push_back(
field_infos_.emplace_back(
FieldInfo(field_infos_.size(),
vname,
Quantity::Text,
@ -1589,7 +1045,7 @@ void MeterCommonImplementation::triggerUpdate(Telegram *t)
t->handled = true;
}
string concatAllFields(Meter *m, Telegram *t, char c, vector<FieldInfo> &fields, vector<Unit> &cs, bool hr,
string concatAllFields(Meter *m, Telegram *t, char c, vector<FieldInfo> &fields, bool hr,
vector<string> *extra_constant_fields)
{
string s;
@ -1613,7 +1069,7 @@ string concatAllFields(Meter *m, Telegram *t, char c, vector<FieldInfo> &fields,
}
else
{
Unit u = replaceWithConversionUnit(fi.defaultUnit(), cs);
Unit u = fi.defaultUnit();
double v = m->getNumericValue(&fi, u);
if (hr) {
s += valueToString(v, u);
@ -1691,7 +1147,7 @@ bool checkCommonField(string *buf, string field, Meter *m, Telegram *t, char c,
// Is the desired field one of the meter printable fields?
bool checkPrintableField(string *buf, string field, Meter *m, Telegram *t, char c,
vector<FieldInfo> &fields, vector<Unit> &cs, bool human_readable)
vector<FieldInfo> &fields, bool human_readable)
{
for (FieldInfo &fi : fields)
{
@ -1724,7 +1180,7 @@ bool checkPrintableField(string *buf, string field, Meter *m, Telegram *t, char
else
{
// Added conversion unit.
Unit u = replaceWithConversionUnit(fi.defaultUnit(), cs);
Unit u = fi.defaultUnit();
if (u != fi.defaultUnit())
{
string unit = unitToStringLowerCase(u);
@ -1762,7 +1218,7 @@ bool checkConstantField(string *buf, string field, char c, vector<string> *extra
return false;
}
string concatFields(Meter *m, Telegram *t, char c, vector<FieldInfo> &prints, vector<Unit> &cs, bool human_readable,
string concatFields(Meter *m, Telegram *t, char c, vector<FieldInfo> &prints, bool human_readable,
vector<string> *selected_fields, vector<string> *extra_constant_fields)
{
if (selected_fields == NULL || selected_fields->size() == 0)
@ -1774,7 +1230,7 @@ string concatFields(Meter *m, Telegram *t, char c, vector<FieldInfo> &prints, ve
}
else
{
return concatAllFields(m, t, c, prints, cs, human_readable, extra_constant_fields);
return concatAllFields(m, t, c, prints, human_readable, extra_constant_fields);
}
}
string buf = "";
@ -1784,7 +1240,7 @@ string concatFields(Meter *m, Telegram *t, char c, vector<FieldInfo> &prints, ve
bool handled = checkCommonField(&buf, field, m, t, c, human_readable);
if (handled) continue;
handled = checkPrintableField(&buf, field, m, t, c, prints, cs, human_readable);
handled = checkPrintableField(&buf, field, m, t, c, prints, human_readable);
if (handled) continue;
handled = checkConstantField(&buf, field, c, extra_constant_fields);
@ -1851,10 +1307,10 @@ 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);
// Invoke any calculators working on the extracted fields.
processFieldCalculators();
// All done....
if (isDebugEnabled())
@ -1891,7 +1347,16 @@ void MeterCommonImplementation::processFieldExtractors(Telegram *t)
{
int current_match_nr = 0;
// This field_info has not been matched to a dv_entry before!
if (!fi.hasMatcher())
{
// This field_info has not been matched to a dv_entry before!
debug("(meters) skipping field without matcher %s(%s)[%d]...\n",
fi.vname().c_str(),
toString(fi.xuantity()),
fi.index());
continue;
}
debug("(meters) trying field info %s(%s)[%d]...\n",
fi.vname().c_str(),
toString(fi.xuantity()),
@ -1995,6 +1460,19 @@ void MeterCommonImplementation::setNumericValue(FieldInfo *fi, Unit u, double v)
numeric_values_[pair<string,Quantity>(field_name_no_unit,fi->xuantity())] = NumericField(u, v, fi);
}
void MeterCommonImplementation::setNumericValue(string vname, Unit u, double v)
{
Quantity q = toQuantity(u);
FieldInfo *fi = findFieldInfo(vname, q);
if (fi == NULL)
{
warning("(meter) cannot set numeric value %g %s for non-existant field \"%s\" %s\n", v, unitToStringLowerCase(u).c_str(), vname.c_str(), toString(q));
return;
}
setNumericValue(fi, u, v);
}
bool MeterCommonImplementation::hasValue(FieldInfo *fi)
{
return hasStringValue(fi) || hasNumericValue(fi);
@ -2114,6 +1592,34 @@ string MeterCommonImplementation::renderJsonOnlyDefaultUnit(string vname, Quanti
return fi->renderJsonOnlyDefaultUnit(this);
}
string MeterCommonImplementation::debugValues()
{
string s;
for (auto &p : numeric_values_)
{
string vname = p.first.first;
Quantity q = p.first.second;
NumericField& nf = p.second;
s += tostrprintf("%s %s = %g\n", toString(q), vname.c_str(), nf.value);
}
for (auto &p : string_values_)
{
string vname = p.first;
StringField& nf = p.second;
s += tostrprintf("%s = \"%s\"\n", vname.c_str(), nf.value.c_str());
}
return s;
}
FieldInfo::~FieldInfo()
{
}
FieldInfo::FieldInfo(int index,
string vname,
Quantity xuantity,
@ -2142,10 +1648,10 @@ FieldInfo::FieldInfo(int index,
set_numeric_value_override_(set_numeric_value_override),
set_string_value_override_(set_string_value_override),
lookup_(lookup),
formula_(formula)
formula_(formula),
field_name_(newStringInterpolator()),
valid_field_name_(field_name_->parse(vname))
{
field_name_ = unique_ptr<StringInterpolator>(newStringInterpolator());
valid_field_name_ = field_name_->parse(vname);
if (!valid_field_name_)
{
warning("(meter) field template \"%s\" could not be parsed!\n", vname.c_str());
@ -2154,12 +1660,12 @@ FieldInfo::FieldInfo(int index,
string FieldInfo::renderJsonOnlyDefaultUnit(Meter *m)
{
return renderJson(m, NULL, NULL);
return renderJson(m, NULL);
}
string FieldInfo::renderJsonText(Meter *m)
{
return renderJson(m, NULL, NULL);
return renderJson(m, NULL);
}
string FieldInfo::generateFieldNameNoUnit(DVEntry *dve)
@ -2185,7 +1691,7 @@ string FieldInfo::generateFieldNameWithUnit(DVEntry *dve)
}
string FieldInfo::renderJson(Meter *m, DVEntry *dve, vector<Unit> *conversions)
string FieldInfo::renderJson(Meter *m, DVEntry *dve)
{
string s;
@ -2213,17 +1719,6 @@ string FieldInfo::renderJson(Meter *m, DVEntry *dve, vector<Unit> *conversions)
else
{
s += "\""+field_name+"_"+default_unit+"\":"+valueToString(m->getNumericValue(this, defaultUnit()), defaultUnit());
if (conversions != NULL)
{
Unit u = replaceWithConversionUnit(defaultUnit(), *conversions);
if (u != defaultUnit())
{
string unit = unitToStringLowerCase(u);
// Appending extra conversion unit.
s += ",\""+field_name+"_"+unit+"\":"+valueToString(m->getNumericValue(this, u), u);
}
}
}
return s;
@ -2238,8 +1733,8 @@ void MeterCommonImplementation::printMeter(Telegram *t,
vector<string> *selected_fields,
bool pretty_print_json)
{
*human_readable = concatFields(this, t, '\t', field_infos_, conversions_, true, selected_fields, extra_constant_fields);
*fields = concatFields(this, t, separator, field_infos_, conversions_, false, selected_fields, extra_constant_fields);
*human_readable = concatFields(this, t, '\t', field_infos_, true, selected_fields, extra_constant_fields);
*fields = concatFields(this, t, separator, field_infos_, false, selected_fields, extra_constant_fields);
string media;
if (t->tpl_id_found)
@ -2317,7 +1812,7 @@ void MeterCommonImplementation::printMeter(Telegram *t,
dve->offset,
dve->dif_vif_key.str().c_str(),
dve->value.c_str());
string out = fi.renderJson(this, dve, &conversions());
string out = fi.renderJson(this, dve);
debug("(meters) %s\n", out.c_str());
s += indent+out+","+newline;
}
@ -2337,7 +1832,7 @@ void MeterCommonImplementation::printMeter(Telegram *t,
// Or if no value has been received, null.
debug("(meters) render field %s(%s)[%d] without dventry\n",
fi.vname().c_str(), toString(fi.xuantity()), fi.index());
string out = fi.renderJson(this, NULL, &conversions());
string out = fi.renderJson(this, NULL);
debug("(meters) %s\n", out.c_str());
s += indent+out+","+newline;
}
@ -2405,14 +1900,6 @@ void MeterCommonImplementation::printMeter(Telegram *t,
{
string envvar = "METER_"+var+"_"+default_unit+"="+valueToString(getNumericValue(&fi, fi.defaultUnit()), fi.defaultUnit());
envs->push_back(envvar);
Unit u = replaceWithConversionUnit(fi.defaultUnit(), conversions_);
if (u != fi.defaultUnit())
{
string unit = unitToStringUpperCase(u);
string envvar = "METER_"+var+"_"+unit+"="+valueToString(getNumericValue(&fi, u), u);
envs->push_back(envvar);
}
}
}
}
@ -2541,7 +2028,6 @@ shared_ptr<Meter> createMeter(MeterInfo *mi)
if (di != NULL)
{
shared_ptr<Meter> newm = di->construct(*mi);
newm->addConversions(mi->conversions);
for (string &j : mi->extra_calculated_fields)
{
newm->addExtraCalculatedField(j);
@ -2569,7 +2055,6 @@ shared_ptr<Meter> createMeter(MeterInfo *mi)
case MeterDriver::driver: \
{ \
newm = create##cname(*mi); \
newm->addConversions(mi->conversions); \
newm->setPollInterval(mi->poll_interval); \
verbose("(meter) created %s " #mname " %s %s\n", \
mi->name.c_str(), mi->idsc.c_str(), keymsg); \
@ -2874,7 +2359,7 @@ bool FieldInfo::extractNumeric(Meter *m, Telegram *t, DVEntry *dve)
extracted_double_value);
}
m->setNumericValue(this, default_unit_, convert(extracted_double_value, decoded_unit, default_unit_));
t->addMoreExplanation(dve->offset, renderJson(m, dve, &m->conversions()));
t->addMoreExplanation(dve->offset, renderJson(m, dve));
found = true;
}
return found;

Wyświetl plik

@ -146,7 +146,6 @@ struct MeterInfo
vector<string> shells;
vector<string> extra_constant_fields; // Additional static fields that are added to each message.
vector<string> extra_calculated_fields; // Additional field calculated using formulas.
vector<Unit> conversions; // Additional units desired in json.
vector<string> selected_fields; // Usually set to the default fields, but can be override in meter config.
// If this is a meter that needs to be polled.
@ -304,6 +303,7 @@ struct PrintProperties
struct FieldInfo
{
~FieldInfo();
FieldInfo(int index,
string vname,
Quantity xuantity,
@ -349,7 +349,7 @@ struct FieldInfo
void performCalculation(Meter *m);
string renderJsonOnlyDefaultUnit(Meter *m);
string renderJson(Meter *m, DVEntry *dve, vector<Unit> *additional_conversions);
string renderJson(Meter *m, DVEntry *dve);
string renderJsonText(Meter *m);
// Render the field name based on the actual field from the telegram.
// A FieldInfo can be declared to handle any number of storage fields of a certain range.
@ -386,13 +386,13 @@ private:
Translate::Lookup lookup_;
// For calculated fields.
unique_ptr<Formula> formula_;
shared_ptr<Formula> formula_;
// For the generated field name.
unique_ptr<StringInterpolator> field_name_;
shared_ptr<StringInterpolator> field_name_;
// If the field name template could not be parsed.
bool valid_field_name_;
bool valid_field_name_ {};
};
struct BusManager;
@ -427,6 +427,7 @@ struct Meter
virtual time_t pollInterval() = 0;
virtual bool usesPolling() = 0;
virtual void setNumericValue(string vname, Unit u, double v) = 0;
virtual void setNumericValue(FieldInfo *fi, Unit u, double v) = 0;
virtual double getNumericValue(FieldInfo *fi, Unit u) = 0;
virtual void setStringValue(FieldInfo *fi, std::string v) = 0;
@ -452,9 +453,7 @@ struct Meter
bool simulated, string *id, bool *id_match, Telegram *out_t = NULL) = 0;
virtual MeterKeys *meterKeys() = 0;
virtual void addConversions(std::vector<Unit> cs) = 0;
virtual void addExtraCalculatedField(std::string ecf) = 0;
virtual vector<Unit>& conversions() = 0;
virtual void addShell(std::string cmdline) = 0;
virtual vector<string> &shellCmdlines() = 0;
virtual void poll(shared_ptr<BusManager> bus) = 0;
@ -462,6 +461,8 @@ struct Meter
virtual FieldInfo *findFieldInfo(string vname, Quantity xuantity) = 0;
virtual string renderJsonOnlyDefaultUnit(string vname, Quantity xuantity) = 0;
virtual string debugValues() = 0;
virtual ~Meter() = default;
};

Wyświetl plik

@ -94,8 +94,6 @@ protected:
void triggerUpdate(Telegram *t);
void setExpectedELLSecurityMode(ELLSecurityMode dsm);
void setExpectedTPLSecurityMode(TPLSecurityMode tsm);
void addConversions(std::vector<Unit> cs);
std::vector<Unit>& conversions() { return conversions_; }
void addShell(std::string cmdline);
void addExtraConstantField(std::string ecf);
std::vector<std::string> &shellCmdlines();
@ -167,6 +165,13 @@ protected:
function<void(Unit,double)> setValueFunc, // Use the SET macro above.
function<double(Unit)> getValueFunc); // Use the GET macro above.
void addNumericField(
string vname, // Name of value without unit, eg total
Quantity vquantity, // Value belongs to this quantity.
PrintProperties print_properties, // Should this be printed by default in fields,json and hr.
string help,
Unit use_unit = Unit::Unknown); // If specified use this unit for the json field instead instead of the default unit.
#define SET_STRING_FUNC(varname) {[=](string s){varname = s;}}
#define GET_STRING_FUNC(varname) {[=](){return varname; }}
@ -231,19 +236,20 @@ protected:
vector<string> *more_json, // Add this json "key"="value" strings.
vector<string> *selected_fields, // Only print these fields.
bool pretty_print); // Insert newlines and indentation.
// Json fields cannot be modified expect by adding conversions.
// Json fields include all values except timestamp_ut, timestamp_utc, timestamp_lt
// 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, Quantity xuantity);
string renderJsonOnlyDefaultUnit(string vname, Quantity xuantity);
string debugValues();
void processFieldExtractors(Telegram *t);
void processFieldCalculators();
virtual void processContent(Telegram *t);
void setNumericValue(string vname, Unit u, double v);
void setNumericValue(FieldInfo *fi, Unit u, double v);
double getNumericValue(FieldInfo *fi, Unit u);
void setStringValue(FieldInfo *fi, std::string v);
@ -290,7 +296,6 @@ private:
protected:
vector<Unit> conversions_;
vector<FieldInfo> field_infos_;
vector<string> field_names_;
// Defaults to a setting specified in the driver. Can be overridden in the meter file.

Wyświetl plik

@ -84,8 +84,8 @@ if [ "$?" != "0" ]; then RC="1"; fi
tests/test_multiple_ids.sh $PROG
if [ "$?" != "0" ]; then RC="1"; fi
#tests/test_conversions.sh $PROG
#if [ "$?" != "0" ]; then RC="1"; fi
tests/test_conversions.sh $PROG
if [ "$?" != "0" ]; then RC="1"; fi
tests/test_formulas.sh $PROG
if [ "$?" != "0" ]; then RC="1"; fi

Wyświetl plik

@ -2,4 +2,3 @@ loglevel=normal
device=simulations/simulation_conversionsadded.txt
logtelegrams=false
format=json
addconversions=GJ,L,F

Wyświetl plik

@ -2,3 +2,6 @@ name=Hettan
type=vario451
id=58234965
key=
calculate_current_gj=current_kwh
calculate_previous_gj=previous_kwh
calculate_total_gj=total_kwh

Wyświetl plik

@ -2,3 +2,7 @@ name=MyTapWater
type=multical21:c1
id=76348799
key=
calculate_external_temperature_f=external_temperature_c
calculate_flow_temperature_f=flow_temperature_c
calculate_target_l=target_m3
calculate_total_l=total_m3

Wyświetl plik

@ -7,18 +7,23 @@ mkdir -p $TEST
TESTNAME="Test config4 with added conversions"
TESTRESULT="ERROR"
cat simulations/simulation_conversionsadded.txt | grep '^{' > $TEST/test_expected.txt
cat simulations/simulation_conversionsadded.txt | grep '^{' | jq --sort-keys . > $TEST/test_expected.txt
$PROG --useconfig=tests/config4 > $TEST/test_output.txt 2> $TEST/test_stderr.txt
$PROG --useconfig=tests/config4 2> $TEST/test_stderr.txt | jq --sort-keys . > $TEST/test_output.txt
if [ "$?" = "0" ]
then
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
cat $TEST/test_output.txt | sed 's/"timestamp": "....-..-..T..:..:..Z"/"timestamp": "1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
diff $TEST/test_expected.txt $TEST/test_responses.txt
if [ "$?" = "0" ]
then
echo "OK: $TESTNAME"
TESTRESULT="OK"
else
if [ "$USE_MELD" = "true" ]
then
meld $TEST/test_expected.txt $TEST/test_responses.txt
fi
fi
fi

Wyświetl plik

@ -1,32 +1,41 @@
#!/bin/sh
# Adding automatic conversions is deprecated!
exit 1
PROG="$1"
rm -rf testoutput
mkdir -p testoutput
TEST=testoutput
TESTNAME="Test conversions of units"
TESTNAME="Test conversions of units using calculations"
TESTRESULT="ERROR"
cat simulations/simulation_conversionsadded.txt | grep '^{' > $TEST/test_expected.txt
$PROG --addconversions=GJ,L,F --format=json simulations/simulation_conversionsadded.txt \
Hettan vario451 58234965 "" \
cat simulations/simulation_conversionsadded.txt | grep '^{' | jq --sort-keys . > $TEST/test_expected.txt
$PROG --format=json --calculate_total_gj=total_kwh \
--calculate_current_gj=current_kwh \
--calculate_previous_gj=previous_kwh \
--calculate_external_temperature_f=external_temperature_c \
--calculate_flow_temperature_f=flow_temperature_c \
--calculate_target_l=target_m3 \
--calculate_total_l=total_m3 \
simulations/simulation_conversionsadded.txt \
Hettan vario451 58234965 "" \
MyTapWater multical21 76348799 "" \
> $TEST/test_output.txt
2> $TEST/test_stderr.txt \
| jq --sort-keys . > $TEST/test_output.txt
if [ "$?" = "0" ]
then
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
cat $TEST/test_output.txt | sed 's/"timestamp": "....-..-..T..:..:..Z"/"timestamp": "1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
diff $TEST/test_expected.txt $TEST/test_responses.txt
if [ "$?" = "0" ]
then
echo "OK: $TESTNAME"
TESTRESULT="OK"
else
if [ "$USE_MELD" = "true" ]
then
meld $TEST/test_expected.txt $TEST/test_responses.txt
fi
fi
fi

Wyświetl plik

@ -16,7 +16,7 @@ EOF
$PROG --format=fields --separator=';' \
--selectfields=id,name,total_l,total_m3,max_flow_m3h,flow_temperature_c,flow_temperature_f \
--addconversions=L,F \
--calculate_total_l=total_m3 --calculate_flow_temperature_f=flow_temperature_c \
simulations/simulation_c1.txt Vatten multical21 76348799 "" \
> $TEST/test_output.txt

Wyświetl plik

@ -24,8 +24,6 @@ mqtt_publish) sent to a REST API (eg curl) or store it in a database
(eg psql).
.SH OPTIONS
\fB\--addconversions=\fR<unit>[,<unit>] add conversion to these units for json and shell envs (GJ,F)
\fB\--alarmexpectedactivity=\fRmon-fri(08-17),sat-sun(09-12) Specify when the timeout is tested, default is mon-sun(00-23)
\fB\--alarmshell=\fR<cmdline> invokes cmdline when an alarm triggers