Added support for negative id match rules.

pull/31/head 0.9.14
weetmuts 2019-09-16 16:32:24 +02:00
rodzic 04be63eb12
commit cd4820a357
9 zmienionych plików z 247 dodań i 37 usunięć

11
CHANGES
Wyświetl plik

@ -1,3 +1,14 @@
Version 0.9.14: 2019-09-16
Added negative match rule for ids. You can now write:
id=78*,!7812345*,!78222222
which will match any meter whose id begins with 78
but not match any meter whose id begins with 7812345,
nor the meter with the exact id 78222222.
The order of the match rules does not matter.
Version 0.9.13: 2019-08-14
Fix bug that prevented rtl_wmbus to run inside daemon.

Wyświetl plik

@ -56,7 +56,9 @@ The latest reading of the meter can also be found here: /var/log/wmbusmeters/met
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.
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*`
which will match all meter ids, except those that begin with 2222.
If you are running on a Raspberry PI with flash storage and you relay the data to
another computer using a shell command (mosquitto_pub or curl or similar) then you might want to remove

Wyświetl plik

@ -338,7 +338,7 @@ unique_ptr<Configuration> parseCommandLine(int argc, char **argv) {
mt = toMeterType(type);
if (mt == MeterType::UNKNOWN) error("Not a valid meter type \"%s\"\n", type.c_str());
if (!isValidId(id)) error("Not a valid meter id \"%s\"\n", id.c_str());
if (!isValidMatchExpressions(id, false)) error("Not a valid id nor a valid meter match expression \"%s\"\n", id.c_str());
if (!isValidKey(key)) error("Not a valid meter key \"%s\"\n", key.c_str());
vector<string> no_meter_shells;
c->meters.push_back(MeterInfo(name, type, id, key, modes, no_meter_shells));

Wyświetl plik

@ -116,8 +116,8 @@ void parseMeterConfig(Configuration *c, vector<char> &buf, string file)
warning("Not a valid meter type \"%s\"\n", type.c_str());
use = false;
}
if (!isValidId(id)) {
warning("Not a valid meter id \"%s\"\n", id.c_str());
if (!isValidMatchExpression(id, false)) {
warning("Not a valid meter id nor a valid meter match expression \"%s\"\n", id.c_str());
use = false;
}
if (!isValidKey(key)) {

Wyświetl plik

@ -29,7 +29,7 @@ MeterCommonImplementation::MeterCommonImplementation(WMBus *bus, MeterInfo &mi,
type_(type), name_(mi.name), bus_(bus)
{
use_aes_ = true;
ids_ = splitIds(mi.id);
ids_ = splitMatchExpressions(mi.id);
if (mi.key.length() == 0) {
use_aes_ = false;
} else {
@ -169,26 +169,7 @@ bool MeterCommonImplementation::isTelegramForMe(Telegram *t)
{
debug("(meter) %s: for me? %s\n", name_.c_str(), t->id.c_str());
bool id_match = false;
for (auto id : ids_)
{
if (id == t->id) {
id_match = true;
break;
}
if (id == "*") {
id_match = true;
break;
}
if (id.length() > 1 && id.back() == '*')
{
if (t->id.length() >= id.length())
{
string prefix = t->id.substr(0, id.length()-1);
id_match = !strncmp(prefix.c_str(), id.c_str(), id.length()-1);
}
}
}
bool id_match = doesIdMatchExpressions(t->id, ids_);
if (!id_match) {
// The id must match.

Wyświetl plik

@ -31,6 +31,7 @@ using namespace std;
int test_crc();
int test_dvparser();
int test_linkmodes();
void test_ids();
int main(int argc, char **argv)
{
@ -41,6 +42,7 @@ int main(int argc, char **argv)
test_crc();
test_dvparser();
test_linkmodes();
test_ids();
return 0;
}
@ -317,3 +319,68 @@ int test_linkmodes()
return 0;
}
void test_valid_match_expression(string s, bool expected)
{
bool b = isValidMatchExpressions(s, false);
if (b == expected) return;
if (expected == true)
{
printf("ERROR! Expected \"%s\" to be valid! But it was not!\n", s.c_str());
}
else
{
printf("ERROR! Expected \"%s\" to be invalid! But it was reported as valid!\n", s.c_str());
}
}
void test_does_id_match_expression(string id, string mes, bool expected)
{
vector<string> expressions = splitMatchExpressions(mes);
bool b = doesIdMatchExpressions(id, expressions);
if (b == expected) return;
if (expected == true)
{
printf("ERROR! Expected \"%s\" to match \"%s\" ! But it did not!\n", id.c_str(), mes.c_str());
}
else
{
printf("ERROR! Expected \"%s\" to NOT match \"%s\" ! But it did!\n", id.c_str(), mes.c_str());
}
}
void test_ids()
{
test_valid_match_expression("12345678", true);
test_valid_match_expression("*", true);
test_valid_match_expression("!12345678", true);
test_valid_match_expression("12345*", true);
test_valid_match_expression("!123456*", true);
test_valid_match_expression("1234567", false);
test_valid_match_expression("", false);
test_valid_match_expression("z1234567", false);
test_valid_match_expression("123456789", false);
test_valid_match_expression("!!12345678", false);
test_valid_match_expression("12345678*", false);
test_valid_match_expression("**", false);
test_valid_match_expression("123**", false);
test_valid_match_expression("2222*,!22224444", true);
test_does_id_match_expression("12345678", "12345678", true);
test_does_id_match_expression("12345678", "*", true);
test_does_id_match_expression("12345678", "2*", false);
test_does_id_match_expression("12345678", "*,!2*", true);
test_does_id_match_expression("22222222", "22*,!22222222", false);
test_does_id_match_expression("22222223", "22*,!22222222", true);
test_does_id_match_expression("22222223", "*,!22*", false);
test_does_id_match_expression("12333333", "123*,!1234*,!1235*,!1236*", true);
test_does_id_match_expression("12366666", "123*,!1234*,!1235*,!1236*", false);
test_does_id_match_expression("11223344", "22*,33*,44*,55*", false);
test_does_id_match_expression("55223344", "22*,33*,44*,55*", true);
test_does_id_match_expression("78563413", "78563412,78563413", true);
}

Wyświetl plik

@ -371,22 +371,165 @@ void error(const char* fmt, ...)
exit(1);
}
bool isValidId(string& ids)
bool isValidMatchExpression(string me, bool non_compliant)
{
vector<string> v = splitIds(ids);
// Examples of valid match expressions:
// 12345678
// *
// 123*
// !12345677
// 2222222*
// !22222222
for (auto id : v)
// A match expression cannot be empty.
if (me.length() == 0) return false;
// An me can be negated with an exclamation mark first.
if (me.front() == '!') me.erase(0, 1);
// A match expression cannot be only a negation mark.
if (me.length() == 0) return false;
int count = 0;
if (non_compliant)
{
if (id == "*") return true;
if (id.back() == '*') return true;
if (id.length() != 8) return false;
for (int i=0; i<8; ++i) {
if (id[i]<'0' || id[i]>'9') return false;
// Some non-compliant meters have full hex in the me....
while (me.length() > 0 &&
((me.front() >= '0' && me.front() <= '9') ||
(me.front() >= 'a' && me.front() <= 'f')))
{
me.erase(0,1);
count++;
}
}
else
{
// But compliant meters use only a bcd subset.
while (me.length() > 0 &&
(me.front() >= '0' && me.front() <= '9'))
{
me.erase(0,1);
count++;
}
}
bool wildcard_used = false;
// An expression can end with a *
if (me.length() > 0 && me.front() == '*')
{
me.erase(0,1);
wildcard_used = true;
}
// Now we should have eaten the whole expression.
if (me.length() > 0) return false;
// Check the length of the matching bcd/hex
// If no wildcard is used, then the match expression must be exactly 8 digits.
if (!wildcard_used) return count == 8;
// If wildcard is used, then the match expressions must be 7 or less digits,
// even zero is allowed which means a single *, which matches any bcd/hex id.
return count <= 7;
}
bool isValidMatchExpressions(string mes, bool non_compliant)
{
vector<string> v = splitMatchExpressions(mes);
for (string me : v)
{
if (!isValidMatchExpression(me, non_compliant)) return false;
}
return true;
}
bool doesIdMatchExpression(string id, string match)
{
if (id.length() == 0) return false;
// Here we assume that the match expression has been
// verified to be valid.
bool can_match = true;
// Now match bcd/hex until end of id, or '*' in match.
while (id.length() > 0 && match.length() > 0 && match.front() != '*')
{
if (id.front() != match.front())
{
// We hit a difference, it cannot match.
can_match = false;
break;
}
id.erase(0,1);
match.erase(0,1);
}
bool wildcard_used = false;
if (match.front() == '*')
{
wildcard_used = true;
match.erase(0,1);
}
if (can_match)
{
// Ok, now the match expression should be empty.
// If wildcard is true, then the id can still have digits,
// otherwise it must also be empty.
if (wildcard_used)
{
can_match = match.length() == 0;
}
else
{
can_match = match.length() == 0 && id.length() == 0;
}
}
return can_match;
}
bool doesIdMatchExpressions(string& id, vector<string>& mes)
{
bool found_match = false;
bool found_negative_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.
for (string me : mes)
{
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 (found_negative_match) return false;
if (found_match) return true;
return false;
}
bool isValidKey(string& key)
{
if (key.length() == 0) return true;
@ -406,11 +549,11 @@ bool isFrequency(std::string& fq)
return true;
}
vector<string> splitIds(string& ids)
vector<string> splitMatchExpressions(string& mes)
{
vector<string> r;
bool eof, err;
vector<uchar> v (ids.begin(), ids.end());
vector<uchar> v (mes.begin(), mes.end());
auto i = v.begin();
for (;;) {

Wyświetl plik

@ -70,11 +70,15 @@ bool isLogTelegramsEnabled();
void debugPayload(std::string intro, std::vector<uchar> &payload);
void logTelegram(std::string intro, std::vector<uchar> &header, std::vector<uchar> &content);
bool isValidId(std::string& id);
bool isValidMatchExpression(std::string id, bool non_compliant);
bool isValidMatchExpressions(std::string ids, bool non_compliant);
bool doesIdMatchExpression(std::string id, std::string match);
bool doesIdMatchExpressions(std::string& id, std::vector<std::string>& ids);
bool isValidKey(std::string& key);
bool isFrequency(std::string& fq);
std::vector<std::string> splitIds(std::string& id);
std::vector<std::string> splitMatchExpressions(std::string& mes);
void incrementIV(uchar *iv, size_t len);

Wyświetl plik

@ -26,10 +26,12 @@ else
fi
cat $SIM | grep '^{' | grep 8856 > $TEST/test_expected.txt
$PROG --format=json $SIM \
Element qcaloric '8856*' '' \
> $TEST/test_output.txt
if [ "$?" == "0" ]
then
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt