diff --git a/CHANGES b/CHANGES index dad2ce9..d57007c 100644 --- a/CHANGES +++ b/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. diff --git a/Makefile b/Makefile index abe4676..085f3aa 100644 --- a/Makefile +++ b/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 \ diff --git a/src/cmdline.cc b/src/cmdline.cc index bd5ae84..93e2b99 100644 --- a/src/cmdline.cc +++ b/src/cmdline.cc @@ -282,17 +282,6 @@ static shared_ptr 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) { diff --git a/src/config.cc b/src/config.cc index 09b441f..82fbdaa 100644 --- a/src/config.cc +++ b/src/config.cc @@ -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 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); diff --git a/src/config.h b/src/config.h index 16e267a..952b29a 100644 --- a/src/config.h +++ b/src/config.h @@ -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 conversions; std::vector selected_fields; std::vector meters; std::vector extra_constant_fields; // Additional constant fields to always add to json. @@ -146,7 +145,6 @@ struct Configuration shared_ptr loadConfiguration(string root, ConfigOverrides overrides); void parseMeterConfig(Configuration *c, vector &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); diff --git a/src/driver_vario451.cc b/src/driver_vario451.cc index a1ad949..35a1761 100644 --- a/src/driver_vario451.cc +++ b/src/driver_vario451.cc @@ -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); } } diff --git a/src/dvparser.h b/src/dvparser.h index e616e89..3a49119 100644 --- a/src/dvparser.h +++ b/src/dvparser.h @@ -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; } diff --git a/src/formula.cc b/src/formula.cc index 91d84b3..f6c31a1 100644 --- a/src/formula.cc +++ b/src/formula.cc @@ -45,8 +45,10 @@ double NumericFormulaMeterField::calculate(SIUnit to_si_unit) { if (formula()->meter() == NULL) return std::numeric_limits::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 ""; + FieldInfo *fi = formula()->meter()->findFieldInfo(vname_, quantity_); + + return fi->vname()+"_"+unitToStringLowerCase(fi->defaultUnit()); } string NumericFormulaMeterField::tree() { - return "vname()+"_"+unitToStringLowerCase(field_info_->defaultUnit())+"> "; + if (formula()->meter() == NULL) return ""; + FieldInfo *fi = formula()->meter()->findFieldInfo(vname_, quantity_); + return "vname()+"_"+unitToStringLowerCase(fi->defaultUnit())+"> "; } string NumericFormulaDVEntryField::str() diff --git a/src/formula_implementation.h b/src/formula_implementation.h index 45e35d9..dc8ec4e 100644 --- a/src/formula_implementation.h +++ b/src/formula_implementation.h @@ -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 diff --git a/src/main.cc b/src/main.cc index 6b54d3e..43fa296 100644 --- a/src/main.cc +++ b/src/main.cc @@ -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()); diff --git a/src/metermanager.cc b/src/metermanager.cc new file mode 100644 index 0000000..1907a56 --- /dev/null +++ b/src/metermanager.cc @@ -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 . +*/ + +#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 +#include +#include +#include +#include +#include +#include + + +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 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/weetmuts/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.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)> 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) + { + 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 &input_frame, + bool simulated, + string force) + { + vector 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 &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 &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. + 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 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)); +} diff --git a/src/meters.cc b/src/meters.cc index b3b603e..8440cc2 100644 --- a/src/meters.cc +++ b/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 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/weetmuts/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.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)> 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) - { - 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 &input_frame, - bool simulated, - string force) - { - vector 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 &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 &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. - 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 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)); -} MeterCommonImplementation::MeterCommonImplementation(MeterInfo &mi, string driver) : @@ -780,14 +219,6 @@ MeterCommonImplementation::MeterCommonImplementation(MeterInfo &mi, force_mfct_index_ = di.forceMfctIndex(); } -void MeterCommonImplementation::addConversions(std::vector 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 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 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 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 setValueFunc, function 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 setValueFunc, function 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 setValueFunc, function 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 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 &fields, vector &cs, bool hr, +string concatAllFields(Meter *m, Telegram *t, char c, vector &fields, bool hr, vector *extra_constant_fields) { string s; @@ -1613,7 +1069,7 @@ string concatAllFields(Meter *m, Telegram *t, char c, vector &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 &fields, vector &cs, bool human_readable) + vector &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 *extra return false; } -string concatFields(Meter *m, Telegram *t, char c, vector &prints, vector &cs, bool human_readable, +string concatFields(Meter *m, Telegram *t, char c, vector &prints, bool human_readable, vector *selected_fields, vector *extra_constant_fields) { if (selected_fields == NULL || selected_fields->size() == 0) @@ -1774,7 +1230,7 @@ string concatFields(Meter *m, Telegram *t, char c, vector &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 &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(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(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 *conversions) +string FieldInfo::renderJson(Meter *m, DVEntry *dve) { string s; @@ -2213,17 +1719,6 @@ string FieldInfo::renderJson(Meter *m, DVEntry *dve, vector *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 *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 createMeter(MeterInfo *mi) if (di != NULL) { shared_ptr newm = di->construct(*mi); - newm->addConversions(mi->conversions); for (string &j : mi->extra_calculated_fields) { newm->addExtraCalculatedField(j); @@ -2569,7 +2055,6 @@ shared_ptr 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; diff --git a/src/meters.h b/src/meters.h index 3dd7214..b088ffe 100644 --- a/src/meters.h +++ b/src/meters.h @@ -146,7 +146,6 @@ struct MeterInfo vector shells; vector extra_constant_fields; // Additional static fields that are added to each message. vector extra_calculated_fields; // Additional field calculated using formulas. - vector conversions; // Additional units desired in json. vector 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 *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_; + shared_ptr formula_; // For the generated field name. - unique_ptr field_name_; + shared_ptr 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 cs) = 0; virtual void addExtraCalculatedField(std::string ecf) = 0; - virtual vector& conversions() = 0; virtual void addShell(std::string cmdline) = 0; virtual vector &shellCmdlines() = 0; virtual void poll(shared_ptr 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; }; diff --git a/src/meters_common_implementation.h b/src/meters_common_implementation.h index a68f062..30fa676 100644 --- a/src/meters_common_implementation.h +++ b/src/meters_common_implementation.h @@ -94,8 +94,6 @@ protected: void triggerUpdate(Telegram *t); void setExpectedELLSecurityMode(ELLSecurityMode dsm); void setExpectedTPLSecurityMode(TPLSecurityMode tsm); - void addConversions(std::vector cs); - std::vector& conversions() { return conversions_; } void addShell(std::string cmdline); void addExtraConstantField(std::string ecf); std::vector &shellCmdlines(); @@ -167,6 +165,13 @@ protected: function setValueFunc, // Use the SET macro above. function 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 *more_json, // Add this json "key"="value" strings. vector *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 conversions_; vector field_infos_; vector field_names_; // Defaults to a setting specified in the driver. Can be overridden in the meter file. diff --git a/test.sh b/test.sh index 03214b0..61c3463 100755 --- a/test.sh +++ b/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 diff --git a/tests/config4/etc/wmbusmeters.conf b/tests/config4/etc/wmbusmeters.conf index 2de847f..c470a29 100644 --- a/tests/config4/etc/wmbusmeters.conf +++ b/tests/config4/etc/wmbusmeters.conf @@ -2,4 +2,3 @@ loglevel=normal device=simulations/simulation_conversionsadded.txt logtelegrams=false format=json -addconversions=GJ,L,F diff --git a/tests/config4/etc/wmbusmeters.d/Hettan b/tests/config4/etc/wmbusmeters.d/Hettan index b00dfb2..f7ce3c1 100644 --- a/tests/config4/etc/wmbusmeters.d/Hettan +++ b/tests/config4/etc/wmbusmeters.d/Hettan @@ -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 diff --git a/tests/config4/etc/wmbusmeters.d/MyTapWater b/tests/config4/etc/wmbusmeters.d/MyTapWater index 0bccd83..b38c898 100644 --- a/tests/config4/etc/wmbusmeters.d/MyTapWater +++ b/tests/config4/etc/wmbusmeters.d/MyTapWater @@ -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 diff --git a/tests/test_config4.sh b/tests/test_config4.sh index 1afc1b2..78c3c40 100755 --- a/tests/test_config4.sh +++ b/tests/test_config4.sh @@ -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 diff --git a/tests/test_conversions.sh b/tests/test_conversions.sh index 9bd622a..13e705f 100755 --- a/tests/test_conversions.sh +++ b/tests/test_conversions.sh @@ -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 diff --git a/tests/test_fields.sh b/tests/test_fields.sh index 9231519..7e2a3ba 100755 --- a/tests/test_fields.sh +++ b/tests/test_fields.sh @@ -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 diff --git a/wmbusmeters.1 b/wmbusmeters.1 index 4659c39..f454873 100644 --- a/wmbusmeters.1 +++ b/wmbusmeters.1 @@ -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[,] 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 invokes cmdline when an alarm triggers