Ignoreduplices enabled by defailt. New C++ object create for each unique meter, despite wildcards.

pull/267/head
Fredrik Öhrström 2021-02-20 22:21:01 +01:00
rodzic 73c4085a00
commit 301c91ea68
19 zmienionych plików z 322 dodań i 106 usunięć

18
CHANGES
Wyświetl plik

@ -1,4 +1,20 @@
Version 1.0.6: 2021-02-20
Ignore duplicates is now enabled by default. Turn it off with --ignoreduplicates=false
Fixed problem with state maintaining meter drivers and wildcard ids.
A HCA driver was producing telegrams with values that jumped up and down.
This was due to the fact that the meter state in the driver was updated
through multiple different telegrams (this is how the meter works).
Clearly a single state object could not properly maintain the state for all
possible meters matching the wildcard, the state became a random mix
of whater telegrams arrived.
The fix now, is that a meter C++ object for a uniqe id (not wildcard)
is created only when the first telegram arrives that matches the wildcard.
Thus each meter will have its own C++ object, in which the correct state
is maintained.
Version 1.1.0: 2021-02-20
Vincent Privat added code for properly decoding several types of izar meter. Vincent Privat added code for properly decoding several types of izar meter.
He also added full support for the heat meter sharky 775! He also added full support for the heat meter sharky 775!

Wyświetl plik

@ -119,7 +119,26 @@ METER_OBJS:=\
$(BUILD)/dvparser.o \ $(BUILD)/dvparser.o \
$(BUILD)/mbus_rawtty.o \ $(BUILD)/mbus_rawtty.o \
$(BUILD)/meters.o \ $(BUILD)/meters.o \
$(BUILD)/manufacturer_specificities.o \
$(BUILD)/printer.o \
$(BUILD)/rtlsdr.o \
$(BUILD)/serial.o \
$(BUILD)/shell.o \
$(BUILD)/sha256.o \
$(BUILD)/threads.o \
$(BUILD)/util.o \
$(BUILD)/units.o \
$(BUILD)/wmbus.o \
$(BUILD)/meter_amiplus.o \ $(BUILD)/meter_amiplus.o \
$(BUILD)/wmbus_amb8465.o \
$(BUILD)/wmbus_im871a.o \
$(BUILD)/wmbus_cul.o \
$(BUILD)/wmbus_rtlwmbus.o \
$(BUILD)/wmbus_rtl433.o \
$(BUILD)/wmbus_simulator.o \
$(BUILD)/wmbus_rawtty.o \
$(BUILD)/wmbus_rc1180.o \
$(BUILD)/wmbus_utils.o \
$(BUILD)/meter_apator08.o \ $(BUILD)/meter_apator08.o \
$(BUILD)/meter_apator162.o \ $(BUILD)/meter_apator162.o \
$(BUILD)/meter_cma12w.o \ $(BUILD)/meter_cma12w.o \
@ -170,25 +189,6 @@ METER_OBJS:=\
$(BUILD)/meter_whe5x.o \ $(BUILD)/meter_whe5x.o \
$(BUILD)/meter_sensostar.o \ $(BUILD)/meter_sensostar.o \
$(BUILD)/meter_gransystems_ccx01.o \ $(BUILD)/meter_gransystems_ccx01.o \
$(BUILD)/manufacturer_specificities.o \
$(BUILD)/printer.o \
$(BUILD)/rtlsdr.o \
$(BUILD)/serial.o \
$(BUILD)/shell.o \
$(BUILD)/sha256.o \
$(BUILD)/threads.o \
$(BUILD)/util.o \
$(BUILD)/units.o \
$(BUILD)/wmbus.o \
$(BUILD)/wmbus_amb8465.o \
$(BUILD)/wmbus_im871a.o \
$(BUILD)/wmbus_cul.o \
$(BUILD)/wmbus_rtlwmbus.o \
$(BUILD)/wmbus_rtl433.o \
$(BUILD)/wmbus_simulator.o \
$(BUILD)/wmbus_rawtty.o \
$(BUILD)/wmbus_rc1180.o \
$(BUILD)/wmbus_utils.o
all: $(BUILD)/wmbusmeters $(BUILD)/wmbusmeters-admin $(BUILD)/testinternals all: $(BUILD)/wmbusmeters $(BUILD)/wmbusmeters-admin $(BUILD)/testinternals
@$(STRIP_BINARY) @$(STRIP_BINARY)

Wyświetl plik

@ -76,7 +76,7 @@ shell=/usr/bin/mosquitto_pub -h localhost -t wmbusmeters/$METER_ID -m "$METER_JS
alarmshell=/usr/bin/mosquitto_pub -h localhost -t wmbusmeters_alarm -m "$ALARM_TYPE $ALARM_MESSAGE" alarmshell=/usr/bin/mosquitto_pub -h localhost -t wmbusmeters_alarm -m "$ALARM_TYPE $ALARM_MESSAGE"
alarmtimeout=1h alarmtimeout=1h
alarmexpectedactivity=mon-sun(00-23) alarmexpectedactivity=mon-sun(00-23)
ignoreduplicates=false ignoreduplicates=true
``` ```
Then add a meter file in /etc/wmbusmeters.d/MyTapWater Then add a meter file in /etc/wmbusmeters.d/MyTapWater
@ -172,7 +172,7 @@ As <options> you can use:
--listmeters=<search> list all meter types containing the text <search> --listmeters=<search> list all meter types containing the text <search>
--logfile=<file> use this file instead of stdout --logfile=<file> use this file instead of stdout
--logtelegrams log the contents of the telegrams for easy replay --logtelegrams log the contents of the telegrams for easy replay
--ignoreduplicates ignore duplicate telegrams, remember the last 10 telegrams --ignoreduplicates=<bool> ignore duplicate telegrams, remember the last 10 telegrams
--meterfiles=<dir> store meter readings in dir --meterfiles=<dir> store meter readings in dir
--meterfilesaction=(overwrite|append) overwrite or append to the meter readings file --meterfilesaction=(overwrite|append) overwrite or append to the meter readings file
--meterfilesnaming=(name|id|name-id) the meter file is the meter's: name, id or name-id --meterfilesnaming=(name|id|name-id) the meter file is the meter's: name, id or name-id

Wyświetl plik

@ -374,7 +374,25 @@ shared_ptr<Configuration> parseCommandLine(int argc, char **argv) {
continue; continue;
} }
if (!strncmp(argv[i], "--ignoreduplicates", 18)) { if (!strncmp(argv[i], "--ignoreduplicates", 18)) {
c->ignore_duplicate_telegrams = true; if (argv[i][18] == 0)
{
c->ignore_duplicate_telegrams = true;
}
else
{
if (!strcmp(argv[i]+18, "=true"))
{
c->ignore_duplicate_telegrams = true;
}
else if (!strcmp(argv[i]+18, "=false"))
{
c->ignore_duplicate_telegrams = false;
}
else
{
error("You must specify true or false after --ignoreduplicates=\n");
}
}
i++; i++;
continue; continue;
} }
@ -564,7 +582,8 @@ shared_ptr<Configuration> parseCommandLine(int argc, char **argv) {
if (!isValidMatchExpressions(id, true)) error("Not a valid id nor a valid meter match expression \"%s\"\n", id.c_str()); if (!isValidMatchExpressions(id, true)) error("Not a valid id nor a valid meter match expression \"%s\"\n", id.c_str());
if (!isValidKey(key, mt)) error("Not a valid meter key \"%s\"\n", key.c_str()); if (!isValidKey(key, mt)) error("Not a valid meter key \"%s\"\n", key.c_str());
vector<string> no_meter_shells, no_meter_jsons; vector<string> no_meter_shells, no_meter_jsons;
c->meters.push_back(MeterInfo(bus, name, type, id, key, modes, bps, no_meter_shells, no_meter_jsons)); vector<string> ids = splitMatchExpressions(id);
c->meters.push_back(MeterInfo(bus, name, mt, ids, key, modes, bps, no_meter_shells, no_meter_jsons));
} }
return shared_ptr<Configuration>(c); return shared_ptr<Configuration>(c);

Wyświetl plik

@ -177,7 +177,8 @@ void parseMeterConfig(Configuration *c, vector<char> &buf, string file)
use = false; use = false;
} }
if (use) { if (use) {
c->meters.push_back(MeterInfo(bus, name, type, id, key, modes, bps, telegram_shells, jsons)); vector<string> ids = splitMatchExpressions(id);
c->meters.push_back(MeterInfo(bus, name, mt, ids, key, modes, bps, telegram_shells, jsons));
} }
return; return;
@ -659,7 +660,7 @@ LinkModeCalculationResult calculateLinkModes(Configuration *config, WMBus *wmbus
{ {
meters_union.unionLinkModeSet(m.link_modes); meters_union.unionLinkModeSet(m.link_modes);
string meter = m.link_modes.hr(); string meter = m.link_modes.hr();
debug("(config) meter %s link mode(s): %s\n", m.type.c_str(), meter.c_str()); debug("(config) meter %s link mode(s): %s\n", toMeterName(m.type).c_str(), meter.c_str());
} }
string metersu = meters_union.hr(); string metersu = meters_union.hr();
debug("(config) all possible link modes that the meters might transmit on: %s\n", metersu.c_str()); debug("(config) all possible link modes that the meters might transmit on: %s\n", metersu.c_str());

Wyświetl plik

@ -67,7 +67,7 @@ struct Configuration
MeterFileTimestamp meterfiles_timestamp {}; // Default is never. MeterFileTimestamp meterfiles_timestamp {}; // Default is never.
bool use_logfile {}; bool use_logfile {};
bool use_stderr_for_log = true; // Default is to use stderr for logging. bool use_stderr_for_log = true; // Default is to use stderr for logging.
bool ignore_duplicate_telegrams = false; // Default is to report all telegrams. bool ignore_duplicate_telegrams = true; // Default is to ignore duplicates.
std::string logfile; std::string logfile;
bool json {}; bool json {};
bool fields {}; bool fields {};

Wyświetl plik

@ -691,7 +691,8 @@ void list_shell_envs(Configuration *config, string meter_type)
vector<string> envs; vector<string> envs;
Telegram t; Telegram t;
MeterInfo mi; MeterInfo mi;
shared_ptr<Meter> meter = createMeter(config, toMeterType(meter_type), &mi); mi.type = toMeterType(meter_type);
shared_ptr<Meter> meter = createMeter(&mi);
meter->printMeter(&t, meter->printMeter(&t,
&ignore1, &ignore1,
&ignore2, config->separator, &ignore2, config->separator,
@ -711,7 +712,8 @@ void list_shell_envs(Configuration *config, string meter_type)
void list_fields(Configuration *config, string meter_type) void list_fields(Configuration *config, string meter_type)
{ {
MeterInfo mi; MeterInfo mi;
shared_ptr<Meter> meter = createMeter(config, toMeterType(meter_type), &mi); mi.type = toMeterType(meter_type);
shared_ptr<Meter> meter = createMeter(&mi);
int width = 0; int width = 0;
for (auto &p : meter->prints()) for (auto &p : meter->prints())
@ -1112,8 +1114,8 @@ void setup_meters(Configuration *config, MeterManager *manager)
{ {
for (auto &m : config->meters) for (auto &m : config->meters)
{ {
auto meter = createMeter(config, toMeterType(m.type), &m); m.conversions = config->conversions;
manager->addMeter(meter); manager->addMeterTemplate(m);
} }
} }
@ -1154,24 +1156,20 @@ bool start(Configuration *config)
// or sent to shell invocations. // or sent to shell invocations.
printer_ = create_printer(config); printer_ = create_printer(config);
meter_manager_ = createMeterManager(); meter_manager_ = createMeterManager(config->daemon);
// When a meter is updated, print it, shell it, log it, etc.
meter_manager_->whenMeterUpdated(
[&](Telegram *t,Meter *meter)
{
printer_->print(t, meter, &config->jsons, &config->selected_fields);
oneshot_check(config, t, meter);
}
);
// Create the Meter objects from the configuration. // Create the Meter objects from the configuration.
setup_meters(config, meter_manager_.get()); setup_meters(config, meter_manager_.get());
// Attach a received-telegram-callback from the meter and
// attach it to the printer.
meter_manager_->forEachMeter(
[&](Meter *meter)
{
meter->onUpdate([&](Telegram *t,Meter *meter)
{
printer_->print(t, meter, &config->jsons, &config->selected_fields);
oneshot_check(config, t, meter);
});
}
);
// Detect and initialize any devices. // Detect and initialize any devices.
// Future changes are triggered through this callback. // Future changes are triggered through this callback.
printed_warning_ = true; printed_warning_ = true;

Wyświetl plik

@ -30,9 +30,23 @@
struct MeterManagerImplementation : public virtual MeterManager struct MeterManagerImplementation : public virtual MeterManager
{ {
private:
bool is_daemon_ {};
vector<MeterInfo> meter_templates_;
vector<shared_ptr<Meter>> meters_;
function<void(AboutTelegram&,vector<uchar>)> on_telegram_;
function<void(Telegram*t,Meter*)> on_meter_updated_;
public:
void addMeterTemplate(MeterInfo &mi)
{
meter_templates_.push_back(mi);
}
void addMeter(shared_ptr<Meter> meter) void addMeter(shared_ptr<Meter> meter)
{ {
meters_.push_back(meter); meters_.push_back(meter);
meter->setIndex(meters_.size());
} }
Meter *lastAddedMeter() Meter *lastAddedMeter()
@ -65,28 +79,115 @@ struct MeterManagerImplementation : public virtual MeterManager
bool hasMeters() bool hasMeters()
{ {
return meters_.size() != 0; return meters_.size() != 0 || meter_templates_.size() != 0;
} }
bool handleTelegram(AboutTelegram &about, vector<uchar> data, bool simulated) bool handleTelegram(AboutTelegram &about, vector<uchar> input_frame, bool simulated)
{ {
if (!hasMeters()) if (!hasMeters())
{ {
if (on_telegram_) if (on_telegram_)
{ {
on_telegram_(about, data); on_telegram_(about, input_frame);
} }
return true; return true;
} }
bool handled = false; bool handled = false;
bool exact_id_match = false;
string ids; string ids;
for (auto &m : meters_) for (auto &m : meters_)
{ {
bool h = m->handleTelegram(about, data, simulated, &ids); bool h = m->handleTelegram(about, input_frame, simulated, &ids, &exact_id_match);
if (h) handled = true; 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 tmp = 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());
tmp.ids = tmp_ids;
tmp.idsc = t.ids.back();
// Now build a meter object with for this exact id.
auto meter = createMeter(&tmp);
meter->onUpdate(on_meter_updated_);
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(),
toMeterName(mi.type).c_str(),
idsc.c_str());
if (is_daemon_)
{
notice("(wmbusmeters) started meter %d (%s %s %s)\n",
meter->index(),
mi.name.c_str(),
tmp.idsc.c_str(),
toMeterName(mi.type).c_str());
}
else
{
verbose("(meter) started meter %d (%s %s %s)\n",
meter->index(),
mi.name.c_str(),
tmp.idsc.c_str(),
toMeterName(mi.type).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(), toMeterName(meter->type()).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(), toMeterName(meter->type()).c_str());
}
else
{
handled = true;
}
}
}
}
}
if (isVerboseEnabled() && !handled) if (isVerboseEnabled() && !handled)
{ {
verbose("(wmbus) telegram from %s ignored by all configured meters!\n", ids.c_str()); verbose("(wmbus) telegram from %s ignored by all configured meters!\n", ids.c_str());
@ -98,32 +199,31 @@ struct MeterManagerImplementation : public virtual MeterManager
{ {
on_telegram_ = cb; on_telegram_ = cb;
} }
void whenMeterUpdated(std::function<void(Telegram*t,Meter*)> cb)
{
on_meter_updated_ = cb;
}
MeterManagerImplementation(bool daemon) : is_daemon_(daemon) {}
~MeterManagerImplementation() {} ~MeterManagerImplementation() {}
private:
vector<shared_ptr<Meter>> meters_;
function<void(AboutTelegram&,vector<uchar>)> on_telegram_;
}; };
shared_ptr<MeterManager> createMeterManager() shared_ptr<MeterManager> createMeterManager(bool daemon)
{ {
return shared_ptr<MeterManager>(new MeterManagerImplementation); return shared_ptr<MeterManager>(new MeterManagerImplementation(daemon));
} }
MeterCommonImplementation::MeterCommonImplementation(MeterInfo &mi, MeterCommonImplementation::MeterCommonImplementation(MeterInfo &mi,
MeterType type) : MeterType type) :
type_(type), name_(mi.name) type_(type), name_(mi.name)
{ {
ids_ = splitMatchExpressions(mi.id); ids_ = mi.ids;
idsc_ = toIdsCommaSeparated(ids_);
if (mi.key.length() > 0) if (mi.key.length() > 0)
{ {
hex2bin(mi.key, &meter_keys_.confidentiality_key); hex2bin(mi.key, &meter_keys_.confidentiality_key);
} }
/*if (bus->type() == DEVICE_SIMULATION)
{
meter_keys_.simulation = true;
}*/
for (auto s : mi.shells) { for (auto s : mi.shells) {
addShell(s); addShell(s);
} }
@ -195,11 +295,16 @@ void MeterCommonImplementation::addPrint(string vname, Quantity vquantity,
prints_.push_back( { vname, vquantity, defaultUnitForQuantity(vquantity), NULL, getValueFunc, help, field, json, vname } ); prints_.push_back( { vname, vquantity, defaultUnitForQuantity(vquantity), NULL, getValueFunc, help, field, json, vname } );
} }
vector<string> MeterCommonImplementation::ids() vector<string>& MeterCommonImplementation::ids()
{ {
return ids_; return ids_;
} }
string MeterCommonImplementation::idsc()
{
return idsc_;
}
vector<string> MeterCommonImplementation::fields() vector<string> MeterCommonImplementation::fields()
{ {
return fields_; return fields_;
@ -266,23 +371,46 @@ LIST_OF_METERS
return LinkModeSet(); return LinkModeSet();
} }
bool MeterCommonImplementation::isTelegramForMe(Telegram *t) bool MeterCommonImplementation::isTelegramForMeter(Telegram *t, Meter *meter, MeterInfo *mi)
{ {
debug("(meter) %s: for me? %s\n", name_.c_str(), t->idsc.c_str()); string name;
vector<string> ids;
string idsc;
MeterType type;
assert((meter && !mi) ||
(!meter && mi));
if (meter)
{
name = meter->name();
ids = meter->ids();
idsc = meter->idsc();
type = meter->type();
}
else
{
name = mi->name;
ids = mi->ids;
idsc = mi->idsc;
type = mi->type;
}
debug("(meter) %s: for me? %s\n", name.c_str(), idsc.c_str());
bool used_wildcard = false; bool used_wildcard = false;
bool id_match = doesIdsMatchExpressions(t->ids, ids_, &used_wildcard); bool id_match = doesIdsMatchExpressions(t->ids, ids, &used_wildcard);
if (!id_match) { if (!id_match) {
// The id must match. // The id must match.
debug("(meter) %s: not for me: not my id\n", name_.c_str()); debug("(meter) %s: not for me: not my id\n", name.c_str());
return false; return false;
} }
bool valid_driver = isMeterDriverValid(type_, t->dll_mfct, t->dll_type, t->dll_version); bool valid_driver = isMeterDriverValid(type, t->dll_mfct, t->dll_type, t->dll_version);
if (!valid_driver && t->tpl_id_found) if (!valid_driver && t->tpl_id_found)
{ {
valid_driver = isMeterDriverValid(type_, t->tpl_mfct, t->tpl_type, t->tpl_version); valid_driver = isMeterDriverValid(type, t->tpl_mfct, t->tpl_type, t->tpl_version);
} }
if (!valid_driver) if (!valid_driver)
@ -309,8 +437,8 @@ bool MeterCommonImplementation::isTelegramForMe(Telegram *t)
string possible_drivers = t->autoDetectPossibleDrivers(); string possible_drivers = t->autoDetectPossibleDrivers();
warning("(meter) %s: meter detection did not match the selected driver %s! correct driver is: %s\n" warning("(meter) %s: meter detection did not match the selected driver %s! correct driver is: %s\n"
"(meter) Not printing this warning agin for id: %02x%02x%02x%02x mfct: (%s) %s (0x%02x) type: %s (0x%02x) ver: 0x%02x\n", "(meter) Not printing this warning agin for id: %02x%02x%02x%02x mfct: (%s) %s (0x%02x) type: %s (0x%02x) ver: 0x%02x\n",
name_.c_str(), name.c_str(),
toMeterName(type()).c_str(), toMeterName(type).c_str(),
possible_drivers.c_str(), possible_drivers.c_str(),
t->dll_id_b[3], t->dll_id_b[2], t->dll_id_b[1], t->dll_id_b[0], t->dll_id_b[3], t->dll_id_b[2], t->dll_id_b[1], t->dll_id_b[0],
manufacturerFlag(t->dll_mfct).c_str(), manufacturerFlag(t->dll_mfct).c_str(),
@ -327,7 +455,7 @@ bool MeterCommonImplementation::isTelegramForMe(Telegram *t)
} }
} }
debug("(meter) %s: yes for me\n", name_.c_str()); debug("(meter) %s: yes for me\n", name.c_str());
return true; return true;
} }
@ -356,6 +484,16 @@ uint16_t MeterCommonImplementation::getRecordAsUInt16(string record)
return 0; return 0;
} }
int MeterCommonImplementation::index()
{
return index_;
}
void MeterCommonImplementation::setIndex(int i)
{
index_ = i;
}
void MeterCommonImplementation::triggerUpdate(Telegram *t) void MeterCommonImplementation::triggerUpdate(Telegram *t)
{ {
datetime_of_update_ = time(NULL); datetime_of_update_ = time(NULL);
@ -486,7 +624,7 @@ string concatFields(Meter *m, Telegram *t, char c, vector<Print> &prints, vector
return s; return s;
} }
bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<uchar> input_frame, bool simulated, string *ids) bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<uchar> input_frame, bool simulated, string *ids, bool *id_match)
{ {
Telegram t; Telegram t;
t.about = about; t.about = about;
@ -496,12 +634,13 @@ bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<ucha
*ids = t.idsc; *ids = t.idsc;
if (!ok || !isTelegramForMe(&t)) if (!ok || !isTelegramForMeter(&t, this, NULL))
{ {
// This telegram is not intended for this meter. // This telegram is not intended for this meter.
return false; return false;
} }
*id_match = true;
verbose("(meter) %s %s handling telegram from %s\n", name().c_str(), meterName().c_str(), t.ids.back().c_str()); verbose("(meter) %s %s handling telegram from %s\n", name().c_str(), meterName().c_str(), t.ids.back().c_str());
if (isDebugEnabled()) if (isDebugEnabled())
@ -738,28 +877,28 @@ METER_DETECTION
return false; return false;
} }
shared_ptr<Meter> createMeter(Configuration *config, MeterType type, MeterInfo *mi) shared_ptr<Meter> createMeter(MeterInfo *mi)
{ {
shared_ptr<Meter> newm; shared_ptr<Meter> newm;
const char *keymsg = (mi->key[0] == 0) ? "not-encrypted" : "encrypted"; const char *keymsg = (mi->key[0] == 0) ? "not-encrypted" : "encrypted";
switch (type) switch (mi->type)
{ {
#define X(mname,link,info,type,cname) \ #define X(mname,link,info,type,cname) \
case MeterType::type: \ case MeterType::type: \
{ \ { \
newm = create##cname(*mi); \ newm = create##cname(*mi); \
newm->addConversions(config->conversions); \ newm->addConversions(mi->conversions); \
verbose("(main) configured \"%s\" \"" #mname "\" \"%s\" %s\n", \ verbose("(meter) created \"%s\" \"" #mname "\" \"%s\" %s\n", \
mi->name.c_str(), mi->id.c_str(), keymsg); \ mi->name.c_str(), mi->idsc.c_str(), keymsg); \
return newm; \ return newm; \
} \ } \
break; break;
LIST_OF_METERS LIST_OF_METERS
#undef X #undef X
case MeterType::UNKNOWN: case MeterType::UNKNOWN:
error("No such meter type \"%s\"\n", mi->type.c_str()); error("No such meter type \"%s\"\n", toMeterName(mi->type).c_str());
break; break;
} }
return newm; return newm;

Wyświetl plik

@ -22,8 +22,9 @@
#include"units.h" #include"units.h"
#include"wmbus.h" #include"wmbus.h"
#include<string>
#include<functional> #include<functional>
#include<numeric>
#include<string>
#include<vector> #include<vector>
#define LIST_OF_METERS \ #define LIST_OF_METERS \
@ -201,24 +202,27 @@ struct MeterInfo
// A bus can be an mbus or a wmbus dongle. // A bus can be an mbus or a wmbus dongle.
// The bus can be the empty string, which means that it will fallback to the first defined bus. // The bus can be the empty string, which means that it will fallback to the first defined bus.
string name; // User specified name of this (group of) meters. string name; // User specified name of this (group of) meters.
string type; // Driver MeterType type {}; // Driver
string id; // How to identify the meter on the bus. vector<string> ids; // Match expressions for ids.
string idsc; // Comma separated ids.
string key; // Decryption key. string key; // Decryption key.
LinkModeSet link_modes; LinkModeSet link_modes;
int bps {}; // For mbus communication you need to know the baud rate. int bps {}; // For mbus communication you need to know the baud rate.
vector<string> shells; vector<string> shells;
vector<string> jsons; // Additional static jsons that are added to each message. vector<string> jsons; // Additional static jsons that are added to each message.
vector<Unit> conversions; // Additional units desired in json.
MeterInfo() MeterInfo()
{ {
} }
MeterInfo(string b, string n, string t, string i, string k, LinkModeSet lms, int baud, vector<string> &s, vector<string> &j) MeterInfo(string b, string n, MeterType t, vector<string> i, string k, LinkModeSet lms, int baud, vector<string> &s, vector<string> &j)
{ {
bus = b; bus = b;
name = n; name = n;
type = t; type = t;
id = i; ids = i;
idsc = toIdsCommaSeparated(ids);
key = k; key = k;
shells = s; shells = s;
jsons = j; jsons = j;
@ -242,8 +246,14 @@ struct Print
struct Meter struct Meter
{ {
// Meters are instantiated on the fly from a template, when a telegram arrives
// and no exact meter exists. Index 1 is the first meter created etc.
virtual int index() = 0;
virtual void setIndex(int i) = 0;
// This meter listens to these ids. // This meter listens to these ids.
virtual vector<string> ids() = 0; virtual vector<string> &ids() = 0;
// Comma separated ids.
virtual string idsc() = 0;
// This meter can report these fields, like total_m3, temp_c. // This meter can report these fields, like total_m3, temp_c.
virtual vector<string> fields() = 0; virtual vector<string> fields() = 0;
virtual vector<Print> prints() = 0; virtual vector<Print> prints() = 0;
@ -267,8 +277,8 @@ struct Meter
// The handleTelegram expects an input_frame where the DLL crcs have been removed. // The handleTelegram expects an input_frame where the DLL crcs have been removed.
// Returns true of this meter handled this telegram! // Returns true of this meter handled this telegram!
virtual bool handleTelegram(AboutTelegram &about, vector<uchar> input_frame, bool simulated, string *id) = 0; // Sets id_match to true, if there was an id match, even though the telegram could not be properly handled.
virtual bool isTelegramForMe(Telegram *t) = 0; virtual bool handleTelegram(AboutTelegram &about, vector<uchar> input_frame, bool simulated, string *id, bool *id_match) = 0;
virtual MeterKeys *meterKeys() = 0; virtual MeterKeys *meterKeys() = 0;
// Dynamically access all data received for the meter. // Dynamically access all data received for the meter.
@ -285,6 +295,7 @@ struct Meter
struct MeterManager struct MeterManager
{ {
virtual void addMeterTemplate(MeterInfo &mi) = 0;
virtual void addMeter(shared_ptr<Meter> meter) = 0; virtual void addMeter(shared_ptr<Meter> meter) = 0;
virtual Meter*lastAddedMeter() = 0; virtual Meter*lastAddedMeter() = 0;
virtual void removeAllMeters() = 0; virtual void removeAllMeters() = 0;
@ -293,10 +304,12 @@ struct MeterManager
virtual bool hasAllMetersReceivedATelegram() = 0; virtual bool hasAllMetersReceivedATelegram() = 0;
virtual bool hasMeters() = 0; virtual bool hasMeters() = 0;
virtual void onTelegram(function<void(AboutTelegram&,vector<uchar>)> cb) = 0; virtual void onTelegram(function<void(AboutTelegram&,vector<uchar>)> cb) = 0;
virtual void whenMeterUpdated(std::function<void(Telegram*t,Meter*)> cb) = 0;
virtual ~MeterManager() = default; virtual ~MeterManager() = default;
}; };
shared_ptr<MeterManager> createMeterManager(); shared_ptr<MeterManager> createMeterManager(bool daemon);
struct WaterMeter : public virtual Meter struct WaterMeter : public virtual Meter
{ {
@ -401,6 +414,6 @@ Generic *createGeneric(WMBus *bus, MeterInfo &m);
struct Configuration; struct Configuration;
struct MeterInfo; struct MeterInfo;
shared_ptr<Meter> createMeter(Configuration *config, MeterType type, MeterInfo *mi); shared_ptr<Meter> createMeter(MeterInfo *mi);
#endif #endif

Wyświetl plik

@ -26,9 +26,12 @@
struct MeterCommonImplementation : public virtual Meter struct MeterCommonImplementation : public virtual Meter
{ {
vector<string> ids(); int index();
vector<string> fields(); void setIndex(int i);
vector<Print> prints(); vector<string>& ids();
string idsc();
vector<string> fields();
vector<Print> prints();
string name(); string name();
MeterType type(); MeterType type();
@ -41,7 +44,7 @@ struct MeterCommonImplementation : public virtual Meter
void onUpdate(function<void(Telegram*,Meter*)> cb); void onUpdate(function<void(Telegram*,Meter*)> cb);
int numUpdates(); int numUpdates();
bool isTelegramForMe(Telegram *t); static bool isTelegramForMeter(Telegram *t, Meter *meter, MeterInfo *mi);
MeterKeys *meterKeys(); MeterKeys *meterKeys();
std::vector<std::string> getRecords(); std::vector<std::string> getRecords();
@ -74,7 +77,7 @@ protected:
// Print the dimensionless Text quantity, no unit is needed. // Print the dimensionless Text quantity, no unit is needed.
void addPrint(string vname, Quantity vquantity, void addPrint(string vname, Quantity vquantity,
function<std::string()> getValueFunc, string help, bool field, bool json); function<std::string()> getValueFunc, string help, bool field, bool json);
bool handleTelegram(AboutTelegram &about, vector<uchar> frame, bool simulated, string *id); bool handleTelegram(AboutTelegram &about, vector<uchar> frame, bool simulated, string *id, bool *id_match);
void printMeter(Telegram *t, void printMeter(Telegram *t,
string *human_readable, string *human_readable,
string *fields, char separator, string *fields, char separator,
@ -87,12 +90,14 @@ protected:
private: private:
int index_ {};
MeterType type_ {}; MeterType type_ {};
MeterKeys meter_keys_ {}; MeterKeys meter_keys_ {};
ELLSecurityMode expected_ell_sec_mode_ {}; ELLSecurityMode expected_ell_sec_mode_ {};
TPLSecurityMode expected_tpl_sec_mode_ {}; TPLSecurityMode expected_tpl_sec_mode_ {};
string name_; string name_;
vector<string> ids_; vector<string> ids_;
string idsc_;
vector<function<void(Telegram*,Meter*)>> on_update_; vector<function<void(Telegram*,Meter*)>> on_update_;
int num_updates_ {}; int num_updates_ {};
time_t datetime_of_update_ {}; time_t datetime_of_update_ {};

Wyświetl plik

@ -263,11 +263,16 @@ int test_linkmodes()
Configuration apator_config; Configuration apator_config;
string apator162 = "apator162"; string apator162 = "apator162";
apator_config.meters.push_back(MeterInfo("", "m1", apator162, "12345678", "", vector<string> ids = { "12345678" };
toMeterLinkModeSet(apator162), apator_config.meters.push_back(MeterInfo("", // bus
0, "m1", // name
no_meter_shells, MeterType::APATOR162, // driver/type
no_meter_jsons)); ids, // ids
"", // Key
toMeterLinkModeSet(apator162), // link mode set
0, // baud
no_meter_shells, // shells
no_meter_jsons)); // jsons
// Check that if no explicit link modes are provided to apator162, then // Check that if no explicit link modes are provided to apator162, then
// automatic deduction will fail, since apator162 can be configured to transmit // automatic deduction will fail, since apator162 can be configured to transmit
@ -301,12 +306,13 @@ int test_linkmodes()
Configuration multical21_and_supercom587_config; Configuration multical21_and_supercom587_config;
string multical21 = "multical21"; string multical21 = "multical21";
string supercom587 = "supercom587"; string supercom587 = "supercom587";
multical21_and_supercom587_config.meters.push_back(MeterInfo("", "m1", multical21, "12345678", "",
multical21_and_supercom587_config.meters.push_back(MeterInfo("", "m1", MeterType::MULTICAL21, ids, "",
toMeterLinkModeSet(multical21), toMeterLinkModeSet(multical21),
0, 0,
no_meter_shells, no_meter_shells,
no_meter_jsons)); no_meter_jsons));
multical21_and_supercom587_config.meters.push_back(MeterInfo("", "m2", supercom587, "12345678", "", multical21_and_supercom587_config.meters.push_back(MeterInfo("", "m2", MeterType::SUPERCOM587, ids, "",
toMeterLinkModeSet(supercom587), toMeterLinkModeSet(supercom587),
0, 0,
no_meter_shells, no_meter_shells,

Wyświetl plik

@ -725,6 +725,18 @@ bool doesIdMatchExpressions(string id, vector<string>& mes, bool *used_wildcard)
return false; return false;
} }
string toIdsCommaSeparated(std::vector<std::string> &ids)
{
string cs;
for (string& s: ids)
{
cs += s;
cs += ",";
}
if (cs.length() > 0) cs.pop_back();
return cs;
}
bool isValidKey(string& key, MeterType mt) bool isValidKey(string& key, MeterType mt)
{ {
if (key.length() == 0) return true; if (key.length() == 0) return true;

Wyświetl plik

@ -108,6 +108,8 @@ bool isValidMatchExpressions(std::string ids, bool non_compliant);
bool doesIdMatchExpression(std::string id, std::string match_rule); bool doesIdMatchExpression(std::string id, std::string match_rule);
bool doesIdMatchExpressions(std::string id, std::vector<std::string>& match_rules, bool *used_wildcard); bool doesIdMatchExpressions(std::string id, std::vector<std::string>& match_rules, bool *used_wildcard);
bool doesIdsMatchExpressions(std::vector<std::string> &ids, std::vector<std::string>& match_rules, bool *used_wildcard); bool doesIdsMatchExpressions(std::vector<std::string> &ids, std::vector<std::string>& match_rules, bool *used_wildcard);
std::string toIdsCommaSeparated(std::vector<std::string> &ids);
bool isValidId(std::string id, bool accept_non_compliant); bool isValidId(std::string id, bool accept_non_compliant);
bool isValidKey(std::string& key, MeterType mt); bool isValidKey(std::string& key, MeterType mt);

Wyświetl plik

@ -14,3 +14,4 @@ alarmtimeout=4s
# expected to be transmitting. Some meters disable transmissions during nights # expected to be transmitting. Some meters disable transmissions during nights
# and weekends. Change this to mon-fri(08-19) # and weekends. Change this to mon-fri(08-19)
alarmexpectedactivity=mon-sun(00-23) alarmexpectedactivity=mon-sun(00-23)
ignoreduplicates=false

Wyświetl plik

@ -26,6 +26,7 @@ fi
if [ "$TESTRESULT" = "ERROR" ]; then echo ERROR: $TESTNAME; exit 1; fi if [ "$TESTRESULT" = "ERROR" ]; then echo ERROR: $TESTNAME; exit 1; fi
TESTNAME="Test additional shell envs from cmdline" TESTNAME="Test additional shell envs from cmdline"
TESTRESULT="ERROR" TESTRESULT="ERROR"

Wyświetl plik

@ -59,7 +59,7 @@ if [ ! -z "$REST" ]
then then
echo ERROR TELEGRAMS: $TESTNAME echo ERROR TELEGRAMS: $TESTNAME
echo ----------------- echo -----------------
diff /tmp/wmbusmeters_telegram_expected /tmp/wmbusmeters_telegram diff /tmp/wmbusmeters_telegram_expected /tmp/wmbusmeters_telegram_output
echo ----------------- echo -----------------
TESTRESULT="ERROR" TESTRESULT="ERROR"
fi fi

Wyświetl plik

@ -43,7 +43,7 @@ cat > $TEST/test_expected.txt <<EOF
{"media":"smoke detector","meter":"lansensm","name":"Rummet","id":"01000273","status":"OK","timestamp":"1111-11-11T11:11:11Z"} {"media":"smoke detector","meter":"lansensm","name":"Rummet","id":"01000273","status":"OK","timestamp":"1111-11-11T11:11:11Z"}
EOF EOF
$PROG --format=json simulations/simulation_duplicates.txt \ $PROG --format=json --ignoreduplicates=false simulations/simulation_duplicates.txt \
Rummet lansensm 01000273 "" \ Rummet lansensm 01000273 "" \
| grep Rummet > $TEST/test_output.txt | grep Rummet > $TEST/test_output.txt

Wyświetl plik

@ -25,7 +25,7 @@ cat > $TEST/test_expected.txt <<EOF
{"media":"reserved","meter":"lansensm","name":"Forren","id":"00010206","status":"OK","timestamp":"1111-11-11T11:11:11Z"} {"media":"reserved","meter":"lansensm","name":"Forren","id":"00010206","status":"OK","timestamp":"1111-11-11T11:11:11Z"}
EOF EOF
$PROG --format=json --usestdoutforlogging simulations/simulation_unknown.txt $METERS > $TEST/test_output.txt $PROG --format=json --ignoreduplicates=false --usestdoutforlogging simulations/simulation_unknown.txt $METERS > $TEST/test_output.txt
if [ "$?" = "0" ] if [ "$?" = "0" ]
then then
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt

Wyświetl plik

@ -2,7 +2,7 @@
PROG="$1" PROG="$1"
TEST=testaes TEST=testoutput
rm -rf $TEST rm -rf $TEST
mkdir -p $TEST mkdir -p $TEST
@ -29,8 +29,11 @@ fi
cat <<EOF > $TEST/test_expected.txt cat <<EOF > $TEST/test_expected.txt
Started config rtlwmbus on stdin listening on any Started config rtlwmbus on stdin listening on any
(wmbus) decrypted content failed check, did you use the correct decryption key? Permanently ignoring telegrams from id: 88888888 mfct: (APA) Apator, Poland (0x601) type: Water meter (0x07) ver: 0x05 (wmbus) decrypted content failed check, did you use the correct decryption key? Permanently ignoring telegrams from id: 88888888 mfct: (APA) Apator, Poland (0x601) type: Water meter (0x07) ver: 0x05
(meter) newly created meter (ApWater 88888888 apator162) did not handle telegram!
(wmbus) decrypted payload crc failed check, did you use the correct decryption key? 979f payload crc (calculated 3431) Permanently ignoring telegrams from id: 76348799 mfct: (KAM) Kamstrup Energi (0x2c2d) type: Cold water meter (0x16) ver: 0x1b (wmbus) decrypted payload crc failed check, did you use the correct decryption key? 979f payload crc (calculated 3431) Permanently ignoring telegrams from id: 76348799 mfct: (KAM) Kamstrup Energi (0x2c2d) type: Cold water meter (0x16) ver: 0x1b
(meter) newly created meter (Vatten 76348799 multical21) did not handle telegram!
(wmbus) decrypted content failed check, did you use the correct decryption key? Permanently ignoring telegrams from id: 77777777 mfct: (SON) Sontex, Switzerland (0x4dee) type: Water meter (0x07) ver: 0x3c (wmbus) decrypted content failed check, did you use the correct decryption key? Permanently ignoring telegrams from id: 77777777 mfct: (SON) Sontex, Switzerland (0x4dee) type: Water meter (0x07) ver: 0x3c
(meter) newly created meter (Wasser 77777777 supercom587) did not handle telegram!
EOF EOF
diff $TEST/test_expected.txt $TEST/test_stderr.txt diff $TEST/test_expected.txt $TEST/test_stderr.txt