wmbusmeters/src/metermanager.cc

514 wiersze
18 KiB
C++

/*
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"meters_common_implementation.h"
#include"units.h"
#include"wmbus.h"
#include"wmbus_utils.h"
#include<algorithm>
#include<chrono>
#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_ {};
int should_profile_ {};
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/wmbusmeters/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.driverName().str() == "auto")
{
// Look up the proper meter driver!
DriverInfo di = pickMeterDriver(&t);
if (di.name().str() == "")
{
if (should_analyze_ == false)
{
// We are not analyzing, so warn here.
warnForUnknownDriver(mi.name, &t);
}
}
else
{
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(),
mi.driverName().str().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(),
mi.driverName().str().c_str());
}
else
{
verbose("(meter) started meter %d (%s %s %s)\n",
meter->index(),
mi.name.c_str(),
meter_info.idsc.c_str(),
mi.driverName().str().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/wmbusmeters/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, int profile)
{
should_analyze_ = b;
should_profile_ = profile;
analyze_format_ = f;
if (force_driver != "auto")
{
analyze_driver_ = force_driver;
}
analyze_key_ = key;
analyze_verbose_ = verbose;
}
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(driver_name, t.dll_type) &&
!isMeterDriverReasonableForMedia(driver_name, t.tpl_type))
{
// Sanity check, skip this driver since it is not relevant for this media.
continue;
}
debug("Testing driver %s...\n", driver_name.c_str());
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.
int best_length = 0;
int best_understood = 0;
string best_driver = findBestNewStyleDriver(mi, &best_length, &best_understood, t, about, input_frame, simulated, "");
if (best_driver == "") best_driver = "unknown";
mi.driver_name = DriverName(best_driver);
// Default to best driver....
using_driver = best_driver;
using_length = best_length;
using_understood = best_understood;
// Unless the existing mapping from mfct/media/version to driver overrides best.
DriverInfo auto_di = pickMeterDriver(&t);
string auto_driver = auto_di.name().str();
// 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 = findBestNewStyleDriver(mi, &force_length, &force_understood, t, about, input_frame, simulated,
force_driver);
using_length = force_length;
using_understood = force_understood;
}
mi.driver_name = using_driver;
auto meter = createMeter(&mi);
assert(meter != NULL);
bool match = false;
string id;
if (should_profile_ > 0)
{
size_t start_peak_rss = getPeakRSS();
size_t start_curr_rss = getCurrentRSS();
string start_peak_prss = humanReadableTwoDecimals(start_peak_rss);
notice("Profiling %d rounds memory rss %zu peak %s\n", should_profile_, start_curr_rss, start_peak_prss.c_str());
chrono::milliseconds start = chrono::duration_cast< chrono::milliseconds >(chrono::system_clock::now().time_since_epoch());
for (int k=0; k<should_profile_; ++k)
{
meter->handleTelegram(about, input_frame, simulated, &id, &match, &t);
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 (k % 100 == 0) fprintf(stderr, ".");
}
chrono::milliseconds end = chrono::duration_cast< chrono::milliseconds >(chrono::system_clock::now().time_since_epoch());
size_t end_peak_rss = getPeakRSS();
size_t end_curr_rss = getCurrentRSS();
string end_peak_prss = humanReadableTwoDecimals(end_peak_rss);
std::chrono::duration<double> diff_s(end-start);
double speed_ms = 1000.0 * (diff_s.count()) / should_profile_;
notice("\nDone profiling after %g s which gives %g ms/telegram memory rss %zu peak %s\n",
diff_s.count(),
speed_ms,
end_curr_rss,
end_peak_prss.c_str());
return;
}
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));
}