diff --git a/.gitignore b/.gitignore
index 56e5052..082375a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,5 +3,6 @@ build_debug
build_arm/
build_arm_debug/
archive/
+testaes/
testoutput/
-*~
\ No newline at end of file
+*~
diff --git a/Makefile b/Makefile
index 07c4116..6f6e96e 100644
--- a/Makefile
+++ b/Makefile
@@ -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 \
diff --git a/simulations/simulation_driver_detection.txt b/simulations/simulation_driver_detection.txt
index 5ca9aac..80df115 100644
--- a/simulations/simulation_driver_detection.txt
+++ b/simulations/simulation_driver_detection.txt
@@ -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|
diff --git a/simulations/simulation_izars.txt b/simulations/simulation_izars.txt
index 236d4ab..6437f47 100644
--- a/simulations/simulation_izars.txt
+++ b/simulations/simulation_izars.txt
@@ -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"}
diff --git a/src/main.cc b/src/main.cc
index b2301c1..553f375 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -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;
});
}
diff --git a/src/manufacturer_specificities.cc b/src/manufacturer_specificities.cc
new file mode 100644
index 0000000..cdf1260
--- /dev/null
+++ b/src/manufacturer_specificities.cc
@@ -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 .
+*/
+
+#include
+#include"manufacturers.h"
+#include"manufacturer_specificities.h"
+
+std::set 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& 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
+ }
+}
+
diff --git a/src/manufacturer_specificities.h b/src/manufacturer_specificities.h
new file mode 100644
index 0000000..58c4f84
--- /dev/null
+++ b/src/manufacturer_specificities.h
@@ -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 .
+*/
+
+#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& frame, DiehlAddressTransformMethod method);
+
+#endif
diff --git a/src/meter_izar.cc b/src/meter_izar.cc
index 9197b30..2798e78 100644
--- a/src/meter_izar.cc
+++ b/src/meter_izar.cc
@@ -63,7 +63,7 @@ private:
uint32_t convertKey(const char *hex);
uint32_t convertKey(const vector &bytes);
uint32_t uint32FromBytes(const vector &data, int offset, bool reverse = false);
- vector decodePrios(const vector &payload, uint32_t key);
+ vector decodePrios(const vector &origin, const vector &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 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 MeterIzar::decodePrios(const vector &frame, uint32_t key)
+vector MeterIzar::decodePrios(const vector &origin, const vector &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;
diff --git a/src/meters.cc b/src/meters.cc
index 2c99794..e1372b1 100644
--- a/src/meters.cc
+++ b/src/meters.cc
@@ -518,7 +518,7 @@ bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector &src, vector *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 &target) {
+std::string bin2hex(const vector &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 &payload, vector::iterator
}
}
-void logTelegram(vector &parsed, int header_size, int suffix_size)
+void logTelegram(vector &original, vector &parsed, int header_size, int suffix_size)
{
if (isLogTelegramsEnabled())
{
+ vector logged = parsed;
+ if (!original.empty())
+ {
+ logged = vector(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)
diff --git a/src/util.h b/src/util.h
index beacbcb..a21b114 100644
--- a/src/util.h
+++ b/src/util.h
@@ -43,7 +43,7 @@ uchar reverse(uchar c);
bool hex2bin(const char* src, std::vector *target);
bool hex2bin(std::string &src, std::vector *target);
bool hex2bin(std::vector &src, std::vector *target);
-std::string bin2hex(std::vector &target);
+std::string bin2hex(const std::vector &target);
std::string bin2hex(std::vector::iterator data, std::vector::iterator end, int len);
std::string safeString(std::vector &target);
void strprintf(std::string &s, const char* fmt, ...);
@@ -88,7 +88,7 @@ bool isLogTelegramsEnabled();
void debugPayload(std::string intro, std::vector &payload);
void debugPayload(std::string intro, std::vector &payload, std::vector::iterator &pos);
-void logTelegram(std::vector &parsed, int header_size, int suffix_size);
+void logTelegram(std::vector &original, std::vector &parsed, int header_size, int suffix_size);
enum class Alarm
{
diff --git a/src/wmbus.cc b/src/wmbus.cc
index f973525..64f1b72 100644
--- a/src/wmbus.cc
+++ b/src/wmbus.cc
@@ -23,6 +23,7 @@
#include"wmbus_common_implementation.h"
#include"wmbus_utils.h"
#include"dvparser.h"
+#include"manufacturer_specificities.h"
#include
#include
#include
@@ -1579,6 +1580,23 @@ bool Telegram::parseTPL(vector::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(frame.begin(), frame.begin() + 10);
+ transformDiehlAddress(frame, diehl_method);
+ }
+ }
+}
+
bool Telegram::parseHeader(vector &input_frame)
{
bool ok;
@@ -1592,6 +1610,8 @@ bool Telegram::parseHeader(vector &input_frame)
vector::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 &input_frame, MeterKeys *mk, bool warn)
vector::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. │
diff --git a/src/wmbus.h b/src/wmbus.h
index 5aa64cd..925287f 100644
--- a/src/wmbus.h
+++ b/src/wmbus.h
@@ -464,12 +464,18 @@ struct Telegram
string autoDetectPossibleDrivers();
+ // part of original telegram bytes, only filled if pre-processing modifies it
+ vector 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::iterator &pos);
bool parseELL(std::vector::iterator &pos);
bool parseNWL(std::vector::iterator &pos);
diff --git a/tests/test_izars.sh b/tests/test_izars.sh
index 115950d..1447693 100755
--- a/tests/test_izars.sh
+++ b/tests/test_izars.sh
@@ -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 <