kopia lustrzana https://github.com/weetmuts/wmbusmeters
Add frame preprocessing to transform Diehl addresses
rodzic
daab1b303e
commit
5d1343f121
|
@ -3,5 +3,6 @@ build_debug
|
|||
build_arm/
|
||||
build_arm_debug/
|
||||
archive/
|
||||
testaes/
|
||||
testoutput/
|
||||
*~
|
1
Makefile
1
Makefile
|
@ -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 \
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) \
|
||||
|
|
15
src/util.cc
15
src/util.cc
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
22
src/wmbus.cc
22
src/wmbus.cc
|
@ -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. │
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue