/* 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 . */ #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 #include #include #include #include #include #include #include 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 meter_templates_; vector> meters_; vector)>> telegram_listeners_; function on_meter_updated_; public: void addMeterTemplate(MeterInfo &mi) { meter_templates_.push_back(mi); } void addMeter(shared_ptr 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 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 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 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)> cb) { telegram_listeners_.push_back(cb); } void whenMeterUpdated(std::function cb) { on_meter_updated_ = cb; } void pollMeters(shared_ptr 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 &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 &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=::\n" "where 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; khandleTelegram(about, input_frame, simulated, &id, &match, &t); string hr, fields, json; vector 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 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 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 createMeterManager(bool daemon) { return shared_ptr(new MeterManagerImplementation(daemon)); }