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. 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. 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)/dvparser.o \
$(BUILD)/formula.o \ $(BUILD)/formula.o \
$(BUILD)/mbus_rawtty.o \ $(BUILD)/mbus_rawtty.o \
$(BUILD)/metermanager.o \
$(BUILD)/meters.o \ $(BUILD)/meters.o \
$(BUILD)/manufacturer_specificities.o \ $(BUILD)/manufacturer_specificities.o \
$(BUILD)/printer.o \ $(BUILD)/printer.o \

Wyświetl plik

@ -282,17 +282,6 @@ static shared_ptr<Configuration> parseNormalCommandLine(Configuration *c, int ar
i++; i++;
continue; 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 (!strncmp(argv[i], "--selectfields=", 15)) {
if (strlen(argv[i]) > 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) void handleLogTimestamps(Configuration *c, string ts)
{ {
if (ts == "never") 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 == "alarmtimeout") handleAlarmTimeout(c, p.second);
else if (p.first == "alarmexpectedactivity") handleAlarmExpectedActivity(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 == "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 == "logtimestamps") handleLogTimestamps(c, p.second);
else if (p.first == "selectfields") handleSelectedFields(c, p.second); else if (p.first == "selectfields") handleSelectedFields(c, p.second);
else if (p.first == "shell") handleShell(c, p.second); else if (p.first == "shell") handleShell(c, p.second);

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -407,6 +407,7 @@ struct FieldMatcher
FieldMatcher() : active(false) { } FieldMatcher() : active(false) { }
FieldMatcher(bool act) : active(act) { } FieldMatcher(bool act) : active(act) { }
static FieldMatcher build() { return FieldMatcher(true); } static FieldMatcher build() { return FieldMatcher(true); }
static FieldMatcher noMatcher() { return FieldMatcher(false); }
FieldMatcher &set(DifVifKey k) { FieldMatcher &set(DifVifKey k) {
dif_vif_key = k; dif_vif_key = k;
match_dif_vif_key = (k.str() != ""); return *this; } 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(); if (formula()->meter() == NULL) return std::numeric_limits<double>::quiet_NaN();
Unit field_unit = field_info_->defaultUnit(); FieldInfo *fi = formula()->meter()->findFieldInfo(vname_, quantity_);
double val = formula()->meter()->getNumericValue(field_info_, field_unit);
Unit field_unit = fi->defaultUnit();
double val = formula()->meter()->getNumericValue(fi, field_unit);
const SIUnit& field_si_unit = toSIUnit(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 from_si_unit = toSIUnit(fi->defaultUnit());
SIUnit to_si_unit = toSIUnit(u); SIUnit to_si_unit = toSIUnit(u);
assert(from_si_unit.canConvertTo(to_si_unit)); 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) void FormulaImplementation::doDVEntryField(Unit u, DVEntryCounterType ct)
@ -953,12 +955,17 @@ string NumericFormulaSquareRoot::tree()
string NumericFormulaMeterField::str() 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() 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() string NumericFormulaDVEntryField::str()

Wyświetl plik

@ -58,7 +58,8 @@ struct NumericFormulaConstant : public NumericFormula
struct NumericFormulaMeterField : 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); double calculate(SIUnit to);
string str(); string str();
@ -67,7 +68,8 @@ struct NumericFormulaMeterField : public NumericFormula
private: private:
FieldInfo *field_info_; string vname_;
Quantity quantity_;
}; };
struct NumericFormulaDVEntryField : public NumericFormula struct NumericFormulaDVEntryField : public NumericFormula

Wyświetl plik

@ -456,7 +456,6 @@ void setup_meters(Configuration *config, MeterManager *manager)
{ {
for (MeterInfo &m : config->meters) for (MeterInfo &m : config->meters)
{ {
m.conversions = config->conversions;
m.extra_calculated_fields.insert(m.extra_calculated_fields.end(), m.extra_calculated_fields.insert(m.extra_calculated_fields.end(),
config->extra_calculated_fields.begin(), config->extra_calculated_fields.begin(),
config->extra_calculated_fields.end()); 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; 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, MeterCommonImplementation::MeterCommonImplementation(MeterInfo &mi,
string driver) : string driver) :
@ -780,14 +219,6 @@ MeterCommonImplementation::MeterCommonImplementation(MeterInfo &mi,
force_mfct_index_ = di.forceMfctIndex(); 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) void MeterCommonImplementation::addShell(string cmdline)
{ {
shell_cmdlines_.push_back(cmdline); shell_cmdlines_.push_back(cmdline);
@ -874,7 +305,7 @@ void MeterCommonImplementation::addMfctTPLStatusBits(Translate::Lookup lookup)
void MeterCommonImplementation::addPrint(string vname, Quantity vquantity, void MeterCommonImplementation::addPrint(string vname, Quantity vquantity,
function<double(Unit)> getValueFunc, string help, PrintProperties pprops) function<double(Unit)> getValueFunc, string help, PrintProperties pprops)
{ {
field_infos_.push_back( field_infos_.emplace_back(
FieldInfo(field_infos_.size(), FieldInfo(field_infos_.size(),
vname, vname,
vquantity, vquantity,
@ -895,7 +326,7 @@ void MeterCommonImplementation::addPrint(string vname, Quantity vquantity,
void MeterCommonImplementation::addPrint(string vname, Quantity vquantity, Unit unit, void MeterCommonImplementation::addPrint(string vname, Quantity vquantity, Unit unit,
function<double(Unit)> getValueFunc, string help, PrintProperties pprops) function<double(Unit)> getValueFunc, string help, PrintProperties pprops)
{ {
field_infos_.push_back( field_infos_.emplace_back(
FieldInfo(field_infos_.size(), FieldInfo(field_infos_.size(),
vname, vname,
vquantity, vquantity,
@ -917,7 +348,7 @@ void MeterCommonImplementation::addPrint(string vname, Quantity vquantity,
function<string()> getValueFunc, function<string()> getValueFunc,
string help, PrintProperties pprops) string help, PrintProperties pprops)
{ {
field_infos_.push_back( field_infos_.emplace_back(
FieldInfo(field_infos_.size(), FieldInfo(field_infos_.size(),
vname, vname,
vquantity, vquantity,
@ -950,7 +381,7 @@ void MeterCommonImplementation::addNumericFieldWithExtractor(
function<void(Unit,double)> setValueFunc, function<void(Unit,double)> setValueFunc,
function<double(Unit)> getValueFunc) function<double(Unit)> getValueFunc)
{ {
field_infos_.push_back( field_infos_.emplace_back(
FieldInfo(field_infos_.size(), FieldInfo(field_infos_.size(),
vname, vname,
vquantity, vquantity,
@ -976,7 +407,7 @@ void MeterCommonImplementation::addNumericFieldWithExtractor(string vname,
FieldMatcher matcher, FieldMatcher matcher,
Unit use_unit) Unit use_unit)
{ {
field_infos_.push_back( field_infos_.emplace_back(
FieldInfo(field_infos_.size(), FieldInfo(field_infos_.size(),
vname, vname,
vquantity, vquantity,
@ -1020,7 +451,7 @@ void MeterCommonImplementation::addNumericFieldWithCalculator(string vname,
vquantity, vquantity,
use_unit == Unit::Unknown ? defaultUnitForQuantity(vquantity) : use_unit, use_unit == Unit::Unknown ? defaultUnitForQuantity(vquantity) : use_unit,
VifScaling::Auto, VifScaling::Auto,
FieldMatcher::build(), FieldMatcher::noMatcher(),
help, help,
print_properties, print_properties,
NULL, NULL,
@ -1040,7 +471,7 @@ void MeterCommonImplementation::addNumericField(
function<void(Unit,double)> setValueFunc, function<void(Unit,double)> setValueFunc,
function<double(Unit)> getValueFunc) function<double(Unit)> getValueFunc)
{ {
field_infos_.push_back( field_infos_.emplace_back(
FieldInfo(field_infos_.size(), FieldInfo(field_infos_.size(),
vname, vname,
vquantity, 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( void MeterCommonImplementation::addStringFieldWithExtractor(
string vname, string vname,
Quantity vquantity, Quantity vquantity,
@ -1072,7 +528,7 @@ void MeterCommonImplementation::addStringFieldWithExtractor(
function<void(string)> setValueFunc, function<void(string)> setValueFunc,
function<string()> getValueFunc) function<string()> getValueFunc)
{ {
field_infos_.push_back( field_infos_.emplace_back(
FieldInfo(field_infos_.size(), FieldInfo(field_infos_.size(),
vname, vname,
vquantity, vquantity,
@ -1095,7 +551,7 @@ void MeterCommonImplementation::addStringFieldWithExtractor(string vname,
PrintProperties print_properties, PrintProperties print_properties,
FieldMatcher matcher) FieldMatcher matcher)
{ {
field_infos_.push_back( field_infos_.emplace_back(
FieldInfo(field_infos_.size(), FieldInfo(field_infos_.size(),
vname, vname,
Quantity::Text, Quantity::Text,
@ -1128,7 +584,7 @@ void MeterCommonImplementation::addStringFieldWithExtractorAndLookup(
function<string()> getValueFunc, function<string()> getValueFunc,
Translate::Lookup lookup) Translate::Lookup lookup)
{ {
field_infos_.push_back( field_infos_.emplace_back(
FieldInfo(field_infos_.size(), FieldInfo(field_infos_.size(),
vname, vname,
vquantity, vquantity,
@ -1152,7 +608,7 @@ void MeterCommonImplementation::addStringFieldWithExtractorAndLookup(string vnam
FieldMatcher matcher, FieldMatcher matcher,
Translate::Lookup lookup) Translate::Lookup lookup)
{ {
field_infos_.push_back( field_infos_.emplace_back(
FieldInfo(field_infos_.size(), FieldInfo(field_infos_.size(),
vname, vname,
Quantity::Text, Quantity::Text,
@ -1174,7 +630,7 @@ void MeterCommonImplementation::addStringField(string vname,
string help, string help,
PrintProperties print_properties) PrintProperties print_properties)
{ {
field_infos_.push_back( field_infos_.emplace_back(
FieldInfo(field_infos_.size(), FieldInfo(field_infos_.size(),
vname, vname,
Quantity::Text, Quantity::Text,
@ -1589,7 +1045,7 @@ void MeterCommonImplementation::triggerUpdate(Telegram *t)
t->handled = true; 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) vector<string> *extra_constant_fields)
{ {
string s; string s;
@ -1613,7 +1069,7 @@ string concatAllFields(Meter *m, Telegram *t, char c, vector<FieldInfo> &fields,
} }
else else
{ {
Unit u = replaceWithConversionUnit(fi.defaultUnit(), cs); Unit u = fi.defaultUnit();
double v = m->getNumericValue(&fi, u); double v = m->getNumericValue(&fi, u);
if (hr) { if (hr) {
s += valueToString(v, u); 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? // Is the desired field one of the meter printable fields?
bool checkPrintableField(string *buf, string field, Meter *m, Telegram *t, char c, bool checkPrintableField(string *buf, string field, Meter *m, Telegram *t, char c,
vector<FieldInfo> &fields, vector<Unit> &cs, bool human_readable) vector<FieldInfo> &fields, bool human_readable)
{ {
for (FieldInfo &fi : fields) for (FieldInfo &fi : fields)
{ {
@ -1724,7 +1180,7 @@ bool checkPrintableField(string *buf, string field, Meter *m, Telegram *t, char
else else
{ {
// Added conversion unit. // Added conversion unit.
Unit u = replaceWithConversionUnit(fi.defaultUnit(), cs); Unit u = fi.defaultUnit();
if (u != fi.defaultUnit()) if (u != fi.defaultUnit())
{ {
string unit = unitToStringLowerCase(u); string unit = unitToStringLowerCase(u);
@ -1762,7 +1218,7 @@ bool checkConstantField(string *buf, string field, char c, vector<string> *extra
return false; 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) vector<string> *selected_fields, vector<string> *extra_constant_fields)
{ {
if (selected_fields == NULL || selected_fields->size() == 0) if (selected_fields == NULL || selected_fields->size() == 0)
@ -1774,7 +1230,7 @@ string concatFields(Meter *m, Telegram *t, char c, vector<FieldInfo> &prints, ve
} }
else 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 = ""; 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); bool handled = checkCommonField(&buf, field, m, t, c, human_readable);
if (handled) continue; 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; if (handled) continue;
handled = checkConstantField(&buf, field, c, extra_constant_fields); handled = checkConstantField(&buf, field, c, extra_constant_fields);
@ -1851,10 +1307,10 @@ bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<ucha
// Invoke standardized field extractors! // Invoke standardized field extractors!
processFieldExtractors(&t); processFieldExtractors(&t);
// Invoke any calculators working on the extracted fields.
processFieldCalculators();
// Invoke tailor made meter specific parsing! // Invoke tailor made meter specific parsing!
processContent(&t); processContent(&t);
// Invoke any calculators working on the extracted fields.
processFieldCalculators();
// All done.... // All done....
if (isDebugEnabled()) if (isDebugEnabled())
@ -1891,7 +1347,16 @@ void MeterCommonImplementation::processFieldExtractors(Telegram *t)
{ {
int current_match_nr = 0; 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", debug("(meters) trying field info %s(%s)[%d]...\n",
fi.vname().c_str(), fi.vname().c_str(),
toString(fi.xuantity()), 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); 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) bool MeterCommonImplementation::hasValue(FieldInfo *fi)
{ {
return hasStringValue(fi) || hasNumericValue(fi); return hasStringValue(fi) || hasNumericValue(fi);
@ -2114,6 +1592,34 @@ string MeterCommonImplementation::renderJsonOnlyDefaultUnit(string vname, Quanti
return fi->renderJsonOnlyDefaultUnit(this); 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, FieldInfo::FieldInfo(int index,
string vname, string vname,
Quantity xuantity, Quantity xuantity,
@ -2142,10 +1648,10 @@ FieldInfo::FieldInfo(int index,
set_numeric_value_override_(set_numeric_value_override), set_numeric_value_override_(set_numeric_value_override),
set_string_value_override_(set_string_value_override), set_string_value_override_(set_string_value_override),
lookup_(lookup), lookup_(lookup),
formula_(formula) 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_) if (!valid_field_name_)
{ {
warning("(meter) field template \"%s\" could not be parsed!\n", vname.c_str()); 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) string FieldInfo::renderJsonOnlyDefaultUnit(Meter *m)
{ {
return renderJson(m, NULL, NULL); return renderJson(m, NULL);
} }
string FieldInfo::renderJsonText(Meter *m) string FieldInfo::renderJsonText(Meter *m)
{ {
return renderJson(m, NULL, NULL); return renderJson(m, NULL);
} }
string FieldInfo::generateFieldNameNoUnit(DVEntry *dve) 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; string s;
@ -2213,17 +1719,6 @@ string FieldInfo::renderJson(Meter *m, DVEntry *dve, vector<Unit> *conversions)
else else
{ {
s += "\""+field_name+"_"+default_unit+"\":"+valueToString(m->getNumericValue(this, defaultUnit()), defaultUnit()); 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; return s;
@ -2238,8 +1733,8 @@ void MeterCommonImplementation::printMeter(Telegram *t,
vector<string> *selected_fields, vector<string> *selected_fields,
bool pretty_print_json) bool pretty_print_json)
{ {
*human_readable = concatFields(this, t, '\t', field_infos_, conversions_, true, 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_, conversions_, false, selected_fields, extra_constant_fields); *fields = concatFields(this, t, separator, field_infos_, false, selected_fields, extra_constant_fields);
string media; string media;
if (t->tpl_id_found) if (t->tpl_id_found)
@ -2317,7 +1812,7 @@ void MeterCommonImplementation::printMeter(Telegram *t,
dve->offset, dve->offset,
dve->dif_vif_key.str().c_str(), dve->dif_vif_key.str().c_str(),
dve->value.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()); debug("(meters) %s\n", out.c_str());
s += indent+out+","+newline; s += indent+out+","+newline;
} }
@ -2337,7 +1832,7 @@ void MeterCommonImplementation::printMeter(Telegram *t,
// Or if no value has been received, null. // Or if no value has been received, null.
debug("(meters) render field %s(%s)[%d] without dventry\n", debug("(meters) render field %s(%s)[%d] without dventry\n",
fi.vname().c_str(), toString(fi.xuantity()), fi.index()); 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()); debug("(meters) %s\n", out.c_str());
s += indent+out+","+newline; 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()); string envvar = "METER_"+var+"_"+default_unit+"="+valueToString(getNumericValue(&fi, fi.defaultUnit()), fi.defaultUnit());
envs->push_back(envvar); 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) if (di != NULL)
{ {
shared_ptr<Meter> newm = di->construct(*mi); shared_ptr<Meter> newm = di->construct(*mi);
newm->addConversions(mi->conversions);
for (string &j : mi->extra_calculated_fields) for (string &j : mi->extra_calculated_fields)
{ {
newm->addExtraCalculatedField(j); newm->addExtraCalculatedField(j);
@ -2569,7 +2055,6 @@ shared_ptr<Meter> createMeter(MeterInfo *mi)
case MeterDriver::driver: \ case MeterDriver::driver: \
{ \ { \
newm = create##cname(*mi); \ newm = create##cname(*mi); \
newm->addConversions(mi->conversions); \
newm->setPollInterval(mi->poll_interval); \ newm->setPollInterval(mi->poll_interval); \
verbose("(meter) created %s " #mname " %s %s\n", \ verbose("(meter) created %s " #mname " %s %s\n", \
mi->name.c_str(), mi->idsc.c_str(), keymsg); \ 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); extracted_double_value);
} }
m->setNumericValue(this, default_unit_, convert(extracted_double_value, decoded_unit, default_unit_)); 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; found = true;
} }
return found; return found;

Wyświetl plik

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

Wyświetl plik

@ -94,8 +94,6 @@ protected:
void triggerUpdate(Telegram *t); void triggerUpdate(Telegram *t);
void setExpectedELLSecurityMode(ELLSecurityMode dsm); void setExpectedELLSecurityMode(ELLSecurityMode dsm);
void setExpectedTPLSecurityMode(TPLSecurityMode tsm); void setExpectedTPLSecurityMode(TPLSecurityMode tsm);
void addConversions(std::vector<Unit> cs);
std::vector<Unit>& conversions() { return conversions_; }
void addShell(std::string cmdline); void addShell(std::string cmdline);
void addExtraConstantField(std::string ecf); void addExtraConstantField(std::string ecf);
std::vector<std::string> &shellCmdlines(); std::vector<std::string> &shellCmdlines();
@ -167,6 +165,13 @@ protected:
function<void(Unit,double)> setValueFunc, // Use the SET macro above. function<void(Unit,double)> setValueFunc, // Use the SET macro above.
function<double(Unit)> getValueFunc); // Use the GET 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 SET_STRING_FUNC(varname) {[=](string s){varname = s;}}
#define GET_STRING_FUNC(varname) {[=](){return varname; }} #define GET_STRING_FUNC(varname) {[=](){return varname; }}
@ -231,19 +236,20 @@ protected:
vector<string> *more_json, // Add this json "key"="value" strings. vector<string> *more_json, // Add this json "key"="value" strings.
vector<string> *selected_fields, // Only print these fields. vector<string> *selected_fields, // Only print these fields.
bool pretty_print); // Insert newlines and indentation. 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 // 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 // since Json is assumed to be decoded by a program and the current timestamp which is the
// same as timestamp_utc, can always be decoded/recoded into local time or a unix timestamp. // same as timestamp_utc, can always be decoded/recoded into local time or a unix timestamp.
FieldInfo *findFieldInfo(string vname, Quantity xuantity); FieldInfo *findFieldInfo(string vname, Quantity xuantity);
string renderJsonOnlyDefaultUnit(string vname, Quantity xuantity); string renderJsonOnlyDefaultUnit(string vname, Quantity xuantity);
string debugValues();
void processFieldExtractors(Telegram *t); void processFieldExtractors(Telegram *t);
void processFieldCalculators(); void processFieldCalculators();
virtual void processContent(Telegram *t); virtual void processContent(Telegram *t);
void setNumericValue(string vname, Unit u, double v);
void setNumericValue(FieldInfo *fi, Unit u, double v); void setNumericValue(FieldInfo *fi, Unit u, double v);
double getNumericValue(FieldInfo *fi, Unit u); double getNumericValue(FieldInfo *fi, Unit u);
void setStringValue(FieldInfo *fi, std::string v); void setStringValue(FieldInfo *fi, std::string v);
@ -290,7 +296,6 @@ private:
protected: protected:
vector<Unit> conversions_;
vector<FieldInfo> field_infos_; vector<FieldInfo> field_infos_;
vector<string> field_names_; vector<string> field_names_;
// Defaults to a setting specified in the driver. Can be overridden in the meter file. // 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 tests/test_multiple_ids.sh $PROG
if [ "$?" != "0" ]; then RC="1"; fi if [ "$?" != "0" ]; then RC="1"; fi
#tests/test_conversions.sh $PROG tests/test_conversions.sh $PROG
#if [ "$?" != "0" ]; then RC="1"; fi if [ "$?" != "0" ]; then RC="1"; fi
tests/test_formulas.sh $PROG tests/test_formulas.sh $PROG
if [ "$?" != "0" ]; then RC="1"; fi if [ "$?" != "0" ]; then RC="1"; fi

Wyświetl plik

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

Wyświetl plik

@ -2,3 +2,6 @@ name=Hettan
type=vario451 type=vario451
id=58234965 id=58234965
key= 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 type=multical21:c1
id=76348799 id=76348799
key= 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" TESTNAME="Test config4 with added conversions"
TESTRESULT="ERROR" 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" ] if [ "$?" = "0" ]
then 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 diff $TEST/test_expected.txt $TEST/test_responses.txt
if [ "$?" = "0" ] if [ "$?" = "0" ]
then then
echo "OK: $TESTNAME" echo "OK: $TESTNAME"
TESTRESULT="OK" TESTRESULT="OK"
else
if [ "$USE_MELD" = "true" ]
then
meld $TEST/test_expected.txt $TEST/test_responses.txt
fi
fi fi
fi fi

Wyświetl plik

@ -1,32 +1,41 @@
#!/bin/sh #!/bin/sh
# Adding automatic conversions is deprecated!
exit 1
PROG="$1" PROG="$1"
rm -rf testoutput rm -rf testoutput
mkdir -p testoutput mkdir -p testoutput
TEST=testoutput TEST=testoutput
TESTNAME="Test conversions of units" TESTNAME="Test conversions of units using calculations"
TESTRESULT="ERROR" 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 --addconversions=GJ,L,F --format=json simulations/simulation_conversionsadded.txt \ $PROG --format=json --calculate_total_gj=total_kwh \
Hettan vario451 58234965 "" \ --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 "" \ MyTapWater multical21 76348799 "" \
> $TEST/test_output.txt 2> $TEST/test_stderr.txt \
| jq --sort-keys . > $TEST/test_output.txt
if [ "$?" = "0" ] if [ "$?" = "0" ]
then 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 diff $TEST/test_expected.txt $TEST/test_responses.txt
if [ "$?" = "0" ] if [ "$?" = "0" ]
then then
echo "OK: $TESTNAME" echo "OK: $TESTNAME"
TESTRESULT="OK" TESTRESULT="OK"
else
if [ "$USE_MELD" = "true" ]
then
meld $TEST/test_expected.txt $TEST/test_responses.txt
fi
fi fi
fi fi

Wyświetl plik

@ -16,7 +16,7 @@ EOF
$PROG --format=fields --separator=';' \ $PROG --format=fields --separator=';' \
--selectfields=id,name,total_l,total_m3,max_flow_m3h,flow_temperature_c,flow_temperature_f \ --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 "" \ simulations/simulation_c1.txt Vatten multical21 76348799 "" \
> $TEST/test_output.txt > $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). (eg psql).
.SH OPTIONS .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\--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 \fB\--alarmshell=\fR<cmdline> invokes cmdline when an alarm triggers