Merge remote-tracking branch 'origin/master' into fb_add_itron_ultramaxx

pull/1091/head
Andreas Horrer 2024-03-02 20:16:32 +01:00
commit 879127ac8f
23 zmienionych plików z 809 dodań i 397 usunięć

12
CHANGES
Wyświetl plik

@ -1,3 +1,15 @@
New improved address specification. E.g. use 12345678.M=KAM.V=1b.T=16
to listen to exactly the telegrams with id 12345678 manufacturer KAM,
version 0x1b and type 0x16. You if you do not specify any M,V or T, they
become wildcards which will be the old default behaviour.
If you receive multiple telegram versions from the same id, and you want to
filter out some versions, do: 12345678,!12345678.V=77
You can now specify p0 to p250, to read from an mbus using the primary address.
E.g. wmbusmeters --pollinterval=5s /dev/ttyUSB1:mbus:2400 TEMP piigth:mbus p0 NOKEY
Version 1.16.1 2024-02-22
Fix docker file generation.

Wyświetl plik

@ -1,4 +1,3 @@
# Copyright (C) 2017-2023 Fredrik Öhrström (gpl-3.0-or-later)
#
# This program is free software: you can redistribute it and/or modify

Wyświetl plik

@ -130,9 +130,11 @@ bus the mbus poll request should be sent to.
wmbusmeters --pollinterval=60s MAIN=/dev/ttyUSB0:mbus:2400 MyTempMeter piigth:MAIN:mbus 12001932 NOKEY
```
If you want to poll an mbus meter using the primary address, just use
a number between 0 and 250 instead of the full 8 digit secondary
address.
If you want to poll an mbus meter using the primary address, use p0 to p250 (deciman numbers)
instead of the full 8 digit secondary address.
```
wmbusmeters --pollinterval=60s MAIN=/dev/ttyUSB0:mbus:2400 MyTempMeter piigth:MAIN:mbus p0 NOKEY
```
# Example wmbusmeter.conf file
@ -217,9 +219,13 @@ The latest reading of the meter can also be found here: `/var/lib/wmbusmeters/me
You can use several ids using `id=1111111,2222222,3333333` or you can listen to all
meters of a certain type `id=*` or you can suffix with star `id=8765*` to match
all meters with a given prefix. If you supply at least one positive match rule, then you
can add negative match rules as well. For example `id=*,!2222*`
can add filter out rules as well. For example `id=*,!2222*`
which will match all meter ids, except those that begin with 2222.
You can also specify the exact manufacturer, version and type: `id=11111111.M=KAM.V=1b.T=16`
or a subset: `id=11111111.T=16` or all telegrams from 22222222 except those with version 77:
`id=22222222,!22222222.V=77`
When matching all meters from the command line you can use `ANYID` instead of `*` to avoid shell quotes.
# Add static and calculated fields to the output

Wyświetl plik

@ -16,10 +16,15 @@
*/
#include"address.h"
#include"manufacturers.h"
#include<assert.h>
using namespace std;
bool isValidMatchExpression(const string& s)
vector<string> splitSequenceOfAddressExpressionsAtCommas(const string& mes);
bool isValidMatchExpression(const string& s, bool *has_wildcard)
{
string me = s;
@ -34,9 +39,12 @@ bool isValidMatchExpression(const string& s)
// A match expression cannot be empty.
if (me.length() == 0) return false;
// An me can be negated with an exclamation mark first.
// An me can be filtered out with an exclamation mark first.
if (me.front() == '!') me.erase(0, 1);
// More than one negation is not allowed.
if (me.front() == '!') return false;
// A match expression cannot be only a negation mark.
if (me.length() == 0) return false;
@ -58,6 +66,7 @@ bool isValidMatchExpression(const string& s)
{
me.erase(0,1);
wildcard_used = true;
if (has_wildcard) *has_wildcard = true;
}
// Now we should have eaten the whole expression.
@ -72,29 +81,51 @@ bool isValidMatchExpression(const string& s)
return count <= 7;
}
bool isValidMatchExpressions(const string& mes)
vector<string> splitSequenceOfAddressExpressionsAtCommas(const string& mes)
{
vector<string> v = splitMatchExpressions(mes);
vector<string> r;
bool eof, err;
vector<uchar> v (mes.begin(), mes.end());
auto i = v.begin();
for (;;) {
auto id = eatTo(v, i, ',', 16, &eof, &err);
if (err) break;
trimWhitespace(&id);
if (id == "ANYID") id = "*";
r.push_back(id);
if (eof) break;
}
return r;
}
bool isValidSequenceOfAddressExpressions(const string& mes)
{
vector<string> v = splitSequenceOfAddressExpressionsAtCommas(mes);
for (string me : v)
{
if (!isValidMatchExpression(me)) return false;
AddressExpression ae;
if (!ae.parse(me)) return false;
}
return true;
}
bool isValidId(const string& id)
vector<AddressExpression> splitAddressExpressions(const string &aes)
{
vector<string> v = splitSequenceOfAddressExpressionsAtCommas(aes);
for (size_t i=0; i<id.length(); ++i)
vector<AddressExpression> r;
for (string me : v)
{
if (id[i] >= '0' && id[i] <= '9') continue;
// Some non-compliant meters have hex in their id.
if (id[i] >= 'a' && id[i] <= 'f') continue;
if (id[i] >= 'A' && id[i] <= 'F') continue;
return false;
AddressExpression ae;
if (ae.parse(me))
{
r.push_back(ae);
}
}
return true;
return r;
}
bool doesIdMatchExpression(const string& s, string match)
@ -149,12 +180,12 @@ bool hasWildCard(const string& mes)
return mes.find('*') != string::npos;
}
bool doesIdsMatchExpressions(vector<string> &ids, vector<string>& mes, bool *used_wildcard)
bool doesIdsMatchExpressionss(vector<string> &ids, vector<string>& mes, bool *used_wildcard)
{
bool match = false;
for (string &id : ids)
{
if (doesIdMatchExpressions(id, mes, used_wildcard))
if (doesIdMatchExpressionss(id, mes, used_wildcard))
{
match = true;
}
@ -164,7 +195,7 @@ bool doesIdsMatchExpressions(vector<string> &ids, vector<string>& mes, bool *use
return match;
}
bool doesIdMatchExpressions(const string& id, vector<string>& mes, bool *used_wildcard)
bool doesIdMatchExpressionss(const string& id, vector<string>& mes, bool *used_wildcard)
{
bool found_match = false;
bool found_negative_match = false;
@ -231,6 +262,74 @@ bool doesIdMatchExpressions(const string& id, vector<string>& mes, bool *used_wi
return false;
}
bool doesIdMatchAddressExpressions(const string& id, vector<AddressExpression>& aes, bool *used_wildcard)
{
/* bool found_match = false;
bool found_negative_match = false;
bool exact_match = false;*/
*used_wildcard = false;
// Goes through all possible match expressions.
// If no expression matches, neither positive nor negative,
// then the result is false. (ie no match)
// If more than one positive match is found, and no negative,
// then the result is true.
// If more than one negative match is found, irrespective
// if there is any positive matches or not, then the result is false.
// If a positive match is found, using a wildcard not any exact match,
// then *used_wildcard is set to true.
/*
for (AddressExpression &ae : aes)
{
bool has_wildcard = ae.has_wildcard;
bool is_negative_rule = (me.length() > 0 && me.front() == '!');
if (is_negative_rule)
{
me.erase(0, 1);
}
bool m = doesIdMatchExpression(id, me);
if (is_negative_rule)
{
if (m) found_negative_match = true;
}
else
{
if (m)
{
found_match = true;
if (!has_wildcard)
{
exact_match = true;
}
}
}
}
if (found_negative_match)
{
return false;
}
if (found_match)
{
if (exact_match)
{
*used_wildcard = false;
}
else
{
*used_wildcard = true;
}
return true;
}
*/
return false;
}
string toIdsCommaSeparated(vector<string> &ids)
{
string cs;
@ -242,3 +341,300 @@ string toIdsCommaSeparated(vector<string> &ids)
if (cs.length() > 0) cs.pop_back();
return cs;
}
string toIdsCommaSeparated(vector<AddressExpression> &ids)
{
string cs;
for (AddressExpression& ae: ids)
{
cs += ae.str();
cs += ",";
}
if (cs.length() > 0) cs.pop_back();
return cs;
}
bool AddressExpression::match(const std::string &i, uint16_t m, uchar v, uchar t)
{
if (!(mfct == 0xffff || mfct == m)) return false;
if (!(version == 0xff || version == v)) return false;
if (!(type == 0xff || type == t)) return false;
if (!doesIdMatchExpression(i, id)) return false;
return true;
}
bool AddressExpression::parse(const string &in)
{
string s = in;
// Example: 12345678
// or 12345678.M=PII.T=1B.V=01
// or 1234*
// or 1234*.M=PII
// or 1234*.V=01
// or 12 // mbus primary
// or 0 // mbus primary
// or 250.MPII.V01.T1B // mbus primary
// or !12345678
// or !*.M=ABC
id = "";
mbus_primary = false;
mfct = 0xffff;
type = 0xff;
version = 0xff;
filter_out = false;
if (s.size() == 0) return false;
if (s.size() > 1 && s[0] == '!')
{
filter_out = true;
s = s.substr(1);
// Double ! not allowed.
if (s.size() > 1 && s[0] == '!') return false;
}
vector<string> parts = splitString(s, '.');
assert(parts.size() > 0);
id = parts[0];
if (!isValidMatchExpression(id, &has_wildcard))
{
// Not a long id, so lets check if it is p0 to p250 for primary mbus ids.
if (id.size() < 2) return false;
if (id[0] != 'p') return false;
for (size_t i=1; i < id.length(); ++i)
{
if (!isdigit(id[i])) return false;
}
// All digits good.
int v = atoi(id.c_str()+1);
if (v < 0 || v > 250) return false;
// It is 0-250 which means it is an mbus primary address.
mbus_primary = true;
}
for (size_t i=1; i<parts.size(); ++i)
{
if (parts[i].size() == 4) // V=xy or T=xy
{
if (parts[i][1] != '=') return false;
vector<uchar> data;
bool ok = hex2bin(&parts[i][2], &data);
if (!ok) return false;
if (data.size() != 1) return false;
if (parts[i][0] == 'V')
{
version = data[0];
}
else if (parts[i][0] == 'T')
{
type = data[0];
}
else
{
return false;
}
}
else if (parts[i].size() == 5) // M=xyz
{
if (parts[i][1] != '=') return false;
if (parts[i][0] != 'M') return false;
bool ok = flagToManufacturer(&parts[i][2], &mfct);
if (!ok) return false;
}
else
{
return false;
}
}
return true;
}
bool flagToManufacturer(const char *s, uint16_t *out_mfct)
{
if (s[0] == 0 || s[1] == 0 || s[2] == 0 || s[3] != 0) return false;
if (s[0] < '@' || s[0] > 'Z' ||
s[1] < '@' || s[1] > 'Z' ||
s[2] < '@' || s[2] > 'Z') return false;
*out_mfct = MANFCODE(s[0],s[1],s[2]);
return true;
}
string AddressExpression::str()
{
string s;
if (filter_out) s = "!";
s.append(id);
if (mfct != 0xffff)
{
s += ".M="+manufacturerFlag(mfct);
}
if (version != 0xff)
{
s += ".V="+tostrprintf("%02x", version);
}
if (type != 0xff)
{
s += ".T="+tostrprintf("%02x", type);
}
return s;
}
string Address::str()
{
string s;
s.append(id);
if (mfct != 0xffff)
{
s += ".M="+manufacturerFlag(mfct);
}
if (version != 0xff)
{
s += ".V="+tostrprintf("%02x", version);
}
if (type != 0xff)
{
s += ".T="+tostrprintf("%02x", type);
}
return s;
}
string Address::concat(std::vector<Address> &addresses)
{
string s;
for (Address& a: addresses)
{
if (s.size() > 0) s.append(",");
s.append(a.str());
}
return s;
}
string AddressExpression::concat(std::vector<AddressExpression> &address_expressions)
{
string s;
for (AddressExpression& a: address_expressions)
{
if (s.size() > 0) s.append(",");
s.append(a.str());
}
return s;
}
string manufacturerFlag(int m_field) {
char a = (m_field/1024)%32+64;
char b = (m_field/32)%32+64;
char c = (m_field)%32+64;
string flag;
flag += a;
flag += b;
flag += c;
return flag;
}
void Address::decodeMfctFirst(const vector<uchar>::iterator &pos)
{
mfct = *(pos+1) << 8 | *(pos+0);
id = tostrprintf("%02x%02x%02x%02x", *(pos+5), *(pos+4), *(pos+3), *(pos+2));
version = *(pos+6);
type = *(pos+7);
}
void Address::decodeIdFirst(const vector<uchar>::iterator &pos)
{
id = tostrprintf("%02x%02x%02x%02x", *(pos+3), *(pos+2), *(pos+1), *(pos+0));
mfct = *(pos+5) << 8 | *(pos+4);
version = *(pos+6);
type = *(pos+7);
}
bool doesTelegramMatchExpressions(std::vector<Address> &addresses,
std::vector<AddressExpression>& address_expressions,
bool *used_wildcard)
{
bool match = false;
for (Address &a : addresses)
{
if (doesAddressMatchExpressions(a, address_expressions, used_wildcard))
{
match = true;
}
// Go through all ids even though there is an early match.
// This way we can see if theres an exact match later.
}
return match;
}
bool doesAddressMatchExpressions(Address &address,
vector<AddressExpression>& address_expressions,
bool *used_wildcard)
{
bool found_match = false;
bool found_negative_match = false;
bool exact_match = false;
// Goes through all possible match expressions.
// If no expression matches, neither positive nor negative,
// then the result is false. (ie no match)
// If more than one positive match is found, and no negative,
// then the result is true.
// If more than one negative match is found, irrespective
// if there is any positive matches or not, then the result is false.
// If a positive match is found, using a wildcard not any exact match,
// then *used_wildcard is set to true.
for (AddressExpression &ae : address_expressions)
{
bool has_wildcard = ae.has_wildcard;
bool is_negative_rule = ae.filter_out;
bool m = doesIdMatchExpression(address.id, ae.id);
if (is_negative_rule)
{
if (m) found_negative_match = true;
}
else
{
if (m)
{
found_match = true;
if (!has_wildcard)
{
exact_match = true;
}
}
}
}
if (found_negative_match)
{
return false;
}
if (found_match)
{
if (exact_match)
{
*used_wildcard = false;
}
else
{
*used_wildcard = true;
}
return true;
}
return false;
}

Wyświetl plik

@ -23,36 +23,87 @@
struct Address
{
std::string id; // p1 or 12345678 or non-compliant hex: 1234abcd
uint16_t mfct {};
uchar type {};
uchar version {};
void decodeMfctFirst(const std::vector<uchar>::iterator &pos);
void decodeIdFirst(const std::vector<uchar>::iterator &pos);
std::string str();
static std::string concat(std::vector<Address> &addresses);
};
struct AddressExpression
{
// An address expression is used to select which telegrams to decode for a driver.
// An address expression is also used to select a specific meter to poll for data.
// Example address: 12345678
// Or fully qualified: 12345678.M=PII.T=1b.V=01
// which means manufacturer triplet PII, type/media=0x1b, version=0x01
std::string id; // 1 or 12345678 or non-compliant hex: 1234abcd
bool wildcard_used {}; // The id contains a *
bool mbus_primary {}; // Signals that the id is 0-250
uint16_t mfct {}; // If 0xffff then any mfct matches this address.
uchar type {}; // If 0xff then any type matches this address.
uchar version {}; // If 0xff then any version matches this address.
bool negate {}; // When used for testing this address was negated. !12345678
// Or wildcards in id: 12*.T=16
// which matches all cold water meters whose ids start with 12.
// Or negated tests: 12345678.V!=66
// which will decode all telegrams from 12345678 except those where the version is 0x66.
// Or every telegram which is does not start with 12 and is not from ABB:
// !12*.M!=ABB
bool parse(std::string &s);
bool match(Address *a);
std::string id; // p1 or 12345678 or non-compliant hex: 1234abcd
bool has_wildcard {}; // The id contains a *
bool mbus_primary {}; // Signals that the id is 0-250
uint16_t mfct {}; // If 0xffff then any mfct matches this address.
uchar version {}; // If 0xff then any version matches this address.
uchar type {}; // If 0xff then any type matches this address.
bool filter_out {}; // Telegrams matching this rule should be filtered out!
AddressExpression() {}
AddressExpression(Address &a) : id(a.id), mfct(a.mfct), version(a.version), type(a.type) { }
bool parse(const std::string &s);
bool match(const std::string &id, uint16_t mfct, uchar version, uchar type);
std::string str();
static std::string concat(std::vector<AddressExpression> &address_expressions);
};
bool isValidMatchExpression(const std::string& s);
bool isValidMatchExpressions(const std::string& s);
/**
isValidSequenceOfAddressExpressions:
Valid sequenes look like this:
12345678
12345678,22334455,34*
12*.T=16,!*.M=XYZ
!*.V=33
*/
bool isValidSequenceOfAddressExpressions(const std::string& s);
bool isValidMatchExpression(const std::string& s, bool *has_wildcard);
bool doesIdMatchExpression(const std::string& id,
std::string match_rule);
bool doesIdMatchExpressions(const std::string& id,
bool doesIdMatchExpressionss(const std::string& id,
std::vector<std::string>& match_rules,
bool *used_wildcard);
bool doesIdsMatchExpressions(std::vector<std::string> &ids,
bool doesIdsMatchExpressionss(std::vector<std::string> &ids,
std::vector<std::string>& match_rules,
bool *used_wildcard);
std::string toIdsCommaSeparated(std::vector<std::string> &ids);
std::string toIdsCommaSeparated(std::vector<AddressExpression> &ids);
bool isValidId(const std::string& id);
std::vector<AddressExpression> splitAddressExpressions(const std::string &aes);
std::vector<std::string> splitMatchExpressions(const std::string& mes);
bool flagToManufacturer(const char *s, uint16_t *out_mfct);
std::string manufacturerFlag(int m_field);
bool doesTelegramMatchExpressions(std::vector<Address> &addresses,
std::vector<AddressExpression>& address_expressions,
bool *used_wildcard);
bool doesAddressMatchExpressions(Address &address,
std::vector<AddressExpression>& address_expressions,
bool *used_wildcard);
#endif

Wyświetl plik

@ -740,38 +740,7 @@ static shared_ptr<Configuration> parseNormalCommandLine(Configuration *c, int ar
error("Not a valid meter driver \"%s\"\n", driver.c_str());
}
//LinkModeSet default_modes = toMeterLinkModeSet(mi.driver);
/*
if (default_modes.has(LinkMode::MBUS))
{
// MBus primary address 0-250
// secondary hex address iiiiiiiimmmmvvmm
}
else
{
// WMBus ids are 8 hex digits iiiiiiii
if (!isValidMatchExpressions(id, true)) error("Not a valid id nor a valid meter match expression \"%s\"\n", id.c_str());
}
if (!isValidKey(key, mi)) error("Not a valid meter key \"%s\"\n", key.c_str());
*/
c->meters.push_back(mi);
// Check if the devices can listen to the meter link mode(s).
/*
Ignore this check for now until all meters have been refactored.
if (!default_modes.hasAll(mi.link_modes))
{
string want = mi.link_modes.hr();
string has = default_modes.hr();
error("(cmdline) cannot set link modes to: %s because meter %s only transmits on: %s\n",
want.c_str(), mi.driverName().str().c_str(), has.c_str());
}
string modeshr = mi.link_modes.hr();
debug("(cmdline) setting link modes to %s for meter %s\n",
mi.link_modes.hr().c_str(), name.c_str());
*/
}
return shared_ptr<Configuration>(c);

Wyświetl plik

@ -179,34 +179,20 @@ void parseMeterConfig(Configuration *c, vector<char> &buf, string file)
mi.parse(name, driver, id, key); // sets driver, extras, name, bus, bps, link_modes, ids, name, key
mi.poll_interval = poll_interval;
/*
Ignore link mode checking until all drivers have been refactored.
LinkModeSet default_modes = toMeterLinkModeSet(mi.driver);
if (!default_modes.hasAll(mi.link_modes))
{
string want = mi.link_modes.hr();
string has = default_modes.hr();
error("(cmdline) cannot set link modes to: %s because meter %s only transmits on: %s\n",
want.c_str(), mi.driverName().str().c_str(), has.c_str());
}
string modeshr = mi.link_modes.hr();
debug("(cmdline) setting link modes to %s for meter %s\n",
mi.link_modes.hr().c_str(), name.c_str());
*/
if (!isValidMatchExpressions(id)) {
warning("Not a valid meter id nor a valid meter match expression \"%s\"\n", id.c_str());
if (!isValidSequenceOfAddressExpressions(id)) {
warning("Not a valid meter id nor a valid sequence of match expression \"%s\"\n", id.c_str());
use = false;
}
if (!isValidKey(key, mi)) {
warning("Not a valid meter key \"%s\"\n", key.c_str());
use = false;
}
if (use) {
if (use)
{
mi.extra_constant_fields = extra_constant_fields;
mi.extra_calculated_fields = extra_calculated_fields;
mi.shells = telegram_shells;
mi.meter_shells = meter_shells;
mi.idsc = toIdsCommaSeparated(mi.ids);
mi.selected_fields = selected_fields;
c->meters.push_back(mi);
}

Wyświetl plik

@ -105,6 +105,7 @@ bool DriverDynamic::load(DriverInfo *di, const string &file_name, const char *co
catch (...)
{
xmqFreeDoc(doc);
di->setDynamic(file, NULL);
return false;
}
}
@ -150,7 +151,7 @@ XMQProceed DriverDynamic::add_detect(XMQDoc *doc, XMQNode *detect, DriverInfo *d
mvt.c_str(),
line,
line);
throw 1;
return XMQ_CONTINUE;
}
string mfct = fields[0];
@ -175,7 +176,7 @@ XMQProceed DriverDynamic::add_detect(XMQDoc *doc, XMQNode *detect, DriverInfo *d
mfct.c_str(),
line,
line);
throw 1;
return XMQ_CONTINUE;
}
mfct_code = toMfctCode(a, b, c);
}
@ -193,7 +194,7 @@ XMQProceed DriverDynamic::add_detect(XMQDoc *doc, XMQNode *detect, DriverInfo *d
mfct.c_str(),
line,
line);
throw 1;
return XMQ_CONTINUE;
}
}
@ -207,7 +208,7 @@ XMQProceed DriverDynamic::add_detect(XMQDoc *doc, XMQNode *detect, DriverInfo *d
version,
line,
line);
throw 1;
return XMQ_CONTINUE;
}
if (type > 255 || type < 0)
@ -220,7 +221,7 @@ XMQProceed DriverDynamic::add_detect(XMQDoc *doc, XMQNode *detect, DriverInfo *d
type,
line,
line);
throw 1;
return XMQ_CONTINUE;
}
string mfct_flag = manufacturerFlag(mfct_code);

Wyświetl plik

@ -160,8 +160,11 @@ namespace
vector<uchar> v;
auto entry = it->second.second;
hex2bin(entry.value.substr(0, 8), &v);
t->addId(v.begin());
std::string info = "*** " + entry.value.substr(0, 8) + " tpl-id (" + t->ids.back() + ")";
// FIXME PROBLEM
Address a;
a.id = tostrprintf("%02x%02x%02x%02x", v[3], v[2], v[1], v[0]);
t->addresses.push_back(a);
std::string info = "*** " + entry.value.substr(0, 8) + " tpl-id (" + t->addresses.back().id + ")";
t->addSpecialExplanation(entry.offset, 4, KindOfData::CONTENT, Understanding::FULL, info.c_str());
v.clear();

Wyświetl plik

@ -581,12 +581,12 @@ bool start(Configuration *config)
vector<string> envs;
string id = "";
if (meter->ids().size() > 0)
if (meter->addressExpressions().size() > 0)
{
id = meter->idsc().c_str();
id = meter->addressExpressions().back().id;
}
meter->createMeterEnv(&id, &envs, &config->extra_constant_fields);
meter->createMeterEnv(id, &envs, &config->extra_constant_fields);
for (auto &s : *shells) {
vector<string> args;

Wyświetl plik

@ -144,11 +144,12 @@ public:
bool handled = false;
bool exact_id_match = false;
string verbose_info;
string ids;
vector<Address> addresses;
for (auto &m : meters_)
{
bool h = m->handleTelegram(about, input_frame, simulated, &ids, &exact_id_match);
bool h = m->handleTelegram(about, input_frame, simulated, &addresses, &exact_id_match);
if (h) handled = true;
}
@ -156,7 +157,12 @@ public:
// 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());
if (isDebugEnabled())
{
string idsc = Address::concat(addresses);
debug("(meter) no meter handled %s checking %d templates.\n",
idsc.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;
@ -165,7 +171,6 @@ public:
if (ok)
{
ids = t.idsc;
for (auto &mi : meter_templates_)
{
if (MeterCommonImplementation::isTelegramForMeter(&t, NULL, &mi))
@ -178,10 +183,9 @@ public:
// This will pick the tpl_id.
// Or a telegram can have a single dll_id,
// then the dll_id will be picked.
vector<string> tmp_ids;
tmp_ids.push_back(t.ids.back());
meter_info.ids = tmp_ids;
meter_info.idsc = t.ids.back();
vector<AddressExpression> aes;
aes.push_back(AddressExpression(t.addresses.back()));
meter_info.address_expressions = aes;
if (meter_info.driverName().str() == "auto")
{
@ -203,47 +207,55 @@ public:
// Now build a meter object with for this exact id.
auto meter = createMeter(&meter_info);
addMeter(meter);
string idsc = toIdsCommaSeparated(t.ids);
verbose("(meter) used meter template %s %s %s to match %s\n",
mi.name.c_str(),
mi.idsc.c_str(),
mi.driverName().str().c_str(),
idsc.c_str());
if (isVerboseEnabled())
{
string idsc = Address::concat(t.addresses);
string mi_idsc = AddressExpression::concat(mi.address_expressions);
verbose("(meter) used meter template %s %s %s to match %s\n",
mi.name.c_str(),
mi_idsc.c_str(),
mi.driverName().str().c_str(),
idsc.c_str());
}
if (is_daemon_)
{
string mi_idsc = AddressExpression::concat(mi.address_expressions);
notice("(wmbusmeters) started meter %d (%s %s %s)\n",
meter->index(),
mi.name.c_str(),
meter_info.idsc.c_str(),
mi_idsc.c_str(),
mi.driverName().str().c_str());
}
else
{
string mi_idsc = AddressExpression::concat(mi.address_expressions);
verbose("(meter) started meter %d (%s %s %s)\n",
meter->index(),
mi.name.c_str(),
meter_info.idsc.c_str(),
mi_idsc.c_str(),
mi.driverName().str().c_str());
}
bool match = false;
bool h = meter->handleTelegram(about, input_frame, simulated, &ids, &match);
bool h = meter->handleTelegram(about, input_frame, simulated, &addresses, &match);
if (!match)
{
string aesc = AddressExpression::concat(meter->addressExpressions());
// Oups, we added a new meter object tailored for this telegram
// but it still did not match! This is probably an error in wmbusmeters!
warning("(meter) newly created meter (%s %s %s) did not match telegram! ",
"Please open an issue at https://github.com/wmbusmeters/wmbusmeters/\n",
meter->name().c_str(), meter->idsc().c_str(), meter->driverName().str().c_str());
meter->name().c_str(), aesc.c_str(), meter->driverName().str().c_str());
}
else if (!h)
{
string aesc = AddressExpression::concat(meter->addressExpressions());
// 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());
meter->name().c_str(), aesc.c_str(), meter->driverName().str().c_str());
}
else
{
@ -259,7 +271,7 @@ public:
}
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", "TODO");
}
return handled;
}
@ -344,8 +356,8 @@ public:
auto meter = createMeter(&mi);
bool match = false;
string id;
bool h = meter->handleTelegram(about, input_frame, simulated, &id, &match, &t);
vector<Address> addresses;
bool h = meter->handleTelegram(about, input_frame, simulated, &addresses, &match, &t);
if (!match)
{
@ -353,11 +365,12 @@ public:
}
else if (!h)
{
string aesc = AddressExpression::concat(meter->addressExpressions());
// 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());
meter->name().c_str(), aesc.c_str(), meter->driverName().str().c_str());
}
else
{
@ -406,9 +419,8 @@ public:
// 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();
mi.address_expressions.clear();
mi.address_expressions.push_back(AddressExpression(t.addresses.back()));
// This will be the driver that will actually decode and print with.
string using_driver;
@ -459,7 +471,6 @@ public:
assert(meter != NULL);
bool match = false;
string id;
if (should_profile_ > 0)
{
@ -473,7 +484,8 @@ public:
for (int k=0; k<should_profile_; ++k)
{
meter->handleTelegram(about, input_frame, simulated, &id, &match, &t);
vector<Address> addresses;
meter->handleTelegram(about, input_frame, simulated, &addresses, &match, &t);
string hr, fields, json;
vector<string> envs, more_json, selected_fields;
@ -500,7 +512,8 @@ public:
return;
}
meter->handleTelegram(about, input_frame, simulated, &id, &match, &t);
vector<Address> addresses;
meter->handleTelegram(about, input_frame, simulated, &addresses, &match, &t);
int u = 0;
int l = 0;

Wyświetl plik

@ -329,8 +329,7 @@ MeterCommonImplementation::MeterCommonImplementation(MeterInfo &mi,
has_process_content_(di.hasProcessContent()),
waiting_for_poll_response_sem_("waiting_for_poll_response")
{
ids_ = mi.ids;
idsc_ = toIdsCommaSeparated(ids_);
address_expressions_ = mi.address_expressions;
link_modes_ = mi.link_modes;
poll_interval_= mi.poll_interval;
@ -675,25 +674,25 @@ void MeterCommonImplementation::poll(shared_ptr<BusManager> bus_manager)
if (!bus_device)
{
warning("(meter) warning! no bus specified for meter %s %s\n", name().c_str(), idsc().c_str());
string aesc = AddressExpression::concat(addressExpressions());
warning("(meter) warning! no bus specified for meter %s %s\n", name().c_str(), aesc.c_str());
return;
}
string id = ids().back();
if (id.length() != 2 && id.length() != 3 && id.length() != 8)
AddressExpression &ae = addressExpressions().back();
if (ae.has_wildcard)
{
debug("(meter) not polling from bad id \"%s\" with wrong length\n", id.c_str());
debug("(meter) not polling from id \"%s\" since poll id must not have a wildcard\n", ae.id.c_str());
return;
}
if (id.length() == 2 || id.length() == 3)
if (ae.mbus_primary)
{
vector<uchar> idhex;
int idnum = atoi(id.c_str());
int idnum = atoi(ae.id.c_str()+1);
if (idnum < 0 || idnum > 250 || idhex.size() != 1)
if (idnum < 0 || idnum > 250)
{
debug("(meter) not polling from bad id \"%s\"\n", id.c_str());
debug("(meter) not polling from bad id \"%s\"\n", ae.id.c_str());
return;
}
@ -701,7 +700,7 @@ void MeterCommonImplementation::poll(shared_ptr<BusManager> bus_manager)
buf.resize(5);
buf[0] = 0x10; // Start
buf[1] = 0x5b; // REQ_UD2
buf[2] = idhex[0];
buf[2] = idnum & 0xff;
uchar cs = 0;
for (int i=1; i<3; ++i) cs += buf[i];
buf[3] = cs; // checksum
@ -709,21 +708,21 @@ void MeterCommonImplementation::poll(shared_ptr<BusManager> bus_manager)
verbose("(meter) polling %s %s (primary) with req ud2 on bus %s\n",
name().c_str(),
id.c_str(),
bus_device->busAlias().c_str(),id.c_str());
ae.id.c_str(),
bus_device->busAlias().c_str(),ae.id.c_str());
bus_device->serial()->send(buf);
}
if (id.length() == 8)
if (!ae.mbus_primary)
{
// A full secondary address 12345678 was specified.
vector<uchar> idhex;
bool ok = hex2bin(id, &idhex);
bool ok = hex2bin(ae.id, &idhex);
if (!ok || idhex.size() != 4)
{
debug("(meter) not polling from bad id \"%s\"\n", id.c_str());
debug("(meter) not polling from bad id \"%s\"\n", ae.id.c_str());
return;
}
@ -741,19 +740,19 @@ void MeterCommonImplementation::poll(shared_ptr<BusManager> bus_manager)
buf[8] = idhex[2]; // id 56
buf[9] = idhex[1]; // id 34
buf[10] = idhex[0]; // id 12
// Use wildcards instead of exact matching here.
// TODO add selection based on these values as well.
buf[11] = 0xff; // mfct
buf[12] = 0xff; // mfct
buf[13] = 0xff; // version/generation
buf[14] = 0xff; // type/media/device
buf[11] = (ae.mfct >> 8) & 0xff; // use 0xff as a wildcard
buf[12] = ae.mfct & 0xff; // mfct
buf[13] = ae.version; // version/generation
buf[14] = ae.type; // type/media/device
uchar cs = 0;
for (int i=4; i<15; ++i) cs += buf[i];
buf[15] = cs; // checksum
buf[16] = 0x16; // Stop
debug("(meter) secondary addressing bus %s to address %s\n", bus_device->busAlias().c_str(), id.c_str());
debug("(meter) secondary addressing bus %s to address %s\n",
bus_device->busAlias().c_str(),
ae.id.c_str());
bus_device->serial()->send(buf);
usleep(1000*500);
@ -769,26 +768,21 @@ void MeterCommonImplementation::poll(shared_ptr<BusManager> bus_manager)
verbose("(meter) polling %s %s (secondary) with req ud2 bus %s\n",
name().c_str(),
id.c_str(),
ae.id.c_str(),
bus_device->busAlias().c_str());
bus_device->serial()->send(buf);
}
bool ok = waiting_for_poll_response_sem_.wait();
if (!ok)
{
warning("(meter) %s %s did not send a response!\n", name().c_str(), idsc().c_str());
warning("(meter) %s %s did not send a response!\n", name().c_str(), ae.id.c_str());
}
}
}
vector<string>& MeterCommonImplementation::ids()
vector<AddressExpression>& MeterCommonImplementation::addressExpressions()
{
return ids_;
}
string MeterCommonImplementation::idsc()
{
return idsc_;
return address_expressions_;
}
vector<FieldInfo> &MeterCommonImplementation::fieldInfos()
@ -854,7 +848,10 @@ void MeterCommonImplementation::setPollInterval(time_t interval)
poll_interval_ = interval;
if (usesPolling() && poll_interval_ == 0)
{
warning("(meter) %s %s needs polling but has no pollinterval set!\n", name().c_str(), idsc().c_str());
string aesc = AddressExpression::concat(addressExpressions());
warning("(meter) %s %s needs polling but has no pollinterval set!\n",
name().c_str(),
aesc.c_str());
}
}
@ -908,8 +905,7 @@ string toString(DriverInfo &di)
bool MeterCommonImplementation::isTelegramForMeter(Telegram *t, Meter *meter, MeterInfo *mi)
{
string name;
vector<string> ids;
string idsc;
vector<AddressExpression> address_expressions;
string driver_name;
assert((meter && !mi) ||
@ -918,22 +914,27 @@ bool MeterCommonImplementation::isTelegramForMeter(Telegram *t, Meter *meter, Me
if (meter)
{
name = meter->name();
ids = meter->ids();
idsc = meter->idsc();
address_expressions = meter->addressExpressions();
driver_name = meter->driverName().str();
}
else
{
name = mi->name;
ids = mi->ids;
idsc = mi->idsc;
address_expressions = mi->address_expressions;
driver_name = mi->driver_name.str();
}
debug("(meter) %s: for me? %s in %s\n", name.c_str(), t->idsc.c_str(), idsc.c_str());
if (isDebugEnabled())
{
// Telegram addresses
string t_idsc = Address::concat(t->addresses);
// Meter/MeterInfo address expressions
string m_idsc = AddressExpression::concat(address_expressions);
debug("(meter) %s: for me? %s in %s\n", name.c_str(), t_idsc.c_str(), m_idsc.c_str());
}
bool used_wildcard = false;
bool id_match = doesIdsMatchExpressions(t->ids, ids, &used_wildcard);
bool id_match = doesTelegramMatchExpressions(t->addresses, address_expressions, &used_wildcard);
if (!id_match) {
// The id must match.
@ -1052,7 +1053,7 @@ bool checkCommonField(string *buf, string desired_field, Meter *m, Telegram *t,
}
if (desired_field == "id")
{
*buf += t->ids.back() + c;
*buf += t->addresses.back().id + c;
return true;
}
if (desired_field == "timestamp")
@ -1196,7 +1197,8 @@ string concatFields(Meter *m, Telegram *t, char c, vector<FieldInfo> &prints, bo
}
bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<uchar> input_frame,
bool simulated, string *ids, bool *id_match, Telegram *out_analyzed)
bool simulated, vector<Address> *addresses,
bool *id_match, Telegram *out_analyzed)
{
Telegram t;
t.about = about;
@ -1205,7 +1207,7 @@ bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<ucha
if (simulated) t.markAsSimulated();
if (out_analyzed != NULL) t.markAsBeingAnalyzed();
*ids = t.idsc;
*addresses = t.addresses;
if (!ok || !isTelegramForMeter(&t, this, NULL))
{
@ -1214,12 +1216,19 @@ bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<ucha
}
*id_match = true;
verbose("(meter) %s(%d) %s handling telegram from %s\n", name().c_str(), index(), driverName().str().c_str(), t.ids.back().c_str());
if (isVerboseEnabled())
{
verbose("(meter) %s(%d) %s handling telegram from %s\n",
name().c_str(),
index(),
driverName().str().c_str(),
t.addresses.back().str().c_str());
}
if (isDebugEnabled())
{
string msg = bin2hex(input_frame);
debug("(meter) %s %s \"%s\"\n", name().c_str(), t.ids.back().c_str(), msg.c_str());
debug("(meter) %s %s \"%s\"\n", name().c_str(), t.addresses.back().str().c_str(), msg.c_str());
}
// For older meters with manufacturer specific data without a nice 0f dif marker.
@ -1746,11 +1755,11 @@ string FieldInfo::renderJson(Meter *m, DVEntry *dve)
return s;
}
void MeterCommonImplementation::createMeterEnv( string *id,
vector<string> *envs,
vector<string> *extra_constant_fields)
void MeterCommonImplementation::createMeterEnv(string id,
vector<string> *envs,
vector<string> *extra_constant_fields)
{
envs->push_back(string("METER_ID="+*id));
envs->push_back(string("METER_ID="+id));
envs->push_back(string("METER_NAME=")+name());
envs->push_back(string("METER_TYPE=")+driverName().str());
@ -1793,9 +1802,9 @@ void MeterCommonImplementation::printMeter(Telegram *t,
}
string id = "";
if (t->ids.size() > 0)
if (t->addresses.size() > 0)
{
id = t->ids.back();
id = t->addresses.back().id;
}
string indent = "";
@ -1943,7 +1952,7 @@ void MeterCommonImplementation::printMeter(Telegram *t,
s += "}";
*json = s;
createMeterEnv(&id, envs, extra_constant_fields);
createMeterEnv(id, envs, extra_constant_fields);
envs->push_back(string("METER_JSON=")+*json);
envs->push_back(string("METER_MEDIA=")+media);
@ -2095,11 +2104,15 @@ shared_ptr<Meter> createMeter(MeterInfo *mi)
{
newm->setSelectedFields(di->defaultFields());
}
verbose("(meter) created %s %s %s %s\n",
mi->name.c_str(),
di->name().str().c_str(),
mi->idsc.c_str(),
keymsg);
if (isVerboseEnabled())
{
string aesc = AddressExpression::concat(mi->address_expressions);
verbose("(meter) created %s %s %s %s\n",
mi->name.c_str(),
di->name().str().c_str(),
aesc.c_str(),
keymsg);
}
return newm;
}
@ -2167,12 +2180,12 @@ string MeterInfo::str()
return r;
}
bool MeterInfo::parse(string n, string d, string i, string k)
bool MeterInfo::parse(string n, string d, string aes, string k)
{
clear();
name = n;
ids = splitMatchExpressions(i);
address_expressions = splitAddressExpressions(aes);
key = k;
bool driverextras_checked = false;
bool bus_checked = false;
@ -2590,87 +2603,6 @@ bool FieldInfo::extractString(Meter *m, Telegram *t, DVEntry *dve)
return found;
}
bool Address::parse(string &s)
{
// Example: 12345678
// or 12345678.M=PII.T=1B.V=01
// or 1234*
// or 1234*.M=PII
// or 1234*.V=01
// or 12 // mbus primary
// or 0 // mbus primary
// or 250.MPII.V01.T1B // mbus primary
// or !12345678
// or !*.M=ABC
id = "";
mbus_primary = false;
mfct = 0xffff;
type = 0xff;
version = 0xff;
negate = false;
if (s.size() == 0) return false;
vector<string> parts = splitString(s, '.');
assert(parts.size() > 0);
if (!isValidMatchExpression(parts[0]))
{
// Not a long id, so lets check if it is 0-250.
for (size_t i=0; i < parts[0].length(); ++i)
{
if (!isdigit(parts[0][i])) return false;
}
// All digits good.
int v = atoi(parts[0].c_str());
if (v < 0 || v > 250) return false;
// It is 0-250 which means it is an mbus primary address.
mbus_primary = true;
}
id = parts[0];
for (size_t i=1; i<parts.size(); ++i)
{
if (parts[i].size() == 4) // V=xy or T=xy
{
if (parts[i][1] != '=') return false;
vector<uchar> data;
bool ok = hex2bin(&parts[i][2], &data);
if (!ok) return false;
if (data.size() != 1) return false;
if (parts[i][0] == 'V')
{
version = data[0];
}
else if (parts[i][0] == 'T')
{
type = data[0];
}
else
{
return false;
}
}
else if (parts[i].size() == 5) // M=xyz
{
if (parts[i][1] != '=') return false;
if (parts[i][0] != 'M') return false;
bool ok = flagToManufacturer(&parts[i][2], &mfct);
if (!ok) return false;
}
else
{
return false;
}
}
return true;
}
bool checkIf(set<string> &fields, const char *s)
{
if (fields.count(s) > 0)

Wyświetl plik

@ -90,8 +90,7 @@ struct MeterInfo
string name; // User specified name of this (group of) meters.
DriverName driver_name; // Will replace MeterDriver.
string extras; // Extra driver specific settings.
vector<string> ids; // Match expressions for ids.
string idsc; // Comma separated ids.
vector<AddressExpression> address_expressions; // Match expressions for ids.
string key; // Decryption key.
LinkModeSet link_modes;
int bps {}; // For mbus communication you need to know the baud rate.
@ -111,13 +110,12 @@ struct MeterInfo
string str();
DriverName driverName();
MeterInfo(string b, string n, string e, vector<string> i, string k, LinkModeSet lms, int baud, vector<string> &s, vector<string> &ms, vector<string> &j, vector<string> &calcfs)
MeterInfo(string b, string n, string e, vector<AddressExpression> aes, string k, LinkModeSet lms, int baud, vector<string> &s, vector<string> &ms, vector<string> &j, vector<string> &calcfs)
{
bus = b;
name = n;
extras = e,
ids = i;
idsc = toIdsCommaSeparated(ids);
address_expressions = aes;
key = k;
shells = s;
meter_shells = ms;
@ -131,8 +129,7 @@ struct MeterInfo
{
bus = "";
name = "";
ids.clear();
idsc = "";
address_expressions.clear();
key = "";
shells.clear();
meter_shells.clear();
@ -374,10 +371,8 @@ struct Meter
virtual void setIndex(int i) = 0;
// Use this bus to send messages to the meter.
virtual string bus() = 0;
// This meter listens to these ids.
virtual vector<string> &ids() = 0;
// Comma separated ids.
virtual string idsc() = 0;
// This meter listens to these address expressions.
virtual std::vector<AddressExpression>& addressExpressions() = 0;
// This meter can report these fields, like total_m3, temp_c.
virtual vector<FieldInfo> &fieldInfos() = 0;
virtual vector<string> &extraConstantFields() = 0;
@ -407,7 +402,7 @@ struct Meter
virtual void onUpdate(std::function<void(Telegram*t,Meter*)> cb) = 0;
virtual int numUpdates() = 0;
virtual void createMeterEnv(string *id,
virtual void createMeterEnv(string id,
vector<string> *envs,
vector<string> *more_json) = 0;
virtual void printMeter(Telegram *t,
@ -423,7 +418,8 @@ struct Meter
// Returns true of this meter handled this telegram!
// Sets id_match to true, if there was an id match, even though the telegram could not be properly handled.
virtual bool handleTelegram(AboutTelegram &about, vector<uchar> input_frame,
bool simulated, string *id, bool *id_match, Telegram *out_t = NULL) = 0;
bool simulated, std::vector<Address> *addresses,
bool *id_match, Telegram *out_t = NULL) = 0;
virtual MeterKeys *meterKeys() = 0;
virtual void addExtraCalculatedField(std::string ecf) = 0;

Wyświetl plik

@ -59,8 +59,7 @@ struct MeterCommonImplementation : public virtual Meter
int index();
void setIndex(int i);
string bus();
vector<string>& ids();
string idsc();
vector<AddressExpression>& addressExpressions();
vector<FieldInfo> &fieldInfos();
vector<string> &extraConstantFields();
string name();
@ -163,8 +162,9 @@ protected:
// Override for mbus meters that need to be queried and likewise for C2/T2 wmbus-meters.
void poll(shared_ptr<BusManager> bus);
bool handleTelegram(AboutTelegram &about, vector<uchar> frame,
bool simulated, string *id, bool *id_match, Telegram *out_analyzed = NULL);
void createMeterEnv(string *id,
bool simulated, std::vector<Address> *addresses,
bool *id_match, Telegram *out_analyzed = NULL);
void createMeterEnv(string id,
vector<string> *envs,
vector<string> *more_json); // Add this json "key"="value" strings.
void printMeter(Telegram *t,
@ -221,8 +221,7 @@ private:
ELLSecurityMode expected_ell_sec_mode_ {};
TPLSecurityMode expected_tpl_sec_mode_ {};
string name_;
vector<string> ids_;
string idsc_;
vector<AddressExpression> address_expressions_;
vector<function<void(Telegram*,Meter*)>> on_update_;
int num_updates_ {};
time_t datetime_of_update_ {};

Wyświetl plik

@ -92,10 +92,10 @@ void Printer::printFiles(Meter *meter, Telegram *t, string &human_readable, stri
snprintf(filename, 127, "%s/%s", meterfiles_dir_.c_str(), meter->name().c_str());
break;
case MeterFileNaming::Id:
snprintf(filename, 127, "%s/%s", meterfiles_dir_.c_str(), t->ids.back().c_str());
snprintf(filename, 127, "%s/%s", meterfiles_dir_.c_str(), t->addresses.back().id.c_str());
break;
case MeterFileNaming::NameId:
snprintf(filename, 127, "%s/%s-%s", meterfiles_dir_.c_str(), meter->name().c_str(), t->ids.back().c_str());
snprintf(filename, 127, "%s/%s-%s", meterfiles_dir_.c_str(), meter->name().c_str(), t->addresses.back().id.c_str());
break;
}
string stamp;

Wyświetl plik

@ -45,7 +45,6 @@ bool verbose_ = false;
X(devices) \
X(linkmodes) \
X(ids) \
X(addresses) \
X(kdf) \
X(periods) \
X(device_parsing) \
@ -77,7 +76,6 @@ bool verbose_ = false;
X(formulas_dventries) \
X(formulas_stringinterpolation) \
#define X(t) void test_##t();
LIST_OF_TESTS
#undef X
@ -426,7 +424,7 @@ void test_linkmodes()
void test_valid_match_expression(string s, bool expected)
{
bool b = isValidMatchExpressions(s);
bool b = isValidSequenceOfAddressExpressions(s);
if (b == expected) return;
if (expected == true)
{
@ -440,9 +438,13 @@ void test_valid_match_expression(string s, bool expected)
void test_does_id_match_expression(string id, string mes, bool expected, bool expected_uw)
{
vector<string> expressions = splitMatchExpressions(mes);
Address a;
a.id = id;
vector<Address> as;
as.push_back(a);
vector<AddressExpression> expressions = splitAddressExpressions(mes);
bool uw = false;
bool b = doesIdMatchExpressions(id, expressions, &uw);
bool b = doesTelegramMatchExpressions(as, expressions, &uw);
if (b != expected)
{
if (expected == true)
@ -498,11 +500,15 @@ void test_ids()
test_does_id_match_expression("78563413", "*,!00156327,!00048713", true, true);
}
void tst_address(string s, bool valid, string id,
string mfct, uchar version, uchar type,
bool mbus_primary, bool wildcard_used)
void tst_address(string s, bool valid,
string id, bool has_wildcard,
string mfct,
uchar version,
uchar type,
bool mbus_primary,
bool filter_out)
{
Address a;
AddressExpression a;
bool ok = a.parse(s);
if (ok != valid)
@ -515,47 +521,119 @@ void tst_address(string s, bool valid, string id,
{
string smfct = manufacturerFlag(a.mfct);
if (id != a.id ||
has_wildcard != a.has_wildcard ||
mfct != smfct ||
version != a.version ||
type != a.type ||
wildcard_used != a.wildcard_used ||
mbus_primary != a.mbus_primary ||
wildcard_used != a.wildcard_used)
filter_out != a.filter_out)
{
printf("Expected parse of address \"%s\" to return (id=%s mfct=%s version=%02x type=%02x mp=%d wu=%d)\n"
"but got (id=%s mfct=%s version=%02x type=%02x mp=%d wu=%d)\n",
printf("Expected parse of address \"%s\" to return\n"
"(id=%s haswild=%d mfct=%s version=%02x type=%02x mbus=%d negt=%d)\n"
"but got\n"
"(id=%s haswild=%d mfct=%s version=%02x type=%02x mbus=%d negt=%d)\n",
s.c_str(),
id.c_str(), mfct.c_str(), version, type, mbus_primary, wildcard_used,
a.id.c_str(), smfct.c_str(), a.version, a.type, a.mbus_primary, a.wildcard_used);
id.c_str(),
has_wildcard,
mfct.c_str(),
version,
type,
mbus_primary,
filter_out,
a.id.c_str(),
a.has_wildcard,
smfct.c_str(),
a.version,
a.type,
a.mbus_primary,
a.filter_out);
}
}
}
void tst_address_match(string expr, string id, uint16_t m, uchar v, uchar t, bool match, bool filter_out)
{
AddressExpression e;
bool ok = e.parse(expr);
assert(ok);
bool test = e.match(id, m, v, t);
if (test != match)
{
printf("Expected address %s %04x %02x %02x to %smatch expression %s\n",
id.c_str(),
m, v, t,
match?"":"not ",
expr.c_str());
}
if (match && e.filter_out != filter_out)
{
printf("Expected %s from match expression %s\n",
filter_out?"FILTERED OUT":"NOT filtered",
expr.c_str());
}
}
void test_addresses()
{
tst_address("12345678",
true,
"12345678", // id
false, // has wildcard
"___", // mfct
0xff, // type
0xff, // version
false, // mbus primary
false // wildcard used
false, // mbus primary found
false // negate test
);
tst_address("123k45678", false, "", "", 0xff, 0xff, false, false);
tst_address("1234", false, "", "", 0xff, 0xff, false, false);
tst_address("0", true, "0", "___", 0xff, 0xff, true,false);
tst_address("250", true, "250", "___", 0xff, 0xff, true, false);
tst_address("251", false, "", "", 0xff, 0xff, false, false);
tst_address("0.M=PII.V=01.T=1b", true, "0", "PII", 0x01, 0x1b, true, false);
tst_address("123.V=11.M=FOO.T=ff", true, "123", "FOO", 0x11, 0xff, true, false);
tst_address("123.M=FOO", true, "123", "FOO", 0xff, 0xff, true, false);
tst_address("123.M=FOO.V=33", true, "123", "FOO", 0x33, 0xff, true, false);
tst_address("123.T=33", true, "123", "___", 0xff, 0x33, true, false);
tst_address("1.V=33", true, "1", "___", 0x33, 0xff, true, false);
tst_address("16.M=BAR", true, "16", "BAR", 0xff, 0xff, true, false);
tst_address("123k45678", false, "", false, "", 0xff, 0xff, false, false);
tst_address("1234", false, "", false, "", 0xff, 0xff, false, false);
tst_address("p0", true, "p0", false, "___", 0xff, 0xff, true, false);
tst_address("p250", true, "p250", false, "___", 0xff, 0xff, true, false);
tst_address("p251", false, "", false, "", 0xff, 0xff, false, false);
tst_address("p0.M=PII.V=01.T=1b", true, "p0", false, "PII", 0x01, 0x1b, true, false);
tst_address("p123.V=11.M=FOO.T=ff", true, "p123", false, "FOO", 0x11, 0xff, true, false);
tst_address("p123.M=FOO", true, "p123", false, "FOO", 0xff, 0xff, true, false);
tst_address("p123.M=FOO.V=33", true, "p123", false, "FOO", 0x33, 0xff, true, false);
tst_address("p123.T=33", true, "p123", false, "___", 0xff, 0x33, true, false);
tst_address("p1.V=33", true, "p1", false, "___", 0x33, 0xff, true, false);
tst_address("p16.M=BAR", true, "p16", false, "BAR", 0xff, 0xff, true, false);
// tst_address("12*", true, "12*", "___", 0xff, 0xff, false, true);
tst_address("12345678.M=ABB.V=66.T=16", true, "12345678", false, "ABB", 0x66, 0x16, false, false);
tst_address("!12345678.M=ABB.V=66.T=16", true, "12345678", false, "ABB", 0x66, 0x16, false, true);
tst_address("!*.M=ABB", true, "*", true, "ABB", 0xff, 0xff, false, true);
tst_address("!*.V=66.T=06", true, "*", true, "___", 0x66, 0x06, false, true);
tst_address("12*", true, "12*", true, "___", 0xff, 0xff, false, false);
tst_address("!1234567*", true, "1234567*", true, "___", 0xff, 0xff, false, true);
tst_address_match("12345678", "12345678", 1, 1, 1, true, false);
tst_address_match("12345678.M=ABB.V=77", "12345678", MANUFACTURER_ABB, 0x77, 88, true, false);
tst_address_match("1*.V=77", "12345678", MANUFACTURER_ABB, 0x77, 1, true, false);
tst_address_match("12345678.M=ABB.V=67.T=06", "12345678", MANUFACTURER_ABB, 0x67, 0x06, true, false);
tst_address_match("12345678.M=ABB.V=67.T=06", "12345678", MANUFACTURER_ABB, 0x68, 0x06, false, false);
tst_address_match("12345678.M=ABB.V=67.T=06", "12345678", MANUFACTURER_ABB, 0x67, 0x07, false, false);
tst_address_match("12345678.M=ABB.V=67.T=06", "12345678", MANUFACTURER_ABB+1, 0x67, 0x06, false, false);
tst_address_match("12345678.M=ABB.V=67.T=06", "12345677", MANUFACTURER_ABB, 0x67, 0x06, false, false);
// Now verify filter out ! character. The filter out does notchange the test. It is still the same
// test, but the match will be used as a filter out. Ie if the match triggers, then the telegram will be filtered out.
tst_address_match("!12345678", "12345677", 1, 1, 1, false, false);
tst_address_match("!*.M=ABB", "99999999", MANUFACTURER_ABB, 1, 1, true, true);
tst_address_match("*.M=ABB", "99999999", MANUFACTURER_ABB, 1, 1, true, false);
// Test that both id wildcard matches and the version.
tst_address_match("9*.V=06", "99999999", MANUFACTURER_ABB, 0x06, 1, true, false);
tst_address_match("9*.V=06", "89999999", MANUFACTURER_ABB, 0x06, 1, false, false);
tst_address_match("9*.V=06", "99999999", MANUFACTURER_ABB, 0x07, 1, false, false);
tst_address_match("9*.V=06", "89999999", MANUFACTURER_ABB, 0x07, 1, false, false);
// Test the same, expect same answers but check that filtered out is set.
tst_address_match("!9*.V=06", "99999999", MANUFACTURER_ABB, 0x06, 1, true, true);
tst_address_match("!9*.V=06", "89999999", MANUFACTURER_ABB, 0x06, 1, false, true);
tst_address_match("!9*.V=06", "99999999", MANUFACTURER_ABB, 0x07, 1, false, true);
tst_address_match("!9*.V=06", "89999999", MANUFACTURER_ABB, 0x07, 1, false, true);
}
void eq(string a, string b, const char *tn)
@ -2177,9 +2255,9 @@ void test_formulas_building_meters()
MeterKeys mk;
t.parse(frame, &mk, true);
string id;
vector<Address> addresses;
bool match;
meter->handleTelegram(t.about, frame, true, &id, &match, &t);
meter->handleTelegram(t.about, frame, true, &addresses, &match, &t);
f->clear();
f->setMeter(meter.get());
@ -2228,7 +2306,7 @@ void test_formulas_building_meters()
MeterKeys mk;
t.parse(frame, &mk, true);
string id;
vector<Address> id;
bool match;
meter->handleTelegram(t.about, frame, true, &id, &match, &t);
@ -2412,7 +2490,7 @@ void test_formulas_parsing_1()
MeterKeys mk;
t.parse(frame, &mk, true);
string id;
vector<Address> id;
bool match;
meter->handleTelegram(t.about, frame, true, &id, &match, &t);
@ -2462,7 +2540,7 @@ void test_formulas_parsing_2()
MeterKeys mk;
t.parse(frame, &mk, true);
string id;
vector<Address> id;
bool match;
meter->handleTelegram(t.about, frame, true, &id, &match, &t);

Wyświetl plik

@ -676,24 +676,6 @@ bool isNumber(const string& fq)
return true;
}
vector<string> splitMatchExpressions(const string& mes)
{
vector<string> r;
bool eof, err;
vector<uchar> v (mes.begin(), mes.end());
auto i = v.begin();
for (;;) {
auto id = eatTo(v, i, ',', 16, &eof, &err);
if (err) break;
trimWhitespace(&id);
if (id == "ANYID") id = "*";
r.push_back(id);
if (eof) break;
}
return r;
}
void incrementIV(uchar *iv, size_t len) {
uchar *p = iv+len-1;
while (p >= iv) {

Wyświetl plik

@ -229,17 +229,18 @@ LIST_OF_MANUFACTURERS
}
void Telegram::addId(const vector<uchar>::iterator &pos)
void Telegram::addAddressMfctFirst(const vector<uchar>::iterator &pos)
{
// string id = tostrprintf("%02x%02x%02x%02x_%02x_%02x", *(pos+3), *(pos+2), *(pos+1), *(pos+0), *(pos+4), *(pos+5));
string id = tostrprintf("%02x%02x%02x%02x", *(pos+3), *(pos+2), *(pos+1), *(pos+0));
ids.push_back(id);
if (idsc.empty()) {
idsc = id;
}
else {
idsc += "," + id;
}
Address a;
a.decodeMfctFirst(pos);
addresses.push_back(a);
}
void Telegram::addAddressIdFirst(const vector<uchar>::iterator &pos)
{
Address a;
a.decodeIdFirst(pos);
addresses.push_back(a);
}
void Telegram::print()
@ -453,29 +454,6 @@ string manufacturer(int m_field) {
return "Unknown";
}
string manufacturerFlag(int m_field) {
char a = (m_field/1024)%32+64;
char b = (m_field/32)%32+64;
char c = (m_field)%32+64;
string flag;
flag += a;
flag += b;
flag += c;
return flag;
}
bool flagToManufacturer(const char *s, uint16_t *out_mfct)
{
if (s[0] == 0 || s[1] == 0 || s[2] == 0 || s[3] != 0) return false;
if (s[0] < '@' || s[0] > 'Z' ||
s[1] < '@' || s[1] > 'Z' ||
s[2] < '@' || s[2] > 'Z') return false;
*out_mfct = MANFCODE(s[0],s[1],s[2]);
return true;
}
string mediaType(int a_field_device_type, int m_field) {
switch (a_field_device_type) {
case 0: return "Other";
@ -926,9 +904,10 @@ bool Telegram::parseMBusDLLandTPL(vector<uchar>::iterator &pos)
addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x dll-a primary (%d)", mbus_primary_address, mbus_primary_address);
// Add dll_id to ids.
string id = tostrprintf("%02x", mbus_primary_address);
ids.push_back(id);
idsc = id;
string id = tostrprintf("p%d", mbus_primary_address);
Address a;
a.id = id;
addresses.push_back(a);
mbus_ci = *pos;
addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x tpl-ci (%s)", mbus_ci, mbusCiField(mbus_ci));
@ -954,6 +933,9 @@ bool Telegram::parseDLL(vector<uchar>::iterator &pos)
dll_c = *pos;
addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x dll-c (%s)", dll_c, cType(dll_c).c_str());
CHECK(8)
addAddressMfctFirst(pos);
dll_mfct_b[0] = *(pos+0);
dll_mfct_b[1] = *(pos+1);
dll_mfct = dll_mfct_b[1] <<8 | dll_mfct_b[0];
@ -973,9 +955,8 @@ bool Telegram::parseDLL(vector<uchar>::iterator &pos)
}
}
// Add dll_id to ids.
addId(pos);
addExplanationAndIncrementPos(pos, 4, KindOfData::PROTOCOL, Understanding::FULL, "%02x%02x%02x%02x dll-id (%s)",
*(pos+0), *(pos+1), *(pos+2), *(pos+3), ids.back().c_str());
*(pos+0), *(pos+1), *(pos+2), *(pos+3), addresses.back().id.c_str());
dll_version = *(pos+0);
dll_type = *(pos+1);
@ -1049,6 +1030,9 @@ bool Telegram::parseELL(vector<uchar>::iterator &pos)
if (has_target_mft_address)
{
CHECK(8);
addAddressMfctFirst(pos);
ell_mfct_b[0] = *(pos+0);
ell_mfct_b[1] = *(pos+1);
ell_mfct = ell_mfct_b[1] << 8 | ell_mfct_b[0];
@ -1063,7 +1047,6 @@ bool Telegram::parseELL(vector<uchar>::iterator &pos)
ell_id_b[3] = *(pos+3);
// Add ell_id to ids.
addId(pos);
addExplanationAndIncrementPos(pos, 4, KindOfData::PROTOCOL, Understanding::FULL, "%02x%02x%02x%02x ell-id",
ell_id_b[0], ell_id_b[1], ell_id_b[2], ell_id_b[3]);
@ -1459,7 +1442,9 @@ bool Telegram::parseShortTPL(std::vector<uchar>::iterator &pos)
bool Telegram::parseLongTPL(std::vector<uchar>::iterator &pos)
{
CHECK(4);
CHECK(8);
addAddressIdFirst(pos);
tpl_id_found = true;
tpl_id_b[0] = *(pos+0);
tpl_id_b[1] = *(pos+1);
@ -1472,9 +1457,6 @@ bool Telegram::parseLongTPL(std::vector<uchar>::iterator &pos)
tpl_a[i] = *(pos+i);
}
// Add the tpl_id to ids.
addId(pos);
addExplanationAndIncrementPos(pos, 4, KindOfData::PROTOCOL, Understanding::FULL,
"%02x%02x%02x%02x tpl-id (%02x%02x%02x%02x)",
tpl_id_b[0], tpl_id_b[1], tpl_id_b[2], tpl_id_b[3],

Wyświetl plik

@ -18,6 +18,7 @@
#ifndef WMBUS_H
#define WMBUS_H
#include"address.h"
#include"dvparser.h"
#include"manufacturers.h"
#include"serial.h"
@ -416,10 +417,9 @@ public:
// If a warning is printed mark this.
bool triggered_warning {};
// The different ids found, the first is th dll_id, ell_id, nwl_id, and the last is the tpl_id.
vector<string> ids;
// Ids separated by commas
string idsc;
// The different addresses found,
// the first is the dll_id_mvt, ell_id_mvt, nwl_id_mvt, and the last is the tpl_id_mvt.
vector<Address> addresses;
// If decryption failed, set this to true, to prevent further processing.
bool decryption_failed {};
@ -536,7 +536,8 @@ public:
bool parseHANHeader(vector<uchar> &input_frame);
bool parseHAN(vector<uchar> &input_frame, MeterKeys *mk, bool warn);
void addId(const vector<uchar>::iterator &pos);
void addAddressMfctFirst(const vector<uchar>::iterator &pos);
void addAddressIdFirst(const vector<uchar>::iterator &pos);
void print();
@ -741,8 +742,6 @@ shared_ptr<BusDevice> openSimulator(Detected detected,
shared_ptr<SerialDevice> serial_override);
string manufacturer(int m_field);
string manufacturerFlag(int m_field);
bool flagToManufacturer(const char *s, uint16_t *out_mfct);
string mediaType(int a_field_device_type, int m_field);
string mediaTypeJSON(int a_field_device_type, int m_field);
bool isCiFieldOfType(int ci_field, CI_TYPE type);

Wyświetl plik

@ -36,11 +36,11 @@ TESTRESULT="ERROR"
cat > $TEST/test_expected.txt <<EOF
(wmbus) WARNING!! telegram should have been fully encrypted, but was not! id: 88888888 mfct: (APA) Apator, Poland (0x601) type: Water meter (0x07) ver: 0x05
(meter) newly created meter (ApWater 88888888 apator162) did not handle telegram!
(meter) newly created meter (ApWater 88888888.M=APA.V=05.T=07 apator162) did not handle telegram!
(wmbus) WARNING! decrypted payload crc failed check, did you use the correct decryption key? e1d6 payload crc (calculated a528) 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!
(meter) newly created meter (Vatten 76348799.M=KAM.V=1b.T=16 multical21) did not handle telegram!
(wmbus) WARNING!! telegram should have been fully encrypted, but was not! id: 77777777 mfct: (SON) Sontex, Switzerland (0x4dee) type: Water meter (0x07) ver: 0x3c
(meter) newly created meter (Wasser 77777777 supercom587) did not handle telegram!
(meter) newly created meter (Wasser 77777777.M=SON.V=3c.T=07 supercom587) did not handle telegram!
(wmbus) WARNING!!! telegram should have been encrypted, but was not! id: 77777777 mfct: (SON) Sontex, Switzerland (0x4dee) type: Water meter (0x07) ver: 0x3c
EOF

Wyświetl plik

@ -181,6 +181,14 @@ cat > $TEST/test_expected.txt <<EOF
-------------------------------------------------------------------------------
There should be three fields, for example: mvt = AAA,07,05
-------------------------------------------------------------------------------
(driver) error in testoutput/driver.xmq, cannot find any detection triplets: driver/detect/mvt
-------------------------------------------------------------------------------
Remember to add: detect { mvt = AAA,05,07 mvt = AAA,06,07 ... }
The triplets consists of MANUFACTURER,VERSION,TYPE
You can see these values when listening to all meters.
The manufacturer can be given as three uppercase characters A-Z
or as 4 lower case hex digits.
-------------------------------------------------------------------------------
Failed to load driver from file: testoutput/driver.xmq
EOF

Wyświetl plik

@ -13,7 +13,7 @@ $PROG --format=json simulations/simulation_bad_keys.txt room fhkvdataiv 03065716
cat > $TEST/expected_err.txt <<EOF
(wmbus) WARNING! no key to decrypt payload! Permanently ignoring telegrams from id: 03065716 mfct: (TCH) Techem Service (0x5068) type: Heat Cost Allocator (0x08) ver: 0x94
(meter) newly created meter (room 03065716 fhkvdataiv) did not handle telegram!
(meter) newly created meter (room 03065716.M=TCH.V=94.T=08 fhkvdataiv) did not handle telegram!
EOF
diff $TEST/test_stderr.txt $TEST/expected_err.txt
@ -28,7 +28,7 @@ $PROG --format=json simulations/simulation_bad_keys.txt room fhkvdataiv 03065716
cat > $TEST/expected_err.txt <<EOF
(wmbus) WARNING!! decrypted content failed check, did you use the correct decryption key? Permanently ignoring telegrams from id: 03065716 mfct: (TCH) Techem Service (0x5068) type: Heat Cost Allocator (0x08) ver: 0x94
(meter) newly created meter (room 03065716 fhkvdataiv) did not handle telegram!
(meter) newly created meter (room 03065716.M=TCH.V=94.T=08 fhkvdataiv) did not handle telegram!
EOF
diff $TEST/test_stderr.txt $TEST/expected_err.txt

Wyświetl plik

@ -28,9 +28,9 @@ fi
cat <<EOF > $TEST/test_expected.txt
Started config rtlwmbus on stdin listening on any
(wmbus) WARNING!! 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!
(meter) newly created meter (ApWater 88888888.M=APA.V=05.T=07 apator162) did not handle telegram!
(wmbus) WARNING! 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!
(meter) newly created meter (Vatten 76348799.M=KAM.V=1b.T=16 multical21) did not handle telegram!
EOF
diff $TEST/test_expected.txt $TEST/test_stderr.txt