kopia lustrzana https://github.com/weetmuts/wmbusmeters
Removed --addconversions=GJ replaced with --calculate_total_gj=total_kwh
rodzic
d831d9cc87
commit
bccee1a170
10
CHANGES
10
CHANGES
|
@ -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.
|
||||
|
||||
|
|
1
Makefile
1
Makefile
|
@ -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 \
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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));
|
||||
}
|
731
src/meters.cc
731
src/meters.cc
|
@ -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;
|
||||
|
|
15
src/meters.h
15
src/meters.h
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
4
test.sh
4
test.sh
|
@ -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
|
||||
|
|
|
@ -2,4 +2,3 @@ loglevel=normal
|
|||
device=simulations/simulation_conversionsadded.txt
|
||||
logtelegrams=false
|
||||
format=json
|
||||
addconversions=GJ,L,F
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Ładowanie…
Reference in New Issue