kopia lustrzana https://github.com/weetmuts/wmbusmeters
Merge remote-tracking branch 'origin/master' into fb_add_itron_ultramaxx
commit
879127ac8f
12
CHANGES
12
CHANGES
|
@ -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.
|
||||
|
|
1
Makefile
1
Makefile
|
@ -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
|
||||
|
|
14
README.md
14
README.md
|
@ -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
|
||||
|
|
428
src/address.cc
428
src/address.cc
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
216
src/meters.cc
216
src/meters.cc
|
@ -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)
|
||||
|
|
22
src/meters.h
22
src/meters.h
|
@ -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;
|
||||
|
|
|
@ -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_ {};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
18
src/util.cc
18
src/util.cc
|
@ -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) {
|
||||
|
|
68
src/wmbus.cc
68
src/wmbus.cc
|
@ -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],
|
||||
|
|
13
src/wmbus.h
13
src/wmbus.h
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Ładowanie…
Reference in New Issue