kopia lustrzana https://github.com/weetmuts/wmbusmeters
New advanced addressing works.
rodzic
78e7c47503
commit
7634b95438
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.
|
||||
|
|
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
|
||||
|
|
245
src/address.cc
245
src/address.cc
|
@ -22,6 +22,8 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
vector<string> splitSequenceOfAddressExpressionsAtCommas(const string& mes);
|
||||
|
||||
bool isValidMatchExpression(const string& s, bool *has_wildcard)
|
||||
{
|
||||
string me = s;
|
||||
|
@ -37,9 +39,12 @@ bool isValidMatchExpression(const string& s, bool *has_wildcard)
|
|||
// 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;
|
||||
|
||||
|
@ -76,29 +81,51 @@ bool isValidMatchExpression(const string& s, bool *has_wildcard)
|
|||
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, NULL)) 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)
|
||||
|
@ -153,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;
|
||||
}
|
||||
|
@ -168,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;
|
||||
|
@ -315,6 +342,18 @@ string toIdsCommaSeparated(vector<string> &ids)
|
|||
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;
|
||||
|
@ -351,8 +390,9 @@ bool AddressExpression::parse(const string &in)
|
|||
{
|
||||
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);
|
||||
|
@ -425,3 +465,176 @@ bool flagToManufacturer(const char *s, uint16_t *out_mfct)
|
|||
*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;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,20 @@
|
|||
#include "util.h"
|
||||
#include <string>
|
||||
|
||||
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.
|
||||
|
@ -35,37 +49,61 @@ struct AddressExpression
|
|||
// Or every telegram which is does not start with 12 and is not from ABB:
|
||||
// !12*.M!=ABB
|
||||
|
||||
std::string id; // 1 or 12345678 or non-compliant hex: 1234abcd
|
||||
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 type {}; // If 0xff then any type 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);
|
||||
};
|
||||
|
||||
/**
|
||||
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 isValidMatchExpressions(const std::string& s);
|
||||
|
||||
|
||||
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<std::string> splitMatchExpressions(const std::string& mes);
|
||||
std::vector<AddressExpression> splitAddressExpressions(const std::string &aes);
|
||||
|
||||
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;
|
||||
|
|
135
src/meters.cc
135
src/meters.cc
|
@ -327,8 +327,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;
|
||||
|
||||
|
@ -673,25 +672,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;
|
||||
}
|
||||
|
||||
|
@ -699,7 +698,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
|
||||
|
@ -707,21 +706,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;
|
||||
}
|
||||
|
||||
|
@ -739,19 +738,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);
|
||||
|
@ -767,26 +766,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()
|
||||
|
@ -852,7 +846,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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -906,8 +903,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) ||
|
||||
|
@ -916,22 +912,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.
|
||||
|
@ -1050,7 +1051,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")
|
||||
|
@ -1194,7 +1195,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;
|
||||
|
@ -1203,7 +1205,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))
|
||||
{
|
||||
|
@ -1212,12 +1214,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.
|
||||
|
@ -1744,11 +1753,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());
|
||||
|
||||
|
@ -1791,9 +1800,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 = "";
|
||||
|
@ -1941,7 +1950,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);
|
||||
|
@ -2093,11 +2102,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;
|
||||
}
|
||||
|
||||
|
@ -2165,12 +2178,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;
|
||||
|
|
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;
|
||||
|
|
|
@ -39,7 +39,6 @@ bool verbose_ = false;
|
|||
|
||||
#define LIST_OF_TESTS \
|
||||
X(addresses) \
|
||||
/*
|
||||
X(dynamic_loading) \
|
||||
X(crc) \
|
||||
X(dvparser) \
|
||||
|
@ -77,8 +76,6 @@ bool verbose_ = false;
|
|||
X(formulas_dventries) \
|
||||
X(formulas_stringinterpolation) \
|
||||
|
||||
*/
|
||||
|
||||
#define X(t) void test_##t();
|
||||
LIST_OF_TESTS
|
||||
#undef X
|
||||
|
@ -427,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)
|
||||
{
|
||||
|
@ -441,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)
|
||||
|
@ -2254,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());
|
||||
|
@ -2305,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);
|
||||
|
||||
|
@ -2489,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);
|
||||
|
||||
|
@ -2539,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) {
|
||||
|
|
57
src/wmbus.cc
57
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,18 +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;
|
||||
}
|
||||
|
||||
string mediaType(int a_field_device_type, int m_field) {
|
||||
switch (a_field_device_type) {
|
||||
case 0: return "Other";
|
||||
|
@ -915,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));
|
||||
|
@ -943,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];
|
||||
|
@ -962,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);
|
||||
|
@ -1038,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];
|
||||
|
@ -1052,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]);
|
||||
|
||||
|
@ -1448,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);
|
||||
|
@ -1461,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],
|
||||
|
|
12
src/wmbus.h
12
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,7 +742,6 @@ shared_ptr<BusDevice> openSimulator(Detected detected,
|
|||
shared_ptr<SerialDevice> serial_override);
|
||||
|
||||
string manufacturer(int m_field);
|
||||
string manufacturerFlag(int m_field);
|
||||
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