Add filter_out to address expression.

pull/868/merge
Fredrik Öhrström 2024-03-01 12:00:49 +01:00
rodzic 0c98b474bb
commit 78e7c47503
7 zmienionych plików z 307 dodań i 128 usunięć

Wyświetl plik

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

Wyświetl plik

@ -16,10 +16,13 @@
*/
#include"address.h"
#include"manufacturers.h"
#include<assert.h>
using namespace std;
bool isValidMatchExpression(const string& s)
bool isValidMatchExpression(const string& s, bool *has_wildcard)
{
string me = s;
@ -58,6 +61,7 @@ bool isValidMatchExpression(const string& s)
{
me.erase(0,1);
wildcard_used = true;
if (has_wildcard) *has_wildcard = true;
}
// Now we should have eaten the whole expression.
@ -78,7 +82,7 @@ bool isValidMatchExpressions(const string& mes)
for (string me : v)
{
if (!isValidMatchExpression(me)) return false;
if (!isValidMatchExpression(me, NULL)) return false;
}
return true;
}
@ -231,6 +235,74 @@ bool doesIdMatchExpressions(const string& id, vector<string>& mes, bool *used_wi
return false;
}
bool doesIdMatchAddressExpressions(const string& id, vector<AddressExpression>& aes, bool *used_wildcard)
{
/* bool found_match = false;
bool found_negative_match = false;
bool exact_match = false;*/
*used_wildcard = false;
// Goes through all possible match expressions.
// If no expression matches, neither positive nor negative,
// then the result is false. (ie no match)
// If more than one positive match is found, and no negative,
// then the result is true.
// If more than one negative match is found, irrespective
// if there is any positive matches or not, then the result is false.
// If a positive match is found, using a wildcard not any exact match,
// then *used_wildcard is set to true.
/*
for (AddressExpression &ae : aes)
{
bool has_wildcard = ae.has_wildcard;
bool is_negative_rule = (me.length() > 0 && me.front() == '!');
if (is_negative_rule)
{
me.erase(0, 1);
}
bool m = doesIdMatchExpression(id, me);
if (is_negative_rule)
{
if (m) found_negative_match = true;
}
else
{
if (m)
{
found_match = true;
if (!has_wildcard)
{
exact_match = true;
}
}
}
}
if (found_negative_match)
{
return false;
}
if (found_match)
{
if (exact_match)
{
*used_wildcard = false;
}
else
{
*used_wildcard = true;
}
return true;
}
*/
return false;
}
string toIdsCommaSeparated(vector<string> &ids)
{
string cs;
@ -242,3 +314,114 @@ string toIdsCommaSeparated(vector<string> &ids)
if (cs.length() > 0) cs.pop_back();
return cs;
}
bool AddressExpression::match(const std::string &i, uint16_t m, uchar v, uchar t)
{
if (!(mfct == 0xffff || mfct == m)) return false;
if (!(version == 0xff || version == v)) return false;
if (!(type == 0xff || type == t)) return false;
if (!doesIdMatchExpression(i, id)) return false;
return true;
}
bool AddressExpression::parse(const string &in)
{
string s = in;
// Example: 12345678
// or 12345678.M=PII.T=1B.V=01
// or 1234*
// or 1234*.M=PII
// or 1234*.V=01
// or 12 // mbus primary
// or 0 // mbus primary
// or 250.MPII.V01.T1B // mbus primary
// or !12345678
// or !*.M=ABC
id = "";
mbus_primary = false;
mfct = 0xffff;
type = 0xff;
version = 0xff;
filter_out = false;
if (s.size() == 0) return false;
if (s.size() > 1 && s[0] == '!')
{
filter_out = true;
s = s.substr(1);
}
vector<string> parts = splitString(s, '.');
assert(parts.size() > 0);
id = parts[0];
if (!isValidMatchExpression(id, &has_wildcard))
{
// Not a long id, so lets check if it is p0 to p250 for primary mbus ids.
if (id.size() < 2) return false;
if (id[0] != 'p') return false;
for (size_t i=1; i < id.length(); ++i)
{
if (!isdigit(id[i])) return false;
}
// All digits good.
int v = atoi(id.c_str()+1);
if (v < 0 || v > 250) return false;
// It is 0-250 which means it is an mbus primary address.
mbus_primary = true;
}
for (size_t i=1; i<parts.size(); ++i)
{
if (parts[i].size() == 4) // V=xy or T=xy
{
if (parts[i][1] != '=') return false;
vector<uchar> data;
bool ok = hex2bin(&parts[i][2], &data);
if (!ok) return false;
if (data.size() != 1) return false;
if (parts[i][0] == 'V')
{
version = data[0];
}
else if (parts[i][0] == 'T')
{
type = data[0];
}
else
{
return false;
}
}
else if (parts[i].size() == 5) // M=xyz
{
if (parts[i][1] != '=') return false;
if (parts[i][0] != 'M') return false;
bool ok = flagToManufacturer(&parts[i][2], &mfct);
if (!ok) return false;
}
else
{
return false;
}
}
return true;
}
bool flagToManufacturer(const char *s, uint16_t *out_mfct)
{
if (s[0] == 0 || s[1] == 0 || s[2] == 0 || s[3] != 0) return false;
if (s[0] < '@' || s[0] > 'Z' ||
s[1] < '@' || s[1] > 'Z' ||
s[2] < '@' || s[2] > 'Z') return false;
*out_mfct = MANFCODE(s[0],s[1],s[2]);
return true;
}

Wyświetl plik

@ -21,24 +21,35 @@
#include "util.h"
#include <string>
struct Address
struct AddressExpression
{
// An address expression is used to select which telegrams to decode for a driver.
// An address expression is also used to select a specific meter to poll for data.
// Example address: 12345678
// Or fully qualified: 12345678.M=PII.T=1b.V=01
// which means manufacturer triplet PII, type/media=0x1b, version=0x01
// Or wildcards in id: 12*.T=16
// which matches all cold water meters whose ids start with 12.
// Or negated tests: 12345678.V!=66
// which will decode all telegrams from 12345678 except those where the version is 0x66.
// Or every telegram which is does not start with 12 and is not from ABB:
// !12*.M!=ABB
std::string id; // 1 or 12345678 or non-compliant hex: 1234abcd
bool wildcard_used {}; // The id contains a *
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.
bool negate {}; // When used for testing this address was negated. !12345678
bool parse(std::string &s);
bool match(Address *a);
bool filter_out {}; // Telegrams matching this rule should be filtered out!
bool parse(const std::string &s);
bool match(const std::string &id, uint16_t mfct, uchar version, uchar type);
};
bool isValidMatchExpression(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,
@ -55,4 +66,6 @@ bool isValidId(const std::string& id);
std::vector<std::string> splitMatchExpressions(const std::string& mes);
bool flagToManufacturer(const char *s, uint16_t *out_mfct);
#endif

Wyświetl plik

@ -2588,87 +2588,6 @@ bool FieldInfo::extractString(Meter *m, Telegram *t, DVEntry *dve)
return found;
}
bool Address::parse(string &s)
{
// Example: 12345678
// or 12345678.M=PII.T=1B.V=01
// or 1234*
// or 1234*.M=PII
// or 1234*.V=01
// or 12 // mbus primary
// or 0 // mbus primary
// or 250.MPII.V01.T1B // mbus primary
// or !12345678
// or !*.M=ABC
id = "";
mbus_primary = false;
mfct = 0xffff;
type = 0xff;
version = 0xff;
negate = false;
if (s.size() == 0) return false;
vector<string> parts = splitString(s, '.');
assert(parts.size() > 0);
if (!isValidMatchExpression(parts[0]))
{
// Not a long id, so lets check if it is 0-250.
for (size_t i=0; i < parts[0].length(); ++i)
{
if (!isdigit(parts[0][i])) return false;
}
// All digits good.
int v = atoi(parts[0].c_str());
if (v < 0 || v > 250) return false;
// It is 0-250 which means it is an mbus primary address.
mbus_primary = true;
}
id = parts[0];
for (size_t i=1; i<parts.size(); ++i)
{
if (parts[i].size() == 4) // V=xy or T=xy
{
if (parts[i][1] != '=') return false;
vector<uchar> data;
bool ok = hex2bin(&parts[i][2], &data);
if (!ok) return false;
if (data.size() != 1) return false;
if (parts[i][0] == 'V')
{
version = data[0];
}
else if (parts[i][0] == 'T')
{
type = data[0];
}
else
{
return false;
}
}
else if (parts[i].size() == 5) // M=xyz
{
if (parts[i][1] != '=') return false;
if (parts[i][0] != 'M') return false;
bool ok = flagToManufacturer(&parts[i][2], &mfct);
if (!ok) return false;
}
else
{
return false;
}
}
return true;
}
bool checkIf(set<string> &fields, const char *s)
{
if (fields.count(s) > 0)

Wyświetl plik

@ -39,13 +39,13 @@ bool verbose_ = false;
#define LIST_OF_TESTS \
X(addresses) \
/*
X(dynamic_loading) \
X(crc) \
X(dvparser) \
X(devices) \
X(linkmodes) \
X(ids) \
X(addresses) \
X(kdf) \
X(periods) \
X(device_parsing) \
@ -77,6 +77,7 @@ bool verbose_ = false;
X(formulas_dventries) \
X(formulas_stringinterpolation) \
*/
#define X(t) void test_##t();
LIST_OF_TESTS
@ -498,11 +499,15 @@ void test_ids()
test_does_id_match_expression("78563413", "*,!00156327,!00048713", true, true);
}
void tst_address(string s, bool valid, string id,
string mfct, uchar version, uchar type,
bool mbus_primary, bool wildcard_used)
void tst_address(string s, bool valid,
string id, bool has_wildcard,
string mfct,
uchar version,
uchar type,
bool mbus_primary,
bool filter_out)
{
Address a;
AddressExpression a;
bool ok = a.parse(s);
if (ok != valid)
@ -515,47 +520,119 @@ void tst_address(string s, bool valid, string id,
{
string smfct = manufacturerFlag(a.mfct);
if (id != a.id ||
has_wildcard != a.has_wildcard ||
mfct != smfct ||
version != a.version ||
type != a.type ||
wildcard_used != a.wildcard_used ||
mbus_primary != a.mbus_primary ||
wildcard_used != a.wildcard_used)
filter_out != a.filter_out)
{
printf("Expected parse of address \"%s\" to return (id=%s mfct=%s version=%02x type=%02x mp=%d wu=%d)\n"
"but got (id=%s mfct=%s version=%02x type=%02x mp=%d wu=%d)\n",
printf("Expected parse of address \"%s\" to return\n"
"(id=%s haswild=%d mfct=%s version=%02x type=%02x mbus=%d negt=%d)\n"
"but got\n"
"(id=%s haswild=%d mfct=%s version=%02x type=%02x mbus=%d negt=%d)\n",
s.c_str(),
id.c_str(), mfct.c_str(), version, type, mbus_primary, wildcard_used,
a.id.c_str(), smfct.c_str(), a.version, a.type, a.mbus_primary, a.wildcard_used);
id.c_str(),
has_wildcard,
mfct.c_str(),
version,
type,
mbus_primary,
filter_out,
a.id.c_str(),
a.has_wildcard,
smfct.c_str(),
a.version,
a.type,
a.mbus_primary,
a.filter_out);
}
}
}
void tst_address_match(string expr, string id, uint16_t m, uchar v, uchar t, bool match, bool filter_out)
{
AddressExpression e;
bool ok = e.parse(expr);
assert(ok);
bool test = e.match(id, m, v, t);
if (test != match)
{
printf("Expected address %s %04x %02x %02x to %smatch expression %s\n",
id.c_str(),
m, v, t,
match?"":"not ",
expr.c_str());
}
if (match && e.filter_out != filter_out)
{
printf("Expected %s from match expression %s\n",
filter_out?"FILTERED OUT":"NOT filtered",
expr.c_str());
}
}
void test_addresses()
{
tst_address("12345678",
true,
"12345678", // id
false, // has wildcard
"___", // mfct
0xff, // type
0xff, // version
false, // mbus primary
false // wildcard used
false, // mbus primary found
false // negate test
);
tst_address("123k45678", false, "", "", 0xff, 0xff, false, false);
tst_address("1234", false, "", "", 0xff, 0xff, false, false);
tst_address("0", true, "0", "___", 0xff, 0xff, true,false);
tst_address("250", true, "250", "___", 0xff, 0xff, true, false);
tst_address("251", false, "", "", 0xff, 0xff, false, false);
tst_address("0.M=PII.V=01.T=1b", true, "0", "PII", 0x01, 0x1b, true, false);
tst_address("123.V=11.M=FOO.T=ff", true, "123", "FOO", 0x11, 0xff, true, false);
tst_address("123.M=FOO", true, "123", "FOO", 0xff, 0xff, true, false);
tst_address("123.M=FOO.V=33", true, "123", "FOO", 0x33, 0xff, true, false);
tst_address("123.T=33", true, "123", "___", 0xff, 0x33, true, false);
tst_address("1.V=33", true, "1", "___", 0x33, 0xff, true, false);
tst_address("16.M=BAR", true, "16", "BAR", 0xff, 0xff, true, false);
tst_address("123k45678", false, "", false, "", 0xff, 0xff, false, false);
tst_address("1234", false, "", false, "", 0xff, 0xff, false, false);
tst_address("p0", true, "p0", false, "___", 0xff, 0xff, true, false);
tst_address("p250", true, "p250", false, "___", 0xff, 0xff, true, false);
tst_address("p251", false, "", false, "", 0xff, 0xff, false, false);
tst_address("p0.M=PII.V=01.T=1b", true, "p0", false, "PII", 0x01, 0x1b, true, false);
tst_address("p123.V=11.M=FOO.T=ff", true, "p123", false, "FOO", 0x11, 0xff, true, false);
tst_address("p123.M=FOO", true, "p123", false, "FOO", 0xff, 0xff, true, false);
tst_address("p123.M=FOO.V=33", true, "p123", false, "FOO", 0x33, 0xff, true, false);
tst_address("p123.T=33", true, "p123", false, "___", 0xff, 0x33, true, false);
tst_address("p1.V=33", true, "p1", false, "___", 0x33, 0xff, true, false);
tst_address("p16.M=BAR", true, "p16", false, "BAR", 0xff, 0xff, true, false);
// tst_address("12*", true, "12*", "___", 0xff, 0xff, false, true);
tst_address("12345678.M=ABB.V=66.T=16", true, "12345678", false, "ABB", 0x66, 0x16, false, false);
tst_address("!12345678.M=ABB.V=66.T=16", true, "12345678", false, "ABB", 0x66, 0x16, false, true);
tst_address("!*.M=ABB", true, "*", true, "ABB", 0xff, 0xff, false, true);
tst_address("!*.V=66.T=06", true, "*", true, "___", 0x66, 0x06, false, true);
tst_address("12*", true, "12*", true, "___", 0xff, 0xff, false, false);
tst_address("!1234567*", true, "1234567*", true, "___", 0xff, 0xff, false, true);
tst_address_match("12345678", "12345678", 1, 1, 1, true, false);
tst_address_match("12345678.M=ABB.V=77", "12345678", MANUFACTURER_ABB, 0x77, 88, true, false);
tst_address_match("1*.V=77", "12345678", MANUFACTURER_ABB, 0x77, 1, true, false);
tst_address_match("12345678.M=ABB.V=67.T=06", "12345678", MANUFACTURER_ABB, 0x67, 0x06, true, false);
tst_address_match("12345678.M=ABB.V=67.T=06", "12345678", MANUFACTURER_ABB, 0x68, 0x06, false, false);
tst_address_match("12345678.M=ABB.V=67.T=06", "12345678", MANUFACTURER_ABB, 0x67, 0x07, false, false);
tst_address_match("12345678.M=ABB.V=67.T=06", "12345678", MANUFACTURER_ABB+1, 0x67, 0x06, false, false);
tst_address_match("12345678.M=ABB.V=67.T=06", "12345677", MANUFACTURER_ABB, 0x67, 0x06, false, false);
// Now verify filter out ! character. The filter out does notchange the test. It is still the same
// test, but the match will be used as a filter out. Ie if the match triggers, then the telegram will be filtered out.
tst_address_match("!12345678", "12345677", 1, 1, 1, false, false);
tst_address_match("!*.M=ABB", "99999999", MANUFACTURER_ABB, 1, 1, true, true);
tst_address_match("*.M=ABB", "99999999", MANUFACTURER_ABB, 1, 1, true, false);
// Test that both id wildcard matches and the version.
tst_address_match("9*.V=06", "99999999", MANUFACTURER_ABB, 0x06, 1, true, false);
tst_address_match("9*.V=06", "89999999", MANUFACTURER_ABB, 0x06, 1, false, false);
tst_address_match("9*.V=06", "99999999", MANUFACTURER_ABB, 0x07, 1, false, false);
tst_address_match("9*.V=06", "89999999", MANUFACTURER_ABB, 0x07, 1, false, false);
// Test the same, expect same answers but check that filtered out is set.
tst_address_match("!9*.V=06", "99999999", MANUFACTURER_ABB, 0x06, 1, true, true);
tst_address_match("!9*.V=06", "89999999", MANUFACTURER_ABB, 0x06, 1, false, true);
tst_address_match("!9*.V=06", "99999999", MANUFACTURER_ABB, 0x07, 1, false, true);
tst_address_match("!9*.V=06", "89999999", MANUFACTURER_ABB, 0x07, 1, false, true);
}
void eq(string a, string b, const char *tn)

Wyświetl plik

@ -465,17 +465,6 @@ string manufacturerFlag(int m_field) {
return flag;
}
bool flagToManufacturer(const char *s, uint16_t *out_mfct)
{
if (s[0] == 0 || s[1] == 0 || s[2] == 0 || s[3] != 0) return false;
if (s[0] < '@' || s[0] > 'Z' ||
s[1] < '@' || s[1] > 'Z' ||
s[2] < '@' || s[2] > 'Z') return false;
*out_mfct = MANFCODE(s[0],s[1],s[2]);
return true;
}
string mediaType(int a_field_device_type, int m_field) {
switch (a_field_device_type) {
case 0: return "Other";

Wyświetl plik

@ -742,7 +742,6 @@ shared_ptr<BusDevice> openSimulator(Detected detected,
string manufacturer(int m_field);
string manufacturerFlag(int m_field);
bool flagToManufacturer(const char *s, uint16_t *out_mfct);
string mediaType(int a_field_device_type, int m_field);
string mediaTypeJSON(int a_field_device_type, int m_field);
bool isCiFieldOfType(int ci_field, CI_TYPE type);