From 78e7c475030f393b1309fad20ee8f30141300f3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20=C3=96hrstr=C3=B6m?= Date: Fri, 1 Mar 2024 12:00:49 +0100 Subject: [PATCH] Add filter_out to address expression. --- Makefile | 1 - src/address.cc | 187 ++++++++++++++++++++++++++++++++++++++++++- src/address.h | 25 ++++-- src/meters.cc | 81 ------------------- src/testinternals.cc | 129 +++++++++++++++++++++++------ src/wmbus.cc | 11 --- src/wmbus.h | 1 - 7 files changed, 307 insertions(+), 128 deletions(-) diff --git a/Makefile b/Makefile index c109a31..2bda17e 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,3 @@ - # Copyright (C) 2017-2023 Fredrik Öhrström (gpl-3.0-or-later) # # This program is free software: you can redistribute it and/or modify diff --git a/src/address.cc b/src/address.cc index e2e6658..40de4ec 100644 --- a/src/address.cc +++ b/src/address.cc @@ -16,10 +16,13 @@ */ #include"address.h" +#include"manufacturers.h" + +#include 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& mes, bool *used_wi return false; } +bool doesIdMatchAddressExpressions(const string& id, vector& 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 &ids) { string cs; @@ -242,3 +314,114 @@ string toIdsCommaSeparated(vector &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 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 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; +} diff --git a/src/address.h b/src/address.h index 0964c5f..318256a 100644 --- a/src/address.h +++ b/src/address.h @@ -21,24 +21,35 @@ #include "util.h" #include -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 splitMatchExpressions(const std::string& mes); +bool flagToManufacturer(const char *s, uint16_t *out_mfct); + #endif diff --git a/src/meters.cc b/src/meters.cc index 15f6253..9008a73 100644 --- a/src/meters.cc +++ b/src/meters.cc @@ -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 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 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 &fields, const char *s) { if (fields.count(s) > 0) diff --git a/src/testinternals.cc b/src/testinternals.cc index a41ee28..8685e42 100644 --- a/src/testinternals.cc +++ b/src/testinternals.cc @@ -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) diff --git a/src/wmbus.cc b/src/wmbus.cc index b5529a9..d485705 100644 --- a/src/wmbus.cc +++ b/src/wmbus.cc @@ -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"; diff --git a/src/wmbus.h b/src/wmbus.h index 0fb0b40..a8db705 100644 --- a/src/wmbus.h +++ b/src/wmbus.h @@ -742,7 +742,6 @@ shared_ptr 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);