Add frame preprocessing to transform Diehl addresses

pull/241/head
Don-vip 2021-02-07 23:06:44 +01:00
rodzic daab1b303e
commit 5d1343f121
15 zmienionych plików z 225 dodań i 50 usunięć

3
.gitignore vendored
Wyświetl plik

@ -3,5 +3,6 @@ build_debug
build_arm/
build_arm_debug/
archive/
testaes/
testoutput/
*~
*~

Wyświetl plik

@ -150,7 +150,7 @@ METER_OBJS:=\
$(BUILD)/meter_multical302.o \
$(BUILD)/meter_multical403.o \
$(BUILD)/meter_multical603.o \
$(BUILD)/meter_multical803.o \
$(BUILD)/meter_multical803.o \
$(BUILD)/meter_omnipower.o \
$(BUILD)/meter_q400.o \
$(BUILD)/meter_qcaloric.o \
@ -167,6 +167,7 @@ METER_OBJS:=\
$(BUILD)/meter_whe5x.o \
$(BUILD)/meter_sensostar.o \
$(BUILD)/meter_gransystems_ccx01.o \
$(BUILD)/manufacturer_specificities.o \
$(BUILD)/printer.o \
$(BUILD)/rtlsdr.o \
$(BUILD)/serial.o \

Wyświetl plik

@ -2,5 +2,5 @@
# that should be properly recognized and mapped to an existing driver.
# I.e. check that a driver can be deduced from each telegram.
# izar water meter with weird identification combo: mfct: DME (0x11a5) type: A/D converter (0x19) ver: 0x00
# izar water meter with address swapping: mfct: DME (0x11a5) type: water meter (0x07) ver: 0x78
telegram=||1944A511780758280019A2610F001328ACC7F38E4D812CFC0D36|

Wyświetl plik

@ -1,8 +1,4 @@
# The design philosophy of the izar water meters, is never ever let
# a water meter report itself as a water meter....
# Test IZAR RC 868 I R4 PL water meter telegram, it reports itself as an oil meter... duh?
# But wmbusmeters politely translates this into water.
# Test IZAR RC 868 I R4 PL water meter telegram
telegram=|1944304C72242421D401A2|013D4013DD8B46A4999C1293E582CC|
{"media":"water","meter":"izar","name":"IzarWater","id":"21242472","total_m3":3.488,"last_month_total_m3":3.486,"last_month_measure_date":"2019-09-30","remaining_battery_life_y":14.5,"current_alarms":"meter_blocked,underflow","previous_alarms":"no_alarm","timestamp":"1111-11-11T11:11:11Z"}
@ -10,14 +6,14 @@ telegram=|1944304C72242421D401A2|013D4013DD8B46A4999C1293E582CC|
# Test new version of IZAR
telegram=|2944A511780729662366A20118001378D3B3DB8CEDD77731F25832AAF3DA8CADF9774EA673172E8C61F2|
{"media":"water","meter":"izar","name":"IzarWater2","id":"66290778","total_m3":16.76,"last_month_total_m3":11.84,"last_month_measure_date":"2019-11-30","remaining_battery_life_y":12,"current_alarms":"no_alarm","previous_alarms":"no_alarm","timestamp":"1111-11-11T11:11:11Z"}
{"media":"water","meter":"izar","name":"IzarWater2","id":"23662907","total_m3":16.76,"last_month_total_m3":11.84,"last_month_measure_date":"2019-11-30","remaining_battery_life_y":12,"current_alarms":"no_alarm","previous_alarms":"no_alarm","timestamp":"1111-11-11T11:11:11Z"}
# Yet another silly version of IZAR, come again, type 0x20 = Breaker (electricity) for a water meter!
# Yet another version of IZAR
telegram=|1944A511780779194820A1|21170013355F8EDB2D03C6912B1E37
{"media":"water","meter":"izar","name":"IzarWater3","id":"19790778","total_m3":4.366,"last_month_total_m3":0,"last_month_measure_date":"2020-12-31","remaining_battery_life_y":11.5,"current_alarms":"no_alarm","previous_alarms":"no_alarm","timestamp":"1111-11-11T11:11:11Z"}
{"media":"water","meter":"izar","name":"IzarWater3","id":"48197907","total_m3":4.366,"last_month_total_m3":0,"last_month_measure_date":"2020-12-31","remaining_battery_life_y":11.5,"current_alarms":"no_alarm","previous_alarms":"no_alarm","timestamp":"1111-11-11T11:11:11Z"}
# And another izar sillines, this time its a "heat meter" and a mfct specific tpl ci field a3.
# And another izar, with a mfct specific tpl ci field a3.
telegram=|1944304c9c5824210c04a363140013716577ec59e8663ab0d31c|
{"media":"water","meter":"izar","name":"IzarWater4","id":"2124589c","total_m3":38.944,"last_month_total_m3":38.691,"last_month_measure_date":"2021-02-01","remaining_battery_life_y":10,"current_alarms":"no_alarm","previous_alarms":"no_alarm","timestamp":"1111-11-11T11:11:11Z"}

Wyświetl plik

@ -1249,7 +1249,7 @@ bool start(Configuration *config)
t.parse(frame, &mk, false); // Try a best effort parse, do not print any warnings.
t.print();
t.explainParse("(wmbus)",0);
logTelegram(t.frame, 0, 0);
logTelegram(t.original, t.frame, 0, 0);
return true;
});
}

Wyświetl plik

@ -0,0 +1,116 @@
/*
Copyright (C) 2021 Vincent Privat
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include<set>
#include"manufacturers.h"
#include"manufacturer_specificities.h"
std::set<int> diehl_manufacturers = {
MANUFACTURER_DME,
MANUFACTURER_EWT,
MANUFACTURER_HYD,
MANUFACTURER_SAP,
MANUFACTURER_SPL
};
// Diehl: is "A field" coded as version/type/serialnumber instead of standard serialnumber/version/type?
DiehlAddressTransformMethod mustTransformDiehlAddress(uchar c_field, int m_field, uchar ci_field, int tpl_cfg)
{
if (diehl_manufacturers.find(m_field) != diehl_manufacturers.end())
{
if (c_field == 0x44 || c_field == 0x46) // SND_NR / SND_IR
{
switch (ci_field) {
case 0x71: // Alarm
return DiehlAddressTransformMethod::SWAPPING; // Real Data
case 0x7A: // EN 13757-3 Application Layer (short tplh)
if (((tpl_cfg >> 8) & 0x10) == 0x10)
{
return DiehlAddressTransformMethod::SWAPPING; // Real Data
}
break; // OMS
case 0xA0: // Manufacturer specific
case 0xA1: // Manufacturer specific
case 0xA2: // Manufacturer specific
case 0xA3: // Manufacturer specific
case 0xA4: // Manufacturer specific
case 0xA5: // Manufacturer specific
case 0xA6: // Manufacturer specific
case 0xA7: // Manufacturer specific
if (m_field == MANUFACTURER_SAP)
{
return DiehlAddressTransformMethod::SAP_PRIOS; // SAP PRIOS
}
return DiehlAddressTransformMethod::SWAPPING; // PRIOS
case 0xB0: // Manufacturer specific
if (m_field == MANUFACTURER_SAP)
{
return DiehlAddressTransformMethod::SAP_PRIOS_STANDARD; // SAP PRIOS STD
}
break; // Reserved
case 0xA8: // Manufacturer specific
case 0xA9: // Manufacturer specific
case 0xAA: // Manufacturer specific
case 0xAB: // Manufacturer specific
case 0xAC: // Manufacturer specific
case 0xAD: // Manufacturer specific
case 0xAE: // Manufacturer specific
case 0xAF: // Manufacturer specific
case 0xB4: // Manufacturer specific
case 0xB5: // Manufacturer specific
case 0xB6: // Manufacturer specific
case 0xB7: // Manufacturer specific
break; // Reserved
case 0xB1: // Manufacturer specific
case 0xB2: // Manufacturer specific
case 0xB3: // Manufacturer specific
return DiehlAddressTransformMethod::SWAPPING; // PRIOS SCR
default:
break; // OMS
}
}
}
return DiehlAddressTransformMethod::NONE;
}
// Diehl: transform "A field" to make it compliant to standard
void transformDiehlAddress(std::vector<uchar>& frame, DiehlAddressTransformMethod transform_method)
{
if (transform_method == DiehlAddressTransformMethod::SWAPPING)
{
debug("(diehl) Pre-processing: swapping address field\n");
uchar version = frame[4];
uchar type = frame[5];
for (int i = 4; i < 8; i++)
{
frame[i] = frame[i+1];
}
frame[8] = version;
frame[9] = type;
}
else if (transform_method == DiehlAddressTransformMethod::SAP_PRIOS)
{
debug("(diehl) Pre-processing: setting device type to water meter for SAP PRIOS\n");
frame[8] = 0x00; // version field is used by IZAR as part of meter id on 5 bytes instead of 4
frame[9] = 0x07; // water meter
}
else if (transform_method == DiehlAddressTransformMethod::SAP_PRIOS_STANDARD)
{
warning("(diehl) Pre-processing: SAP PRIOS STANDARD transformation not implemented!\n"); // TODO
}
}

Wyświetl plik

@ -0,0 +1,36 @@
/*
Copyright (C) 2021 Vincent Privat
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MANUFACTURER_SPECIFICITIES_H
#define MANUFACTURER_SPECIFICITIES_H
#include"util.h"
enum class DiehlAddressTransformMethod {
NONE,
SWAPPING,
SAP_PRIOS,
SAP_PRIOS_STANDARD
};
// Diehl: Is "A field" coded as version/type/serialnumber instead of standard serialnumber/version/type?
DiehlAddressTransformMethod mustTransformDiehlAddress(uchar c_field, int m_field, uchar ci_field, int tpl_cfg);
// Diehl: swap "A field" to make it compliant to standard
void transformDiehlAddress(std::vector<uchar>& frame, DiehlAddressTransformMethod method);
#endif

Wyświetl plik

@ -63,7 +63,7 @@ private:
uint32_t convertKey(const char *hex);
uint32_t convertKey(const vector<uchar> &bytes);
uint32_t uint32FromBytes(const vector<uchar> &data, int offset, bool reverse = false);
vector<uchar> decodePrios(const vector<uchar> &payload, uint32_t key);
vector<uchar> decodePrios(const vector<uchar> &origin, const vector<uchar> &payload, uint32_t key);
double remaining_battery_life;
uint16_t h0_year;
@ -95,14 +95,8 @@ MeterIzar::MeterIzar(MeterInfo &mi) :
keys.push_back(convertKey(PRIOS_DEFAULT_KEY2));
}
// media 0x01 Oil meter? why?
// media 0x15 Hot water
// medua 0x66 Woot?
addLinkMode(LinkMode::T1);
// Meters with different versions exist, don't set any to avoid warnings
addPrint("total", Quantity::Volume,
[&](Unit u){ return totalWaterConsumption(u); },
"The total water consumption recorded by this meter.",
@ -251,7 +245,7 @@ void MeterIzar::processContent(Telegram *t)
vector<uchar> decoded_content;
for (auto& key : keys) {
decoded_content = decodePrios(frame, key);
decoded_content = decodePrios(t->original.empty() ? frame : t->original, frame, key);
if (!decoded_content.empty())
break;
}
@ -293,16 +287,13 @@ void MeterIzar::processContent(Telegram *t)
alarms.sensor_fraud_previously = frame[13] >> 2 & 0x1;
alarms.mechanical_fraud_currently = frame[13] >> 1 & 0x1;
alarms.mechanical_fraud_previously = frame[13] & 0x1;
// override incorrectly reported medium (oil)
t->dll_type = 7;
}
vector<uchar> MeterIzar::decodePrios(const vector<uchar> &frame, uint32_t key)
vector<uchar> MeterIzar::decodePrios(const vector<uchar> &origin, const vector<uchar> &frame, uint32_t key)
{
// modify seed key with header values
key ^= uint32FromBytes(frame, 2); // manufacturer + address[0-1]
key ^= uint32FromBytes(frame, 6); // address[2-3] + version + type
key ^= uint32FromBytes(origin, 2); // manufacturer + address[0-1]
key ^= uint32FromBytes(origin, 6); // address[2-3] + version + type
key ^= uint32FromBytes(frame, 10); // ci + some more bytes from the telegram...
int size = frame.size() - 15;

Wyświetl plik

@ -518,7 +518,7 @@ bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<ucha
char log_prefix[256];
snprintf(log_prefix, 255, "(%s) log", meterName().c_str());
logTelegram(t.frame, t.header_size, t.suffix_size);
logTelegram(t.original, t.frame, t.header_size, t.suffix_size);
// Invoke meter specific parsing!
processContent(&t);

Wyświetl plik

@ -118,13 +118,10 @@
X(IPERL, MANUFACTURER_SEN, 0x06, 0x68) \
X(IPERL, MANUFACTURER_SEN, 0x07, 0x68) \
X(IPERL, MANUFACTURER_SEN, 0x07, 0x7c) \
X(IZAR, MANUFACTURER_SAP, 0x01, -1) \
X(IZAR, MANUFACTURER_SAP, 0x15, -1) \
X(IZAR, MANUFACTURER_SAP, 0x66, -1) \
X(IZAR, MANUFACTURER_SAP, 0x04, -1) \
X(IZAR, MANUFACTURER_DME, 0x66, -1) \
X(IZAR, MANUFACTURER_DME, 0x19, 0x00) \
X(IZAR, MANUFACTURER_DME, 0x20, 0x48) \
X(IZAR, MANUFACTURER_SAP, 0x07, 0x00) \
X(IZAR, MANUFACTURER_DME, 0x07, 0x78) \
X(IZAR3, MANUFACTURER_SAP, 0x00, 0x88) \
X(LANSENSM, MANUFACTURER_LAS, 0x1a, 0x03) \
X(LANSENTH, MANUFACTURER_LAS, 0x1b, 0x07) \

Wyświetl plik

@ -193,7 +193,7 @@ bool hex2bin(vector<uchar> &src, vector<uchar> *target)
char const hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A','B','C','D','E','F'};
std::string bin2hex(vector<uchar> &target) {
std::string bin2hex(const vector<uchar> &target) {
std::string str;
for (size_t i = 0; i < target.size(); ++i) {
const char ch = target[i];
@ -863,12 +863,21 @@ void debugPayload(string intro, vector<uchar> &payload, vector<uchar>::iterator
}
}
void logTelegram(vector<uchar> &parsed, int header_size, int suffix_size)
void logTelegram(vector<uchar> &original, vector<uchar> &parsed, int header_size, int suffix_size)
{
if (isLogTelegramsEnabled())
{
vector<uchar> logged = parsed;
if (!original.empty())
{
logged = vector<uchar>(parsed);
for (unsigned int i = 0; i < original.size(); i++)
{
logged[i] = original[i];
}
}
time_t diff = time(NULL)-telegrams_start_time_;
string parsed_hex = bin2hex(parsed);
string parsed_hex = bin2hex(logged);
string header = parsed_hex.substr(0, header_size*2);
string content = parsed_hex.substr(header_size*2);
if (suffix_size == 0)

Wyświetl plik

@ -43,7 +43,7 @@ uchar reverse(uchar c);
bool hex2bin(const char* src, std::vector<uchar> *target);
bool hex2bin(std::string &src, std::vector<uchar> *target);
bool hex2bin(std::vector<uchar> &src, std::vector<uchar> *target);
std::string bin2hex(std::vector<uchar> &target);
std::string bin2hex(const std::vector<uchar> &target);
std::string bin2hex(std::vector<uchar>::iterator data, std::vector<uchar>::iterator end, int len);
std::string safeString(std::vector<uchar> &target);
void strprintf(std::string &s, const char* fmt, ...);
@ -88,7 +88,7 @@ bool isLogTelegramsEnabled();
void debugPayload(std::string intro, std::vector<uchar> &payload);
void debugPayload(std::string intro, std::vector<uchar> &payload, std::vector<uchar>::iterator &pos);
void logTelegram(std::vector<uchar> &parsed, int header_size, int suffix_size);
void logTelegram(std::vector<uchar> &original, std::vector<uchar> &parsed, int header_size, int suffix_size);
enum class Alarm
{

Wyświetl plik

@ -23,6 +23,7 @@
#include"wmbus_common_implementation.h"
#include"wmbus_utils.h"
#include"dvparser.h"
#include"manufacturer_specificities.h"
#include<assert.h>
#include<semaphore.h>
#include<stdarg.h>
@ -1579,6 +1580,23 @@ bool Telegram::parseTPL(vector<uchar>::iterator &pos)
return false;
}
void Telegram::preProcess()
{
if (frame.size() >= 15)
{
uchar c_field = frame[1];
int m_field = frame[3] <<8 | frame[2];
uchar ci_field = frame[10];
int tpl_cfg = frame[14] <<8 | frame[13];
DiehlAddressTransformMethod diehl_method = mustTransformDiehlAddress(c_field, m_field, ci_field, tpl_cfg);
if (diehl_method != DiehlAddressTransformMethod::NONE)
{
original = vector<uchar>(frame.begin(), frame.begin() + 10);
transformDiehlAddress(frame, diehl_method);
}
}
}
bool Telegram::parseHeader(vector<uchar> &input_frame)
{
bool ok;
@ -1592,6 +1610,8 @@ bool Telegram::parseHeader(vector<uchar> &input_frame)
vector<uchar>::iterator pos = frame.begin();
// Parsed accumulates parsed bytes.
parsed.clear();
// Fixes quirks from non-compliant meters to make telegram compatible with the standard
preProcess();
ok = parseDLL(pos);
if (!ok) return false;
@ -1626,6 +1646,8 @@ bool Telegram::parse(vector<uchar> &input_frame, MeterKeys *mk, bool warn)
vector<uchar>::iterator pos = frame.begin();
// Parsed accumulates parsed bytes.
parsed.clear();
// Fixes quirks from non-compliant meters to make telegram compatible with the standard
preProcess();
// ┌──────────────────────────────────────────────┐
// │ │
// │ Parse DLL Data Link Layer for Wireless MBUS. │

Wyświetl plik

@ -464,12 +464,18 @@ struct Telegram
string autoDetectPossibleDrivers();
// part of original telegram bytes, only filled if pre-processing modifies it
vector<uchar> original;
private:
bool is_simulated_ {};
bool parser_warns_ = true;
MeterKeys *meter_keys {};
// Fixes quirks from non-compliant meters to make telegram compatible with the standard
void preProcess();
bool parseDLL(std::vector<uchar>::iterator &pos);
bool parseELL(std::vector<uchar>::iterator &pos);
bool parseNWL(std::vector<uchar>::iterator &pos);

Wyświetl plik

@ -14,8 +14,8 @@ mkdir -p $TEST
rm -f $LOGFILE
METERS="IzarWater izar 21242472 NOKEY
IzarWater2 izar 66290778 NOKEY
IzarWater3 izar 19790778 NOKEY
IzarWater2 izar 23662907 NOKEY
IzarWater3 izar 48197907 NOKEY
IzarWater4 izar 2124589c NOKEY"
cat simulations/simulation_izars.txt | grep '^{' > $TEST/test_expected.txt
@ -50,23 +50,23 @@ cat > $LOGFILE_EXPECTED <<EOF
No meters configured. Printing id:s of all telegrams heard!
Received telegram from: 21242472
manufacturer: (SAP) Sappel (0x4c30)
type: Oil meter (0x01)
ver: 0xd4
type: Water meter (0x07)
ver: 0x00
driver: izar
Received telegram from: 66290778
Received telegram from: 23662907
manufacturer: (DME) DIEHL Metering, Germany (0x11a5)
type: Unknown (0x66)
ver: 0x23
type: Water meter (0x07)
ver: 0x78
driver: izar
Received telegram from: 19790778
Received telegram from: 48197907
manufacturer: (DME) DIEHL Metering, Germany (0x11a5)
type: Breaker (electricity) (0x20)
ver: 0x48
type: Water meter (0x07)
ver: 0x78
driver: izar
Received telegram from: 2124589c
manufacturer: (SAP) Sappel (0x4c30)
type: Heat meter (0x04)
ver: 0x0c
type: Water meter (0x07)
ver: 0x00
driver: izar
EOF