kopia lustrzana https://github.com/weetmuts/wmbusmeters
Merge remote-tracking branch 'origin/master' into fb_add_itron_ultramaxx
commit
c5ab867acd
10
CHANGES
10
CHANGES
|
@ -10,6 +10,16 @@ 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
|
||||
|
||||
Added option --identitymode=(id|id-mfct|full|none) to specify how
|
||||
wmbusmeters groups meter state when receiving telegrams.
|
||||
|
||||
The default (which is the same as before) is to map state based only on id.
|
||||
This usually works ok, however if you have two meters with the same id, but
|
||||
from different manufacturers, you must separate their state with --identitymode=id-mfct
|
||||
Full takes into account version and type as well. None means do not separate state
|
||||
at all, used with wildcards and meters that do not need to keep state, ie all info
|
||||
is in every telegram.
|
||||
|
||||
Version 1.16.1 2024-02-22
|
||||
|
||||
Fix docker file generation.
|
||||
|
|
30
README.md
30
README.md
|
@ -6,6 +6,33 @@ wireless wm-bus meters. The readings can then be published using
|
|||
MQTT, curled to a REST api, inserted into a database or stored in a
|
||||
log file.
|
||||
|
||||
# What does it do?
|
||||
|
||||
Wmbusmeters converts incoming telegrams from (w)mbus/OMS compatible meters like:
|
||||
`1844AE4C4455223368077A55000000_041389E20100023B0000`
|
||||
|
||||
into human readable:
|
||||
`MyTapWater 33225544 123.529 m³ 0 m³/h 2024-03-03 19:36:22`
|
||||
|
||||
or into csv:
|
||||
`MyTapWater;33225544;123.529;0;2024-03-03 19:36:45`
|
||||
|
||||
or into json:
|
||||
```json
|
||||
{
|
||||
"media":"water",
|
||||
"meter":"iperl",
|
||||
"name":"MyTapWater",
|
||||
"id":"33225544",
|
||||
"max_flow_m3h":0,
|
||||
"total_m3":123.529,
|
||||
"timestamp":"2024-03-03T18:37:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
Wmbusmeters can collect telegrams from radio using hardware dongles or rtl-sdr software radio dongles,
|
||||
or from m-bus meters using serial ports, or from files/pipes.
|
||||
|
||||
[FAQ/WIKI/MANUAL pages](https://wmbusmeters.github.io/wmbusmeters-wiki/)
|
||||
|
||||
The program runs on GNU/Linux, MacOSX, FreeBSD, and Raspberry Pi.
|
||||
|
@ -175,7 +202,7 @@ And an mbus meter file in /etc/wmbusmeters.d/MyTempHygro
|
|||
```ini
|
||||
name=MyTempHygro
|
||||
id=11223344
|
||||
driver=piigth:mbus
|
||||
driver=piigth:MAIN:mbus
|
||||
pollinterval=60s
|
||||
```
|
||||
|
||||
|
@ -425,6 +452,7 @@ As {options} you can use:
|
|||
--exitafter=<time> exit program after time, eg 20h, 10m 5s
|
||||
--format=<hr/json/fields> for human readable, json or semicolon separated fields
|
||||
--help list all options
|
||||
--identitymode=(id|id-mfct|full|none) group meter state based on the identity mode. Default is id.
|
||||
--ignoreduplicates=<bool> ignore duplicate telegrams, remember the last 10 telegrams
|
||||
--field_xxx=yyy always add "xxx"="yyy" to the json output and add shell env METER_xxx=yyy (--json_xxx=yyy also works)
|
||||
--license print GPLv3+ license
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
telegram=|7B4479169977997730378C208B900F002C25E4EF0A002EA98E7D58B3ADC57299779977991611028B005087102F2F#0DFD090F34302e3030562030303030303030300D790E31323334353637383839595345310DFD100AAAAAAAAAAAAAAAAAAAAA0D780E31323334353637383930594553312F2F2F2F2F2F2F2F2F2F2F|
|
||||
telegram=|7B4479169977997730378C20F0900F002C2549EE0A0077C19D3D1A08ABCD729977997779161102F0005007102F2F#0702F5C3FA000000000007823C5407000000000000841004E081020084200415000000042938AB000004A9FF01FA0A000004A9FF02050A000004A9FF03389600002F2F2F2F2F2F2F2F2F2F2F2F2F|
|
|
@ -0,0 +1,4 @@
|
|||
telegram=|414493447514916746377275149167934446044D000020_0C06490000004C0600000000426CFF2CCC080611000000C2086C1F3102FD170000326CFFFF046D330F1432|
|
||||
telegram=|5b44934475149167463778077975149167934446040dff5f3500823d0000810007c006ffff49000000ff2c000000001f3111000000008000800080008000800080008000800080000000000B002f02fd170000046d390d1432488408|
|
||||
telegram=|414493447514916746377275149167934446044D000020_0C06490000004C0600000000426CFF2CCC080611000000C2086C1F3102FD170000326CFFFF046D330F1432|
|
||||
telegram=|5b44934475149167463778077975149167934446040dff5f3500823d0000810007c006ffff49000000ff2c000000001f3111000000008000800080008000800080008000800080000000000B002f02fd170000046d390d1432488408|
|
335
src/address.cc
335
src/address.cc
|
@ -19,10 +19,20 @@
|
|||
#include"manufacturers.h"
|
||||
|
||||
#include<assert.h>
|
||||
#include<algorithm>
|
||||
#include<string.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
vector<string> splitSequenceOfAddressExpressionsAtCommas(const string& mes);
|
||||
bool isValidMatchExpression(const std::string& s, bool *has_wildcard);
|
||||
bool doesIdMatchExpression(const std::string& id, std::string match_rule);
|
||||
bool doesAddressMatchExpressions(Address &address,
|
||||
std::vector<AddressExpression>& address_expressions,
|
||||
bool *used_wildcard,
|
||||
bool *filtered_out,
|
||||
bool *required_found,
|
||||
bool *required_failed);
|
||||
|
||||
bool isValidMatchExpression(const string& s, bool *has_wildcard)
|
||||
{
|
||||
|
@ -89,7 +99,7 @@ vector<string> splitSequenceOfAddressExpressionsAtCommas(const string& mes)
|
|||
auto i = v.begin();
|
||||
|
||||
for (;;) {
|
||||
auto id = eatTo(v, i, ',', 16, &eof, &err);
|
||||
auto id = eatTo(v, i, ',', 64, &eof, &err);
|
||||
if (err) break;
|
||||
trimWhitespace(&id);
|
||||
if (id == "ANYID") id = "*";
|
||||
|
@ -180,180 +190,6 @@ bool hasWildCard(const string& mes)
|
|||
return mes.find('*') != string::npos;
|
||||
}
|
||||
|
||||
bool doesIdsMatchExpressionss(vector<string> &ids, vector<string>& mes, bool *used_wildcard)
|
||||
{
|
||||
bool match = false;
|
||||
for (string &id : ids)
|
||||
{
|
||||
if (doesIdMatchExpressionss(id, mes, 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 doesIdMatchExpressionss(const string& id, vector<string>& mes, 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 (string me : mes)
|
||||
{
|
||||
bool has_wildcard = hasWildCard(me);
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
for (string& s: ids)
|
||||
{
|
||||
cs += s;
|
||||
cs += ",";
|
||||
}
|
||||
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;
|
||||
|
@ -364,6 +200,36 @@ bool AddressExpression::match(const std::string &i, uint16_t m, uchar v, uchar t
|
|||
return true;
|
||||
}
|
||||
|
||||
void AddressExpression::trimToIdentity(IdentityMode im, Address &a)
|
||||
{
|
||||
switch (im)
|
||||
{
|
||||
case IdentityMode::FULL:
|
||||
id = a.id;
|
||||
mfct = a.mfct;
|
||||
version = a.version;
|
||||
type = a.type;
|
||||
required = true;
|
||||
break;
|
||||
case IdentityMode::ID_MFCT:
|
||||
id = a.id;
|
||||
mfct = a.mfct;
|
||||
version = 0xff;
|
||||
type = 0xff;
|
||||
required = true;
|
||||
break;
|
||||
case IdentityMode::ID:
|
||||
id = a.id;
|
||||
mfct = 0xffff;
|
||||
version = 0xff;
|
||||
type = 0xff;
|
||||
required = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool AddressExpression::parse(const string &in)
|
||||
{
|
||||
string s = in;
|
||||
|
@ -446,6 +312,19 @@ bool AddressExpression::parse(const string &in)
|
|||
bool ok = flagToManufacturer(&parts[i][2], &mfct);
|
||||
if (!ok) return false;
|
||||
}
|
||||
else if (parts[i].size() == 6) // M=abcd explicit hex version
|
||||
{
|
||||
if (parts[i][1] != '=') return false;
|
||||
if (parts[i][0] != 'M') return false;
|
||||
|
||||
vector<uchar> data;
|
||||
bool ok = hex2bin(&parts[i][2], &data);
|
||||
if (!ok) return false;
|
||||
if (data.size() != 2) return false;
|
||||
|
||||
mfct = data[1] << 8 | data[0];
|
||||
if (!ok) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
|
@ -471,6 +350,7 @@ string AddressExpression::str()
|
|||
string s;
|
||||
|
||||
if (filter_out) s = "!";
|
||||
if (required) s = "R";
|
||||
|
||||
s.append(id);
|
||||
if (mfct != 0xffff)
|
||||
|
@ -565,21 +445,37 @@ bool doesTelegramMatchExpressions(std::vector<Address> &addresses,
|
|||
bool *used_wildcard)
|
||||
{
|
||||
bool match = false;
|
||||
bool filtered_out = false;
|
||||
bool required_found = false; // An R12345678 field was found.
|
||||
bool required_failed = true; // Init to fail, set to true if R is satistifed anywhere.
|
||||
|
||||
for (Address &a : addresses)
|
||||
{
|
||||
if (doesAddressMatchExpressions(a, address_expressions, used_wildcard))
|
||||
if (doesAddressMatchExpressions(a,
|
||||
address_expressions,
|
||||
used_wildcard,
|
||||
&filtered_out,
|
||||
&required_found,
|
||||
&required_failed))
|
||||
{
|
||||
match = true;
|
||||
}
|
||||
// Go through all ids even though there is an early match.
|
||||
// This way we can see if theres an exact match later.
|
||||
}
|
||||
// If any expression triggered a filter out, then the whole telegram does not match.
|
||||
if (filtered_out) match = false;
|
||||
// If a required field was found and it failed....
|
||||
if (required_found && required_failed) match = false;
|
||||
return match;
|
||||
}
|
||||
|
||||
bool doesAddressMatchExpressions(Address &address,
|
||||
vector<AddressExpression>& address_expressions,
|
||||
bool *used_wildcard)
|
||||
bool *used_wildcard,
|
||||
bool *filtered_out,
|
||||
bool *required_found,
|
||||
bool *required_failed)
|
||||
{
|
||||
bool found_match = false;
|
||||
bool found_negative_match = false;
|
||||
|
@ -597,12 +493,18 @@ bool doesAddressMatchExpressions(Address &address,
|
|||
|
||||
// If a positive match is found, using a wildcard not any exact match,
|
||||
// then *used_wildcard is set to true.
|
||||
|
||||
// If an expression is required and it fails, then the match fails.
|
||||
for (AddressExpression &ae : address_expressions)
|
||||
{
|
||||
bool has_wildcard = ae.has_wildcard;
|
||||
bool is_negative_rule = ae.filter_out;
|
||||
// We currently assume that only a single expression is required, the last one!
|
||||
bool is_required = ae.required;
|
||||
|
||||
bool m = doesIdMatchExpression(address.id, ae.id);
|
||||
if (is_required) *required_found = true;
|
||||
|
||||
bool m = ae.match(address.id, address.mfct, address.version, address.type);
|
||||
|
||||
if (is_negative_rule)
|
||||
{
|
||||
|
@ -612,16 +514,25 @@ bool doesAddressMatchExpressions(Address &address,
|
|||
{
|
||||
if (m)
|
||||
{
|
||||
found_match = true;
|
||||
if (!has_wildcard)
|
||||
// A match, but the required does not count.
|
||||
if (!is_required)
|
||||
{
|
||||
exact_match = true;
|
||||
found_match = true;
|
||||
if (!has_wildcard)
|
||||
{
|
||||
exact_match = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
*required_failed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found_negative_match)
|
||||
{
|
||||
*filtered_out = true;
|
||||
return false;
|
||||
}
|
||||
if (found_match)
|
||||
|
@ -638,3 +549,65 @@ bool doesAddressMatchExpressions(Address &address,
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *toString(IdentityMode im)
|
||||
{
|
||||
switch (im)
|
||||
{
|
||||
case IdentityMode::ID: return "id";
|
||||
case IdentityMode::ID_MFCT: return "id-mfct";
|
||||
case IdentityMode::FULL: return "full";
|
||||
case IdentityMode::NONE: return "none";
|
||||
case IdentityMode::INVALID: return "invalid";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
IdentityMode toIdentityMode(const char *s)
|
||||
{
|
||||
if (!strcmp(s,"id")) return IdentityMode::ID;
|
||||
if (!strcmp(s,"id-mfct")) return IdentityMode::ID_MFCT;
|
||||
if (!strcmp(s, "full")) return IdentityMode::FULL;
|
||||
if (!strcmp(s, "none")) return IdentityMode::NONE;
|
||||
return IdentityMode::INVALID;
|
||||
}
|
||||
|
||||
void AddressExpression::clear()
|
||||
{
|
||||
id = "";
|
||||
has_wildcard = false;
|
||||
mbus_primary = false;
|
||||
mfct = 0xffff;
|
||||
version = 0xff;
|
||||
type = 0xff;
|
||||
}
|
||||
|
||||
void AddressExpression::appendIdentity(IdentityMode im,
|
||||
AddressExpression *identity_expression,
|
||||
std::vector<Address> &as,
|
||||
std::vector<AddressExpression> &es)
|
||||
{
|
||||
identity_expression->clear();
|
||||
if (im == IdentityMode::NONE) return;
|
||||
|
||||
// Copy id, id-mfct, id-mfct-v-t to identity_expression from the last address.
|
||||
identity_expression->trimToIdentity(im, as.back());
|
||||
|
||||
// Is this identity expression already in the list of address expressions?
|
||||
if (std::find(es.begin(), es.end(), *identity_expression) == es.end())
|
||||
{
|
||||
// No, then add it at the end.
|
||||
es.push_back(*identity_expression);
|
||||
}
|
||||
}
|
||||
|
||||
bool AddressExpression::operator==(const AddressExpression&ae) const
|
||||
{
|
||||
return id == ae.id &&
|
||||
has_wildcard == ae.has_wildcard&&
|
||||
mbus_primary == ae.mbus_primary &&
|
||||
mfct == ae.mfct &&
|
||||
version == ae.version &&
|
||||
type == ae.type &&
|
||||
filter_out == ae.filter_out;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,28 @@
|
|||
#include "util.h"
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
IdentityMode:
|
||||
|
||||
@ID: The default, only the id groups the meter content.
|
||||
@ID_MFCT: Used when you have two meters with the same id but different manufacturers.
|
||||
@FULL: Used when you want to fully separate meter content on id.mft.v.t
|
||||
@NONE: Do not separate any meters! This might lead to telegrams overwriting each others state.
|
||||
Use this when no state is to be kept in the wmbusmeters object.
|
||||
@INVALID: Cannot parse cmdline.
|
||||
*/
|
||||
enum class IdentityMode
|
||||
{
|
||||
ID,
|
||||
ID_MFCT,
|
||||
FULL,
|
||||
NONE,
|
||||
INVALID
|
||||
};
|
||||
|
||||
const char *toString(IdentityMode im);
|
||||
IdentityMode toIdentityMode(const char *s);
|
||||
|
||||
struct Address
|
||||
{
|
||||
std::string id; // p1 or 12345678 or non-compliant hex: 1234abcd
|
||||
|
@ -53,18 +75,26 @@ struct AddressExpression
|
|||
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.
|
||||
uint16_t mfct { 0xffff }; // If 0xffff then any mfct matches this address.
|
||||
uchar version { 0xff }; // If 0xff then any version matches this address.
|
||||
uchar type { 0xff }; // If 0xff then any type matches this address.
|
||||
|
||||
bool filter_out {}; // Telegrams matching this rule should be filtered out!
|
||||
bool required {}; // If true, then this address expression must be matched!
|
||||
|
||||
AddressExpression() {}
|
||||
AddressExpression(Address &a) : id(a.id), mfct(a.mfct), version(a.version), type(a.type) { }
|
||||
bool operator==(const AddressExpression&) const;
|
||||
void clear();
|
||||
void trimToIdentity(IdentityMode im, Address &a);
|
||||
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);
|
||||
static void appendIdentity(IdentityMode im,
|
||||
AddressExpression *identity_expression,
|
||||
std::vector<Address> &as,
|
||||
std::vector<AddressExpression> &es);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -77,33 +107,11 @@ struct AddressExpression
|
|||
!*.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 doesIdMatchExpressionss(const std::string& id,
|
||||
std::vector<std::string>& match_rules,
|
||||
bool *used_wildcard);
|
||||
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);
|
||||
|
||||
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
|
||||
|
|
|
@ -617,6 +617,15 @@ static shared_ptr<Configuration> parseNormalCommandLine(Configuration *c, int ar
|
|||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!strncmp(argv[i], "--identitymode=", 15) && strlen(argv[i]) > 15) {
|
||||
c->identity_mode = toIdentityMode(argv[i]+15);
|
||||
if (c->identity_mode == IdentityMode::INVALID)
|
||||
{
|
||||
error("Not a valid identity mode. \"%s\"\n", argv[i]+15);
|
||||
}
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!strncmp(argv[i], "--resetafter=", 13) && strlen(argv[i]) > 13) {
|
||||
c->resetafter = parseTime(argv[i]+13);
|
||||
if (c->resetafter <= 0) {
|
||||
|
@ -728,12 +737,13 @@ static shared_ptr<Configuration> parseNormalCommandLine(Configuration *c, int ar
|
|||
string bus;
|
||||
string name = argv[m*4+i+0];
|
||||
string driver = argv[m*4+i+1];
|
||||
string id = argv[m*4+i+2];
|
||||
string address_expressions = argv[m*4+i+2];
|
||||
string key = argv[m*4+i+3];
|
||||
|
||||
MeterInfo mi;
|
||||
mi.parse(name, driver, id, key);
|
||||
mi.parse(name, driver, address_expressions, key);
|
||||
mi.poll_interval = c->pollinterval;
|
||||
mi.identity_mode = c->identity_mode;
|
||||
|
||||
if (mi.driver_name.str() == "")
|
||||
{
|
||||
|
|
|
@ -53,10 +53,11 @@ void parseMeterConfig(Configuration *c, vector<char> &buf, string file)
|
|||
string bus;
|
||||
string name;
|
||||
string driver = "auto";
|
||||
string id;
|
||||
string address_expressions;
|
||||
string key = "";
|
||||
string linkmodes;
|
||||
int poll_interval = 0;
|
||||
IdentityMode identity_mode {};
|
||||
vector<string> telegram_shells;
|
||||
vector<string> meter_shells;
|
||||
vector<string> alarm_shells;
|
||||
|
@ -108,7 +109,7 @@ void parseMeterConfig(Configuration *c, vector<char> &buf, string file)
|
|||
else
|
||||
if (p.first == "driver") driver = p.second;
|
||||
else
|
||||
if (p.first == "id") id = p.second;
|
||||
if (p.first == "id") address_expressions = p.second;
|
||||
else
|
||||
if (p.first == "key")
|
||||
{
|
||||
|
@ -129,6 +130,15 @@ void parseMeterConfig(Configuration *c, vector<char> &buf, string file)
|
|||
}
|
||||
}
|
||||
else
|
||||
if (p.first == "identitymode") {
|
||||
identity_mode = toIdentityMode(p.second.c_str());
|
||||
|
||||
if (identity_mode == IdentityMode::INVALID)
|
||||
{
|
||||
error("Invalid identity mode: \"%s\"!\n", p.second.c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
if (p.first == "shell") {
|
||||
telegram_shells.push_back(p.second);
|
||||
}
|
||||
|
@ -176,11 +186,12 @@ void parseMeterConfig(Configuration *c, vector<char> &buf, string file)
|
|||
|
||||
MeterInfo mi;
|
||||
|
||||
mi.parse(name, driver, id, key); // sets driver, extras, name, bus, bps, link_modes, ids, name, key
|
||||
mi.parse(name, driver, address_expressions, key); // sets driver, extras, name, bus, bps, link_modes, ids, name, key
|
||||
mi.poll_interval = poll_interval;
|
||||
mi.identity_mode = identity_mode;
|
||||
|
||||
if (!isValidSequenceOfAddressExpressions(id)) {
|
||||
warning("Not a valid meter id nor a valid sequence of match expression \"%s\"\n", id.c_str());
|
||||
if (!isValidSequenceOfAddressExpressions(address_expressions)) {
|
||||
warning("Not a valid meter id nor a valid sequence of match expression \"%s\"\n", address_expressions.c_str());
|
||||
use = false;
|
||||
}
|
||||
if (!isValidKey(key, mi)) {
|
||||
|
|
|
@ -99,6 +99,7 @@ struct Configuration
|
|||
bool json {};
|
||||
bool pretty_print_json {};
|
||||
int pollinterval {}; // Time between polling of mbus meters.
|
||||
IdentityMode identity_mode {}; // How to group meters identities into state objects.
|
||||
bool fields {};
|
||||
char separator { ';' };
|
||||
std::vector<std::string> telegram_shells;
|
||||
|
|
|
@ -403,6 +403,14 @@ void log_start_information(Configuration *config)
|
|||
verbose("(config) using device: %s \n", specified_device.str().c_str());
|
||||
}
|
||||
verbose("(config) number of meters: %d\n", config->meters.size());
|
||||
if (isDebugEnabled())
|
||||
{
|
||||
for (MeterInfo &m : config->meters)
|
||||
{
|
||||
string aes = AddressExpression::concat(m.address_expressions);
|
||||
debug("(config) template %s %s %s\n", m.name.c_str(), aes.c_str(), m.str().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void oneshot_check(Configuration *config, Telegram *t, Meter *meter)
|
||||
|
|
|
@ -177,15 +177,28 @@ public:
|
|||
{
|
||||
// We found a match, make a copy of the meter info.
|
||||
MeterInfo meter_info = mi;
|
||||
// Overwrite the wildcard pattern with the highest level id.
|
||||
// The last id in the t.ids is the highest level id.
|
||||
// For example: a telegram can have dll_id,tpl_id
|
||||
// This will pick the tpl_id.
|
||||
// Or a telegram can have a single dll_id,
|
||||
// then the dll_id will be picked.
|
||||
vector<AddressExpression> aes;
|
||||
aes.push_back(AddressExpression(t.addresses.back()));
|
||||
meter_info.address_expressions = aes;
|
||||
// Append the identity to the address expressions.
|
||||
// The identity is by default the highest level id found.
|
||||
// I.e. often the tpl_id. This is the last element in t->addresses.
|
||||
//
|
||||
// When instantiating a meter from a template we
|
||||
// make sure the meter triggers exactly on this identity.
|
||||
// So we append the identity to the address expressions.
|
||||
//
|
||||
// E.g. if the template address expression is 12*.M=PII and the meter
|
||||
// 12345678 is received then the meters address expressions
|
||||
// will be: 12*.M=PII,12345678
|
||||
//
|
||||
// The default type of identity can be changed.
|
||||
// identitymode=id
|
||||
// identitymode=id-mfct
|
||||
// identitymode=full
|
||||
// identitymode=none
|
||||
AddressExpression identity_expression;
|
||||
AddressExpression::appendIdentity(mi.identity_mode,
|
||||
&identity_expression,
|
||||
t.addresses,
|
||||
meter_info.address_expressions);
|
||||
|
||||
if (meter_info.driverName().str() == "auto")
|
||||
{
|
||||
|
@ -221,20 +234,24 @@ public:
|
|||
if (is_daemon_)
|
||||
{
|
||||
string mi_idsc = AddressExpression::concat(mi.address_expressions);
|
||||
notice("(wmbusmeters) started meter %d (%s %s %s)\n",
|
||||
notice("(wmbusmeters) started meter %d (%s %s %s) identity mode: %s %s\n",
|
||||
meter->index(),
|
||||
mi.name.c_str(),
|
||||
mi_idsc.c_str(),
|
||||
mi.driverName().str().c_str());
|
||||
mi.driverName().str().c_str(),
|
||||
toString(mi.identity_mode),
|
||||
identity_expression.str().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
string mi_idsc = AddressExpression::concat(mi.address_expressions);
|
||||
verbose("(meter) started meter %d (%s %s %s)\n",
|
||||
verbose("(meter) started meter %d (%s %s %s) identity mode: %s %s\n",
|
||||
meter->index(),
|
||||
mi.name.c_str(),
|
||||
mi_idsc.c_str(),
|
||||
mi.driverName().str().c_str());
|
||||
mi.driverName().str().c_str(),
|
||||
toString(mi.identity_mode),
|
||||
identity_expression.str().c_str());
|
||||
}
|
||||
|
||||
bool match = false;
|
||||
|
|
116
src/meters.cc
116
src/meters.cc
|
@ -330,6 +330,7 @@ MeterCommonImplementation::MeterCommonImplementation(MeterInfo &mi,
|
|||
waiting_for_poll_response_sem_("waiting_for_poll_response")
|
||||
{
|
||||
address_expressions_ = mi.address_expressions;
|
||||
identity_mode_ = mi.identity_mode;
|
||||
link_modes_ = mi.link_modes;
|
||||
poll_interval_= mi.poll_interval;
|
||||
|
||||
|
@ -785,6 +786,11 @@ vector<AddressExpression>& MeterCommonImplementation::addressExpressions()
|
|||
return address_expressions_;
|
||||
}
|
||||
|
||||
IdentityMode MeterCommonImplementation::identityMode()
|
||||
{
|
||||
return identity_mode_;
|
||||
}
|
||||
|
||||
vector<FieldInfo> &MeterCommonImplementation::fieldInfos()
|
||||
{
|
||||
return field_infos_;
|
||||
|
@ -934,11 +940,14 @@ bool MeterCommonImplementation::isTelegramForMeter(Telegram *t, Meter *meter, Me
|
|||
}
|
||||
|
||||
bool used_wildcard = false;
|
||||
bool id_match = doesTelegramMatchExpressions(t->addresses, address_expressions, &used_wildcard);
|
||||
bool match = doesTelegramMatchExpressions(t->addresses,
|
||||
address_expressions,
|
||||
&used_wildcard);
|
||||
|
||||
if (!id_match) {
|
||||
if (!match)
|
||||
{
|
||||
// The id must match.
|
||||
debug("(meter) %s: not for me: not my id\n", name.c_str());
|
||||
debug("(meter) %s: not for me: no match\n", name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -954,16 +963,11 @@ bool MeterCommonImplementation::isTelegramForMeter(Telegram *t, Meter *meter, Me
|
|||
// this particular driver, mfct, media, version combo
|
||||
// is not registered in the METER_DETECTION list in meters.h
|
||||
|
||||
/*
|
||||
if (used_wildcard)
|
||||
{
|
||||
// The match for the id was not exact, thus the user is listening using a wildcard
|
||||
// to many meters and some received matched meter telegrams are not from the right meter type,
|
||||
// ie their driver does not match. Lets just ignore telegrams that probably cannot be decoded properly.
|
||||
verbose("(meter) ignoring telegram from %s since it matched a wildcard id rule but driver (%s) does not match.\n",
|
||||
t->idsc.c_str(), driver_name.c_str());
|
||||
return false;
|
||||
}*/
|
||||
// There was an attempt to give up here if there was a wildcard and it was the wrong driver.
|
||||
// However some users did expect it to work anyway! This might make sense
|
||||
// in the future when we have even better dynamic drivers.
|
||||
// It already make sense if you create an amalgamation driver for several different
|
||||
// types of meters and want to force the use of this driver.
|
||||
|
||||
// The match was exact, ie the user has actually specified 12345678 and foo as driver even
|
||||
// though they do not match. Lets warn and then proceed. It is common that a user tries a
|
||||
|
@ -1043,6 +1047,21 @@ string findField(string key, vector<string> *extra_constant_fields)
|
|||
return "";
|
||||
}
|
||||
|
||||
string build_id(Address &a, IdentityMode im)
|
||||
{
|
||||
string id = a.id;
|
||||
if (im == IdentityMode::ID_MFCT ||
|
||||
im == IdentityMode::FULL)
|
||||
{
|
||||
id += string(".M=")+manufacturerFlag(a.mfct);
|
||||
}
|
||||
if (im == IdentityMode::FULL)
|
||||
{
|
||||
id += tostrprintf(".V=%02x.T=%02x", a.version, a.type);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
// Is the desired field one of the fields common to all meters and telegrams?
|
||||
bool checkCommonField(string *buf, string desired_field, Meter *m, Telegram *t, char c, bool human_readable)
|
||||
{
|
||||
|
@ -1053,7 +1072,8 @@ bool checkCommonField(string *buf, string desired_field, Meter *m, Telegram *t,
|
|||
}
|
||||
if (desired_field == "id")
|
||||
{
|
||||
*buf += t->addresses.back().id + c;
|
||||
string id = build_id(t->addresses.back(), m->identityMode());
|
||||
*buf += id + c;
|
||||
return true;
|
||||
}
|
||||
if (desired_field == "timestamp")
|
||||
|
@ -1804,7 +1824,7 @@ void MeterCommonImplementation::printMeter(Telegram *t,
|
|||
string id = "";
|
||||
if (t->addresses.size() > 0)
|
||||
{
|
||||
id = t->addresses.back().id;
|
||||
id = build_id(t->addresses.back(), identityMode());
|
||||
}
|
||||
|
||||
string indent = "";
|
||||
|
@ -1864,72 +1884,6 @@ void MeterCommonImplementation::printMeter(Telegram *t,
|
|||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
for (FieldInfo& fi : field_infos_)
|
||||
{
|
||||
if (fi.printProperties().hasHIDE()) continue;
|
||||
|
||||
// The field should be printed in the json. (Most usually should.)
|
||||
for (auto& i : t->dv_entries)
|
||||
{
|
||||
// Check each telegram dv entry.
|
||||
DVEntry *dve = &i.second.second;
|
||||
// Has the entry been matches to this field, then print it as json.
|
||||
if (dve->hasFieldInfo(&fi))
|
||||
{
|
||||
assert(founds[&fi].count(dve) == 0);
|
||||
|
||||
founds[&fi].insert(dve);
|
||||
string field_name = fi.generateFieldNameNoUnit(dve);
|
||||
found_vnames.insert(field_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (FieldInfo& fi : field_infos_)
|
||||
{
|
||||
if (fi.printProperties().hasHIDE()) continue;
|
||||
|
||||
if (founds.count(&fi) != 0)
|
||||
{
|
||||
// This field info has matched against some dventries.
|
||||
for (DVEntry *dve : founds[&fi])
|
||||
{
|
||||
debug("(meters) render field %s(%s %s)[%d] with dventry @%d key %s data %s\n",
|
||||
fi.vname().c_str(), toString(fi.xuantity()), unitToStringLowerCase(fi.displayUnit()).c_str(), fi.index(),
|
||||
dve->offset,
|
||||
dve->dif_vif_key.str().c_str(),
|
||||
dve->value.c_str());
|
||||
string out = fi.renderJson(this, dve);
|
||||
debug("(meters) %s\n", out.c_str());
|
||||
s += indent+out+","+newline;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ok, no value found in received telegram.
|
||||
// Print field anyway if it is required,
|
||||
// or if a value has been received before and this field has not been received using a different rule.
|
||||
// Why this complicated rule?
|
||||
// E.g. the minmoess mbus seems to use storage 1 for target_m3 but the wmbus version uses storage 8.
|
||||
// I.e. we have two rules that store into target_m3, this check will prevent target_m3 from being printed twice.
|
||||
if (fi.printProperties().hasREQUIRED() ||
|
||||
(hasValue(&fi) && (
|
||||
found_vnames.count(fi.vname()) == 0 ||
|
||||
fi.hasFormula()))) // TODO! Fix so a new field total_l does not overwrite total_m3 in mem.
|
||||
{
|
||||
// No telegram entries found, but this field should be printed anyway.
|
||||
// It will be printed with any value received from a previous telegram.
|
||||
// Or if no value has been received, null.
|
||||
debug("(meters) render field %s(%s)[%d] without dventry\n",
|
||||
fi.vname().c_str(), toString(fi.xuantity()), fi.index());
|
||||
string out = fi.renderJson(this, NULL);
|
||||
debug("(meters) %s\n", out.c_str());
|
||||
s += indent+out+","+newline;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
s += indent+"\"timestamp\":\""+datetimeOfUpdateRobot()+"\"";
|
||||
|
||||
if (t->about.device != "")
|
||||
|
|
|
@ -91,6 +91,7 @@ struct MeterInfo
|
|||
DriverName driver_name; // Will replace MeterDriver.
|
||||
string extras; // Extra driver specific settings.
|
||||
vector<AddressExpression> address_expressions; // Match expressions for ids.
|
||||
IdentityMode identity_mode {}; // How to group telegram content into objects with state. Default is by id.
|
||||
string key; // Decryption key.
|
||||
LinkModeSet link_modes;
|
||||
int bps {}; // For mbus communication you need to know the baud rate.
|
||||
|
@ -373,6 +374,7 @@ struct Meter
|
|||
virtual string bus() = 0;
|
||||
// This meter listens to these address expressions.
|
||||
virtual std::vector<AddressExpression>& addressExpressions() = 0;
|
||||
virtual IdentityMode identityMode() = 0;
|
||||
// This meter can report these fields, like total_m3, temp_c.
|
||||
virtual vector<FieldInfo> &fieldInfos() = 0;
|
||||
virtual vector<string> &extraConstantFields() = 0;
|
||||
|
|
|
@ -60,6 +60,7 @@ struct MeterCommonImplementation : public virtual Meter
|
|||
void setIndex(int i);
|
||||
string bus();
|
||||
vector<AddressExpression>& addressExpressions();
|
||||
IdentityMode identityMode();
|
||||
vector<FieldInfo> &fieldInfos();
|
||||
vector<string> &extraConstantFields();
|
||||
string name();
|
||||
|
@ -84,7 +85,6 @@ struct MeterCommonImplementation : public virtual Meter
|
|||
static bool isTelegramForMeter(Telegram *t, Meter *meter, MeterInfo *mi);
|
||||
MeterKeys *meterKeys();
|
||||
|
||||
// MeterCommonImplementation(MeterInfo &mi, string driver);
|
||||
MeterCommonImplementation(MeterInfo &mi, DriverInfo &di);
|
||||
|
||||
~MeterCommonImplementation() = default;
|
||||
|
@ -222,6 +222,7 @@ private:
|
|||
TPLSecurityMode expected_tpl_sec_mode_ {};
|
||||
string name_;
|
||||
vector<AddressExpression> address_expressions_;
|
||||
IdentityMode identity_mode_;
|
||||
vector<function<void(Telegram*,Meter*)>> on_update_;
|
||||
int num_updates_ {};
|
||||
time_t datetime_of_update_ {};
|
||||
|
|
|
@ -575,6 +575,42 @@ void tst_address_match(string expr, string id, uint16_t m, uchar v, uchar t, boo
|
|||
}
|
||||
}
|
||||
|
||||
void tst_telegram_match(string addresses, string expressions, bool match, bool uw)
|
||||
{
|
||||
vector<AddressExpression> exprs = splitAddressExpressions(expressions);
|
||||
vector<AddressExpression> as = splitAddressExpressions(addresses);
|
||||
vector<Address> addrs;
|
||||
|
||||
for (auto &ad : as)
|
||||
{
|
||||
Address a;
|
||||
a.id = ad.id;
|
||||
a.mfct = ad.mfct;
|
||||
a.version = ad.version;
|
||||
a.type = ad.type;
|
||||
|
||||
addrs.push_back(a);
|
||||
}
|
||||
|
||||
bool used_wildcard = false;
|
||||
bool m = doesTelegramMatchExpressions(addrs, exprs, &used_wildcard);
|
||||
|
||||
if (m != match)
|
||||
{
|
||||
printf("Expected addresses %s to %smatch expressions %s\n",
|
||||
addresses.c_str(),
|
||||
match?"":"NOT ",
|
||||
expressions.c_str());
|
||||
}
|
||||
if (uw != used_wildcard)
|
||||
{
|
||||
printf("Expected addresses %s from match expression %s %susing wildcard\n",
|
||||
addresses.c_str(),
|
||||
expressions.c_str(),
|
||||
uw?"":"NOT ");
|
||||
}
|
||||
}
|
||||
|
||||
void test_addresses()
|
||||
{
|
||||
tst_address("12345678",
|
||||
|
@ -634,6 +670,33 @@ void test_addresses()
|
|||
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);
|
||||
|
||||
tst_telegram_match("12345678", "12345678", true, false);
|
||||
tst_telegram_match("11111111,22222222", "12345678,22*", true, true);
|
||||
tst_telegram_match("11111111,22222222", "12345678,22222222", true, false);
|
||||
tst_telegram_match("11111111.M=KAM,22222222.M=PII", "11111111.M=KAM", true, false);
|
||||
tst_telegram_match("11111111.M=KAF", "11111111.M=KAM", false, false);
|
||||
|
||||
tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.M=KAM", true, false);
|
||||
tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.M=KAF", false, false);
|
||||
tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111", true, false);
|
||||
tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.M=KAM", true, false);
|
||||
tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.V=1b", true, false);
|
||||
tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.T=16", true, false);
|
||||
tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.M=KAM.T=16", true, false);
|
||||
tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.M=KAM.V=1b", true, false);
|
||||
tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.T=16.V=1b", true, false);
|
||||
|
||||
tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.M=KAL", false, false);
|
||||
tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.V=1c", false, false);
|
||||
tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.T=17", false, false);
|
||||
tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.M=KAM.T=17", false, false);
|
||||
tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.M=KAL.V=1b", false, false);
|
||||
tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.T=17.V=1b", false, false);
|
||||
|
||||
// Test * matches both 11111111 and 2222222 but the only the 111111 matches the filter out V=1b.
|
||||
// Verify that the filter out !1*.V=1b will override successfull match (with no filter out) * for 22222222.
|
||||
tst_telegram_match("11111111.M=KAM.V=1b.T=16,22222222.M=XXX.V=aa.T=99", "*,!1*.V=1b", false, true);
|
||||
}
|
||||
|
||||
void eq(string a, string b, const char *tn)
|
||||
|
|
12
test.sh
12
test.sh
|
@ -123,6 +123,18 @@ if [ "$?" != "0" ]; then RC="1"; fi
|
|||
tests/test_additional_json.sh $PROG
|
||||
if [ "$?" != "0" ]; then RC="1"; fi
|
||||
|
||||
tests/test_addresses.sh $PROG
|
||||
if [ "$?" != "0" ]; then RC="1"; fi
|
||||
|
||||
tests/test_address_filtering.sh $PROG
|
||||
if [ "$?" != "0" ]; then RC="1"; fi
|
||||
|
||||
tests/test_address_dll.sh $PROG
|
||||
if [ "$?" != "0" ]; then RC="1"; fi
|
||||
|
||||
tests/test_identity_mode.sh $PROG
|
||||
if [ "$?" != "0" ]; then RC="1"; fi
|
||||
|
||||
tests/test_rtlwmbus.sh $PROG
|
||||
if [ "$?" != "0" ]; then RC="1"; fi
|
||||
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
#!/bin/sh
|
||||
|
||||
PROG="$1"
|
||||
|
||||
mkdir -p testoutput
|
||||
TEST=testoutput
|
||||
|
||||
TESTNAME="Test addresses dll"
|
||||
TESTRESULT="OK"
|
||||
|
||||
# Test that selecting the meter using the dll id instead of the tpl id works.
|
||||
OUT=$($PROG --format=fields --selectfields=total_m3 6644496A1064035514377251345015496A0007EE0050052F2F_0C1359000000026CBE2B82046CA12B8C0413FFFFFFFF8D0493132CFBFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF02FD1700002F2F W minomess 55036410 NOKEY)
|
||||
|
||||
if [ "$OUT" != "0.059" ]
|
||||
then
|
||||
echo "ERROR: Test addresses dll failed"
|
||||
else
|
||||
echo "OK: Test addresses dll"
|
||||
fi
|
||||
|
||||
# Test that selecting the meter using the tpl id instead of the dll id works.
|
||||
OUT=$($PROG --format=fields --selectfields=total_m3 6644496A1064035514377251345015496A0007EE0050052F2F_0C1359000000026CBE2B82046CA12B8C0413FFFFFFFF8D0493132CFBFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF02FD1700002F2F W minomess 15503451 NOKEY)
|
||||
|
||||
if [ "$OUT" != "0.059" ]
|
||||
then
|
||||
echo "ERROR: Test addresses tpl failed"
|
||||
else
|
||||
echo "OK: Test addresses tpl"
|
||||
fi
|
||||
|
||||
OUT=$($PROG --format=fields --selectfields=total_m3 6644496A1064035514377251345015496A0007EE0050052F2F_0C1359000000026CBE2B82046CA12B8C0413FFFFFFFF8D0493132CFBFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF02FD1700002F2F W minomess '*.T=07' NOKEY)
|
||||
|
||||
if [ "$OUT" != "0.059" ]
|
||||
then
|
||||
echo "ERROR: Test addresses *.T=07 failed"
|
||||
else
|
||||
echo "OK: Test addresses *.T=07"
|
||||
fi
|
||||
|
||||
OUT=$($PROG --format=fields --selectfields=total_m3 6644496A1064035514377251345015496A0007EE0050052F2F_0C1359000000026CBE2B82046CA12B8C0413FFFFFFFF8D0493132CFBFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF02FD1700002F2F W minomess '*.T=37' NOKEY)
|
||||
|
||||
if [ "$OUT" != "0.059" ]
|
||||
then
|
||||
echo "ERROR: Test addresses *.T=37 failed"
|
||||
else
|
||||
echo "OK: Test addresses *.T=37"
|
||||
fi
|
|
@ -0,0 +1,28 @@
|
|||
#!/bin/sh
|
||||
|
||||
PROG="$1"
|
||||
|
||||
mkdir -p testoutput
|
||||
TEST=testoutput
|
||||
|
||||
TESTNAME="Test address type filtering"
|
||||
TESTRESULT="OK"
|
||||
|
||||
$PROG --format=fields simulations/simulation_qheat_bad.txt HEAT qheat '67911475.T=04' NOKEY > $TEST/test_output.txt 2>&1
|
||||
|
||||
cat $TEST/test_output.txt | sed 's/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9].[0-9][0-9]$/1111-11-11 11:11.11/' > $TEST/test_responses.txt
|
||||
|
||||
cat > $TEST/test_expected.txt <<EOF
|
||||
HEAT;67911475;49;2024-01-31;11;1111-11-11 11:11.11
|
||||
EOF
|
||||
|
||||
if ! diff $TEST/test_expected.txt $TEST/test_responses.txt
|
||||
then
|
||||
if [ "$USE_MELD" = "true" ]
|
||||
then
|
||||
meld $TEST/test_expected.txt $TEST/test_responses.txt
|
||||
fi
|
||||
echo "ERROR: $TESTNAME"
|
||||
else
|
||||
echo OK: $TESTNAME
|
||||
fi
|
|
@ -0,0 +1,84 @@
|
|||
#!/bin/sh
|
||||
|
||||
PROG="$1"
|
||||
|
||||
mkdir -p testoutput
|
||||
TEST=testoutput
|
||||
|
||||
TESTNAME="Test addresses"
|
||||
TESTRESULT="OK"
|
||||
|
||||
# dll-mfct (ESY) dll-id (77887788) dll-version (30) dll-type (37 Radio converter (meter side))
|
||||
# tpl-id (77997799) tpl-mfct (ESY) tpl-version (11) tpl-type (02 Electricity meter)
|
||||
TELEGRAM=7B4479168877887730378C20F0900F002C2549EE0A0077C19D3D1A08ABCD729977997779161102F0005007102F2F_0702F5C3FA000000000007823C5407000000000000841004E081020084200415000000042938AB000004A9FF01FA0A000004A9FF02050A000004A9FF03389600002F2F2F2F2F2F2F2F2F2F2F2F2F
|
||||
ARGS="--format=fields --selectfields=total_energy_consumption_kwh $TELEGRAM EL esyswm"
|
||||
|
||||
checkResult() {
|
||||
F=$($PROG $ARGS "$E" NOKEY)
|
||||
if [ "$F" != "1643.4165" ]
|
||||
then
|
||||
echo "EXPECTED 1643.4165 *********************************************"
|
||||
echo "E=$E"
|
||||
echo $PROG $ARGS "$E" NOKEY
|
||||
$PROG $ARGS "$E" NOKEY
|
||||
echo "*********************************************"
|
||||
TESTRESULT=ERROR
|
||||
fi
|
||||
}
|
||||
|
||||
expectEmpty() {
|
||||
F=$($PROG $ARGS "$E" NOKEY)
|
||||
if [ "$F" != "" ]
|
||||
then
|
||||
echo "EXPECTED EMPTY OUTPUT *********************************************"
|
||||
echo "E=$E"
|
||||
echo $PROG $ARGS "$E" NOKEY
|
||||
$PROG $ARGS "$E" NOKEY
|
||||
echo "*********************************************"
|
||||
TESTRESULT=ERROR
|
||||
fi
|
||||
}
|
||||
|
||||
E=77997799
|
||||
checkResult
|
||||
|
||||
E=77997799.M=ESY
|
||||
checkResult
|
||||
|
||||
E=77997799.M=PII
|
||||
expectEmpty
|
||||
|
||||
E=77*
|
||||
checkResult
|
||||
|
||||
E=*
|
||||
checkResult
|
||||
|
||||
E=ANYID
|
||||
checkResult
|
||||
|
||||
E=77997799.T=02
|
||||
checkResult
|
||||
|
||||
E=77887788.T=02
|
||||
expectEmpty
|
||||
|
||||
E=7788*.T=37.V=30.M=ESY
|
||||
checkResult
|
||||
|
||||
E=77997799,!*.V=88
|
||||
checkResult
|
||||
|
||||
E=*.T=02
|
||||
checkResult
|
||||
|
||||
E=*.T=02,!77*
|
||||
expectEmpty
|
||||
|
||||
E=*.T=02,!77*.V=11
|
||||
expectEmpty
|
||||
|
||||
E=7788*.T=37,!7799*.T=02
|
||||
expectEmpty
|
||||
|
||||
echo "$TESTRESULT: $TESTNAME"
|
|
@ -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.M=APA.V=05.T=07 apator162) did not handle telegram!
|
||||
(meter) newly created meter (ApWater 88888888 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.M=KAM.V=1b.T=16 multical21) did not handle telegram!
|
||||
(meter) newly created meter (Vatten 76348799 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.M=SON.V=3c.T=07 supercom587) did not handle telegram!
|
||||
(meter) newly created meter (Wasser 77777777 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
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
#!/bin/sh
|
||||
|
||||
PROG="$1"
|
||||
|
||||
mkdir -p testoutput
|
||||
TEST=testoutput
|
||||
|
||||
TESTNAME="Test identity mode"
|
||||
TESTRESULT="OK"
|
||||
|
||||
# dll-mfct (ESY) dll-id (77887788) dll-version (30) dll-type (37 Radio converter (meter side))
|
||||
# tpl-id (77997799) tpl-mfct (ESY) tpl-version (11) tpl-type (02 Electricity meter)
|
||||
|
||||
$PROG --identitymode=id --format=fields simulations/simulation_identity_mode.txt EL esyswm 77997799 NOKEY > $TEST/test_output.txt 2>&1
|
||||
$PROG --identitymode=id-mfct --format=fields simulations/simulation_identity_mode.txt EL esyswm 77997799 NOKEY >> $TEST/test_output.txt 2>&1
|
||||
$PROG --identitymode=full --format=fields simulations/simulation_identity_mode.txt EL esyswm 77997799 NOKEY >> $TEST/test_output.txt 2>&1
|
||||
|
||||
cat $TEST/test_output.txt | sed 's/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9].[0-9][0-9]$/1111-11-11 11:11.11/' > $TEST/test_responses.txt
|
||||
|
||||
cat > $TEST/test_expected.txt <<EOF
|
||||
EL;77997799;null;null;null;null;null;null;null;null;1ESY9887654321;1111-11-11 11:11.11
|
||||
EL;77997799;1643.4165;0.43832;0.1876;1643.2;0.21;0.0281;0.02565;0.38456;1ESY9887654321;1111-11-11 11:11.11
|
||||
EL;77997799.M=ETY;null;null;null;null;null;null;null;null;1ESY9887654321;1111-11-11 11:11.11
|
||||
EL;77997799.M=ESY;1643.4165;0.43832;0.1876;1643.2;0.21;0.0281;0.02565;0.38456;null;1111-11-11 11:11.11
|
||||
EL;77997799.M=ETY.V=11.T=02;null;null;null;null;null;null;null;null;1ESY9887654321;1111-11-11 11:11.11
|
||||
EL;77997799.M=ESY.V=11.T=02;1643.4165;0.43832;0.1876;1643.2;0.21;0.0281;0.02565;0.38456;null;1111-11-11 11:11.11
|
||||
EOF
|
||||
|
||||
if ! diff $TEST/test_expected.txt $TEST/test_responses.txt
|
||||
then
|
||||
if [ "$USE_MELD" = "true" ]
|
||||
then
|
||||
meld $TEST/test_expected.txt $TEST/test_responses.txt
|
||||
fi
|
||||
echo "ERROR: $TESTNAME"
|
||||
else
|
||||
echo OK: $TESTNAME
|
||||
fi
|
|
@ -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.M=TCH.V=94.T=08 fhkvdataiv) did not handle telegram!
|
||||
(meter) newly created meter (room 03065716 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.M=TCH.V=94.T=08 fhkvdataiv) did not handle telegram!
|
||||
(meter) newly created meter (room 03065716 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.M=APA.V=05.T=07 apator162) did not handle telegram!
|
||||
(meter) newly created meter (ApWater 88888888 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.M=KAM.V=1b.T=16 multical21) did not handle telegram!
|
||||
(meter) newly created meter (Vatten 76348799 multical21) did not handle telegram!
|
||||
EOF
|
||||
|
||||
diff $TEST/test_expected.txt $TEST/test_stderr.txt
|
||||
|
|
|
@ -53,6 +53,8 @@ Add :verbose to any analyze to get more verbose analyze output.
|
|||
|
||||
\fB\--help\fR list all options
|
||||
|
||||
\fB\--identitymode\fR=(id,id-mfct,full,none) group meter state based on the identity mode. Default is id.
|
||||
|
||||
\fB\--ignoreduplicates\fR=<bool> ignore duplicate telegrams, remember the last 10 telegrams. Default is true.
|
||||
|
||||
\fB\--field_xxx=yyy\fR always add "xxx"="yyy" to the json output and add shell env METER_xxx=yyy The field xxx can also be selected or added using selectfields=. Equivalent older command is --json_xxx=yyy.
|
||||
|
@ -176,7 +178,7 @@ and the other set to beta. Alfa has an antenna tuned for 433M, beta has an anten
|
|||
.TP
|
||||
\fBmeter_type\fR for example multical21:t1 (suffix means that we expect this meter to transmit t1 telegrams) the driver auto can be used, but is not recommended for production.
|
||||
.TP
|
||||
\fBmeter_id\fR one or more 8 digit numbers separated with commas, a single '*' wildcard, or a prefix '76543*' with wildcard.
|
||||
\fBmeter_id\fR one or more addresses separated with commas, a single '*' wildcard, or a prefix '76543*' with wildcard. You can as a suffix fully or partially specify manufacturer, version and type: 12345678.M=KAM.V=1b.T=16 You can use p0 to p250 to specify an mbus primary address. You can filter out telegrams: 76543*,!76543210
|
||||
.TP
|
||||
\fBmeter_key\fR a unique key for the meter, if meter telegrams are not encrypted, you must supply an empty key: ""
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue