Added mbus driver ultraheat.

pull/451/head
Fredrik Öhrström 2022-01-13 09:51:08 +01:00
rodzic c20a63e5b7
commit b322838b59
14 zmienionych plików z 404 dodań i 84 usunięć

Wyświetl plik

@ -135,8 +135,12 @@ PROG_OBJS:=\
$(BUILD)/wmbus_rc1180.o \ $(BUILD)/wmbus_rc1180.o \
$(BUILD)/wmbus_utils.o \ $(BUILD)/wmbus_utils.o \
ifeq ($(DRIVER),)
DRIVER_OBJS:=$(wildcard src/meter_*.cc) DRIVER_OBJS:=$(wildcard src/meter_*.cc)
else
$(info Building a single driver $(DRIVER))
DRIVER_OBJS:=src/meter_auto.cc src/meter_unknown.cc src/meter_$(DRIVER).cc
endif
DRIVER_OBJS:=$(patsubst src/%.cc,$(BUILD)/%.o,$(DRIVER_OBJS)) DRIVER_OBJS:=$(patsubst src/%.cc,$(BUILD)/%.o,$(DRIVER_OBJS))
all: $(BUILD)/wmbusmeters $(BUILD)/wmbusmetersd $(BUILD)/wmbusmeters.g $(BUILD)/wmbusmeters-admin $(BUILD)/testinternals all: $(BUILD)/wmbusmeters $(BUILD)/wmbusmetersd $(BUILD)/wmbusmeters.g $(BUILD)/wmbusmeters-admin $(BUILD)/testinternals

Wyświetl plik

@ -92,7 +92,7 @@ telegram=|27442D2C5768663230028D20E900C91C2011BA79138CCCFB|1A0300000000000003000
{"media":"electricity","meter":"omnipower","name":"myomnipower","id":"32666857","total_energy_consumption_kwh":7.94,"total_energy_production_kwh":0,"current_power_consumption_kw":0.003,"current_power_production_kw":0,"timestamp":"1111-11-11T11:11:11Z"} {"media":"electricity","meter":"omnipower","name":"myomnipower","id":"32666857","total_energy_consumption_kwh":7.94,"total_energy_production_kwh":0,"current_power_consumption_kw":0.003,"current_power_production_kw":0,"timestamp":"1111-11-11T11:11:11Z"}
# Test EI Electronics smoke detector # Test EI Electronics smoke detector
telegram=|5E462515112801000C1A7A370050252F2F|0BFD0F060101046D300CAB2202FD17000082206CAB22426C01018440FF2C000F11008250FD61000082506C01018260FD6100008360FD3100000082606C01018270FD61010082706CAB222F2F2F2F| telegram=|5E442515112801000C1A7A370050252F2F#0BFD0F060101046D300CAB2202FD17000082206CAB22426C01018440FF2C000F11008250FD61000082506C01018260FD6100008360FD3100000082606C01018270FD61010082706CAB222F2F2F2F|
{"media":"smoke detector","meter":"ei6500","name":"Smokey","id":"00012811","software_version":"1.1.6","message_datetime":"2021-02-11 12:48","last_alarm_date":"2000-01-01","smoke_alarm_counter":"0","total_remove_duration":"0 minutes","last_remove_date":"2000-01-01","removed_counter":"0","test_button_last_date":"2021-02-11","test_button_counter":"1","status":"NOT_INSTALLED","timestamp":"1111-11-11T11:11:11Z"} {"media":"smoke detector","meter":"ei6500","name":"Smokey","id":"00012811","software_version":"1.1.6","message_datetime":"2021-02-11 12:48","last_alarm_date":"2000-01-01","smoke_alarm_counter":"0","total_remove_duration":"0 minutes","last_remove_date":"2000-01-01","removed_counter":"0","test_button_last_date":"2021-02-11","test_button_counter":"1","status":"NOT_INSTALLED","timestamp":"1111-11-11T11:11:11Z"}
# Test Techem radio convert + Wehrle water meter combo. # Test Techem radio convert + Wehrle water meter combo.

Wyświetl plik

@ -34,7 +34,21 @@ const char *toString(ValueInformation v)
switch (v) { switch (v) {
case ValueInformation::None: return "None"; case ValueInformation::None: return "None";
case ValueInformation::Any: return "Any"; case ValueInformation::Any: return "Any";
#define X(name,from,to,quantity) case ValueInformation::name: return #name; #define X(name,from,to,quantity,unit) case ValueInformation::name: return #name;
LIST_OF_VALUETYPES
#undef X
}
assert(0);
}
Unit toDefaultUnit(ValueInformation v)
{
switch (v) {
case ValueInformation::Any:
case ValueInformation::None:
assert(0);
break;
#define X(name,from,to,quantity,unit) case ValueInformation::name: return unit;
LIST_OF_VALUETYPES LIST_OF_VALUETYPES
#undef X #undef X
} }
@ -43,7 +57,7 @@ LIST_OF_VALUETYPES
ValueInformation toValueInformation(int i) ValueInformation toValueInformation(int i)
{ {
#define X(name,from,to,quantity) if (from <= i && i <= to) return ValueInformation::name; #define X(name,from,to,quantity,unit) if (from <= i && i <= to) return ValueInformation::name;
LIST_OF_VALUETYPES LIST_OF_VALUETYPES
#undef X #undef X
return ValueInformation::None; return ValueInformation::None;
@ -321,7 +335,7 @@ void valueInfoRange(ValueInformation v, int *low, int *hi)
switch (v) { switch (v) {
case ValueInformation::Any: case ValueInformation::Any:
case ValueInformation::None: *low = 0; *hi = 0; return; case ValueInformation::None: *low = 0; *hi = 0; return;
#define X(name,from,to,quantity) case ValueInformation::name: *low = from; *hi = to; return; #define X(name,from,to,quantity,unit) case ValueInformation::name: *low = from; *hi = to; return;
LIST_OF_VALUETYPES LIST_OF_VALUETYPES
#undef X #undef X
} }

Wyświetl plik

@ -19,6 +19,7 @@
#define DVPARSER_H #define DVPARSER_H
#include"util.h" #include"util.h"
#include"units.h"
#include"wmbus.h" #include"wmbus.h"
#include<map> #include<map>
@ -28,31 +29,32 @@
#include<vector> #include<vector>
#define LIST_OF_VALUETYPES \ #define LIST_OF_VALUETYPES \
X(Volume,0x10,0x17,Quantity::Volume) \ X(Volume,0x10,0x17,Quantity::Volume,Unit::M3) \
X(OperatingTime,0x24,0x27, Quantity::Time) \ X(OperatingTime,0x24,0x27, Quantity::Time, Unit::Second) \
X(VolumeFlow,0x38,0x3F, Quantity::Flow) \ X(VolumeFlow,0x38,0x3F, Quantity::Flow, Unit::M3H) \
X(FlowTemperature,0x58,0x5B, Quantity::Temperature) \ X(FlowTemperature,0x58,0x5B, Quantity::Temperature, Unit::C) \
X(ReturnTemperature,0x5C,0x5F, Quantity::Temperature) \ X(ReturnTemperature,0x5C,0x5F, Quantity::Temperature, Unit::C) \
X(TemperatureDifference,0x60,0x63, Quantity::Temperature) \ X(TemperatureDifference,0x60,0x63, Quantity::Temperature, Unit::C) \
X(ExternalTemperature,0x64,0x67, Quantity::Temperature) \ X(ExternalTemperature,0x64,0x67, Quantity::Temperature, Unit::C) \
X(HeatCostAllocation,0x6E,0x6E, Quantity::HCA) \ X(HeatCostAllocation,0x6E,0x6E, Quantity::HCA, Unit::HCA) \
X(Date,0x6C,0x6C, Quantity::PointInTime) \ X(Date,0x6C,0x6C, Quantity::PointInTime, Unit::DateTimeLT) \
X(DateTime,0x6D,0x6D, Quantity::PointInTime) \ X(DateTime,0x6D,0x6D, Quantity::PointInTime, Unit::DateTimeLT) \
X(EnergyMJ,0x0E,0x0F, Quantity::Energy) \ X(EnergyMJ,0x0E,0x0F, Quantity::Energy, Unit::MJ) \
X(EnergyWh,0x00,0x07, Quantity::Energy) \ X(EnergyWh,0x00,0x07, Quantity::Energy, Unit::KWH) \
X(PowerW,0x28,0x2f, Quantity::Power) \ X(PowerW,0x28,0x2f, Quantity::Power, Unit::KW) \
X(ActualityDuration,0x74,0x77, Quantity::Time) \ X(ActualityDuration,0x74,0x77, Quantity::Time, Unit::Second) \
enum class ValueInformation enum class ValueInformation
{ {
None, None,
Any, Any,
#define X(name,from,to,quantity) name, #define X(name,from,to,quantity,unit) name,
LIST_OF_VALUETYPES LIST_OF_VALUETYPES
#undef X #undef X
}; };
const char *toString(ValueInformation v); const char *toString(ValueInformation v);
Unit toDefaultUnit(ValueInformation v);
ValueInformation toValueInformation(int i); ValueInformation toValueInformation(int i);
struct DifVifKey struct DifVifKey

Wyświetl plik

@ -120,7 +120,7 @@ void MBusRawTTY::processSerialData()
for (;;) for (;;)
{ {
FrameStatus status = checkMBusFrame(read_buffer_, &frame_length, &payload_len, &payload_offset); FrameStatus status = checkMBusFrame(read_buffer_, &frame_length, &payload_len, &payload_offset, false);
if (status == PartialFrame) if (status == PartialFrame)
{ {

Wyświetl plik

@ -90,6 +90,7 @@
X(FLOWIQ3100,MANUFACTURER_KAM, 0x16, 0x1d) \ X(FLOWIQ3100,MANUFACTURER_KAM, 0x16, 0x1d) \
X(MULTICAL302,MANUFACTURER_KAM, 0x04, 0x30) \ X(MULTICAL302,MANUFACTURER_KAM, 0x04, 0x30) \
X(MULTICAL302,MANUFACTURER_KAM, 0x0d, 0x30) \ X(MULTICAL302,MANUFACTURER_KAM, 0x0d, 0x30) \
X(MULTICAL302,MANUFACTURER_KAM, 0x0c, 0x30) \
X(MULTICAL403,MANUFACTURER_KAM, 0x0a, 0x34) \ X(MULTICAL403,MANUFACTURER_KAM, 0x0a, 0x34) \
X(MULTICAL403,MANUFACTURER_KAM, 0x0b, 0x34) \ X(MULTICAL403,MANUFACTURER_KAM, 0x0b, 0x34) \
X(MULTICAL403,MANUFACTURER_KAM, 0x0c, 0x34) \ X(MULTICAL403,MANUFACTURER_KAM, 0x0c, 0x34) \

Wyświetl plik

@ -0,0 +1,146 @@
/*
Copyright (C) 2022 Fredrik Öhrström
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"dvparser.h"
#include"meters.h"
#include"meters_common_implementation.h"
#include"wmbus.h"
#include"wmbus_utils.h"
#include"util.h"
using namespace std;
struct MeterUltraHeat : public virtual MeterCommonImplementation
{
MeterUltraHeat(MeterInfo &mi, DriverInfo &di);
private:
double heat_mj_ {};
double volume_m3_ {};
double power_kw_ {};
double flow_m3h_ {};
double flow_c_ {};
double return_c_ {};
};
static bool ok = registerDriver([](DriverInfo&di)
{
di.setName("ultraheat");
di.setMeterType(MeterType::HeatMeter);
di.addDetection(MANUFACTURER_LUG, 0x04, 0x04);
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new MeterUltraHeat(mi, di)); });
});
MeterUltraHeat::MeterUltraHeat(MeterInfo &mi, DriverInfo &di) :
MeterCommonImplementation(mi, di)
{
addFieldWithExtractor(
"heat",
Quantity::Energy,
NoDifVifKey,
VifScaling::Auto,
MeasurementType::Instantaneous,
ValueInformation::EnergyMJ,
StorageNr(0),
TariffNr(0),
IndexNr(1),
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
"The total heat energy consumption recorded by this meter.",
SET_FUNC(heat_mj_, Unit::MJ),
GET_FUNC(heat_mj_, Unit::MJ));
addFieldWithExtractor(
"volume",
Quantity::Volume,
NoDifVifKey,
VifScaling::Auto,
MeasurementType::Instantaneous,
ValueInformation::Volume,
StorageNr(0),
TariffNr(0),
IndexNr(1),
PrintProperty::JSON ,
"The total heating media volume recorded by this meter.",
SET_FUNC(volume_m3_, Unit::M3),
GET_FUNC(volume_m3_, Unit::M3));
addFieldWithExtractor(
"power",
Quantity::Power,
NoDifVifKey,
VifScaling::Auto,
MeasurementType::Instantaneous,
ValueInformation::PowerW,
StorageNr(0),
TariffNr(0),
IndexNr(1),
PrintProperty::JSON ,
"The current power consumption.",
SET_FUNC(power_kw_, Unit::KW),
GET_FUNC(power_kw_, Unit::KW));
addFieldWithExtractor(
"flow",
Quantity::Flow,
NoDifVifKey,
VifScaling::Auto,
MeasurementType::Instantaneous,
ValueInformation::VolumeFlow,
StorageNr(0),
TariffNr(0),
IndexNr(1),
PrintProperty::JSON ,
"The current heat media volume flow.",
SET_FUNC(flow_m3h_, Unit::M3H),
GET_FUNC(flow_m3h_, Unit::M3H));
addFieldWithExtractor(
"flow",
Quantity::Temperature,
NoDifVifKey,
VifScaling::Auto,
MeasurementType::Instantaneous,
ValueInformation::FlowTemperature,
StorageNr(0),
TariffNr(0),
IndexNr(1),
PrintProperty::JSON ,
"The current forward heat media temperature.",
SET_FUNC(flow_c_, Unit::C),
GET_FUNC(flow_c_, Unit::C));
addFieldWithExtractor(
"return",
Quantity::Temperature,
NoDifVifKey,
VifScaling::Auto,
MeasurementType::Instantaneous,
ValueInformation::ReturnTemperature,
StorageNr(0),
TariffNr(0),
IndexNr(1),
PrintProperty::JSON ,
"The current return heat media temperature.",
SET_FUNC(return_c_, Unit::C),
GET_FUNC(return_c_, Unit::C));
}
// Test: MyUltra ultraheat 70444600 NOKEY
// telegram=|68F8F86808007200464470A7320404270000000974040970040C0E082303000C14079519000B2D0500000B3B0808000A5B52000A5F51000A6206004C14061818004C0E490603000C7800464470891071609B102D020100DB102D0201009B103B6009009A105B78009A105F74000C22726701003C22000000007C2200000000426C01018C2006000000008C3006000000008C80100600000000CC200600000000CC300600000000CC801006000000009A115B64009A115F63009B113B5208009B112D020100BC0122000000008C010E490603008C2106000000008C3106000000008C811006000000008C011406181800046D310ACA210F21040010A0C116|
// {"media":"heat","meter":"ultraheat","name":"MyUltra","id":"70444600","heat_kwh":8974.444444,"volume_m3":1995.07,"power_kw":0.5,"flow_m3h":0.808,"flow_c":52,"return_c":51,"timestamp":"1111-11-11T11:11:11Z"}
// |MyUltra;70444600;8974.444444;1111-11-11 11:11.11

Wyświetl plik

@ -719,7 +719,13 @@ void MeterCommonImplementation::addFieldWithExtractor(
&extracted_double_value, &extracted_double_value,
fi->vifScaling() == VifScaling::Auto)) fi->vifScaling() == VifScaling::Auto))
{ {
fi->setValueDouble(fi->defaultUnit(), extracted_double_value); Unit decoded_unit = fi->defaultUnit();
if (fi->valueInformation() != ValueInformation::Any &&
fi->valueInformation() != ValueInformation::None)
{
decoded_unit = toDefaultUnit(fi->valueInformation());
}
fi->setValueDouble(decoded_unit, extracted_double_value);
t->addMoreExplanation(offset, fi->renderJson(&m->conversions())); t->addMoreExplanation(offset, fi->renderJson(&m->conversions()));
found = true; found = true;
} }

Wyświetl plik

@ -268,10 +268,12 @@ void Telegram::print()
void Telegram::printDLL() void Telegram::printDLL()
{ {
string possible_drivers = autoDetectPossibleDrivers(); if (about.type == FrameType::WMBUS)
{
string possible_drivers = autoDetectPossibleDrivers();
string man = manufacturerFlag(dll_mfct); string man = manufacturerFlag(dll_mfct);
verbose("(telegram) DLL L=%02x C=%02x (%s) M=%04x (%s) A=%02x%02x%02x%02x VER=%02x TYPE=%02x (%s) (driver %s) DEV=%s RSSI=%d\n", verbose("(telegram) DLL L=%02x C=%02x (%s) M=%04x (%s) A=%02x%02x%02x%02x VER=%02x TYPE=%02x (%s) (driver %s) DEV=%s RSSI=%d\n",
dll_len, dll_len,
dll_c, cType(dll_c).c_str(), dll_c, cType(dll_c).c_str(),
dll_mfct, dll_mfct,
@ -283,6 +285,16 @@ void Telegram::printDLL()
possible_drivers.c_str(), possible_drivers.c_str(),
about.device.c_str(), about.device.c_str(),
about.rssi_dbm); about.rssi_dbm);
}
if (about.type == FrameType::MBUS)
{
verbose("(telegram) DLL L=%02x C=%02x (%s) A=%02x\n",
dll_len,
dll_c, cType(dll_c).c_str(),
mbus_primary_address);
}
} }
void Telegram::printELL() void Telegram::printELL()
@ -818,25 +830,46 @@ bool expectedMore(int line)
bool Telegram::parseMBusDLL(vector<uchar>::iterator &pos) bool Telegram::parseMBusDLL(vector<uchar>::iterator &pos)
{ {
int remaining = distance(pos, frame.end()); int remaining = distance(pos, frame.end());
if (remaining == 0) return expectedMore(__LINE__); if (remaining < 5) return expectedMore(__LINE__);
debug("(mbus) parse MBUS DLL @%d %d\n", distance(frame.begin(), pos), remaining);
debugPayload("(mbus) ", frame);
if (*pos != 0x68) return false;
addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "68 start");
debug("(wmbus) parse MBUS DLL @%d %d\n", distance(frame.begin(), pos), remaining);
dll_len = *pos; dll_len = *pos;
if (remaining < dll_len) return expectedMore(__LINE__);
addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x length (%d bytes)", dll_len, dll_len); addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x length (%d bytes)", dll_len, dll_len);
// Two identical length bytes are expected!
if (*pos != dll_len) return false;
addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x length again (%d bytes)", dll_len, dll_len);
if (*pos != 0x68) return false;
addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "68 start");
if (remaining < dll_len) return expectedMore(__LINE__);
dll_c = *pos; dll_c = *pos;
addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x dll-c (%s)", dll_c, mbusCField(dll_c).c_str()); addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x dll-c (%s)", dll_c, mbusCField(dll_c));
mbus_primary_address = *pos; mbus_primary_address = *pos;
addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x dll-a primary (%d)", mbus_primary_address, mbus_primary_address); addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x dll-a primary (%d)", mbus_primary_address, mbus_primary_address);
// Add dll_id to ids. // Add dll_id to ids.
string id = tostrprintf("%02x", dll_a[0]); string id = tostrprintf("%02x", mbus_primary_address);
ids.push_back(id); ids.push_back(id);
idsc = id; idsc = id;
return true; mbus_ci = *pos;
addExplanationAndIncrementPos(pos, 1, KindOfData::PROTOCOL, Understanding::FULL, "%02x dll-ci (%s)", mbus_ci, mbusCiField(mbus_ci));
if (mbus_ci == 0x72)
{
return parse_TPL_72(pos);
}
return false;
} }
bool Telegram::parseDLL(vector<uchar>::iterator &pos) bool Telegram::parseDLL(vector<uchar>::iterator &pos)
@ -1918,8 +1951,7 @@ bool Telegram::parseMBUS(vector<uchar> &input_frame, MeterKeys *mk, bool warn)
vector<uchar>::iterator pos = frame.begin(); vector<uchar>::iterator pos = frame.begin();
// Parsed accumulates parsed bytes. // Parsed accumulates parsed bytes.
parsed.clear(); parsed.clear();
// Fixes quirks from non-compliant meters to make telegram compatible with the standard
preProcess();
// ┌──────────────────────────────────────────────┐ // ┌──────────────────────────────────────────────┐
// │ │ // │ │
// │ Parse DLL Data Link Layer for Wireless MBUS. │ // │ Parse DLL Data Link Layer for Wireless MBUS. │
@ -2087,7 +2119,7 @@ string Telegram::autoDetectPossibleDrivers()
return possibles; return possibles;
} }
string mbusCField(uchar c_field) const char *mbusCField(uchar c_field)
{ {
string s; string s;
switch (c_field) switch (c_field)
@ -2097,6 +2129,24 @@ string mbusCField(uchar c_field)
return "?"; return "?";
} }
const char *mbusCiField(uchar c_field)
{
string s;
switch (c_field)
{
case 0x78: return "no header";
case 0x7a: return "short header";
case 0x72: return "long header";
case 0x79: return "no header compact frame";
case 0x7b: return "short header compact frame";
case 0x73: return "long header compact frame";
case 0x69: return "no header format frame";
case 0x6a: return "short header format frame";
case 0x6b: return "long header format frame";
}
return "?";
}
string cType(int c_field) string cType(int c_field)
{ {
string s; string s;
@ -2131,6 +2181,22 @@ string cType(int c_field)
return s; return s;
} }
bool isValidWMBusCField(int c_field)
{
// These are the currently seen valid C fields for wmbus telegrams.
// 0x46 is only from an ei6500 meter.... all else is ox44
// However in the future we might see relayed telegrams which will perhaps have
// some other c field.
return
c_field == 0x44 ||
c_field == 0x46;
}
bool isValidMBusCField(int c_field)
{
return false;
}
string ccType(int cc_field) string ccType(int cc_field)
{ {
string s = ""; string s = "";
@ -4223,7 +4289,10 @@ bool trimCRCsFrameFormatAInternal(std::vector<uchar> &payload, bool fail_is_ok)
if (calc_crc != check_crc && !FUZZING) if (calc_crc != check_crc && !FUZZING)
{ {
debug("(wmbus) ff a dll crc first (calculated %04x) did not match (expected %04x) for bytes 0-%zu!\n", calc_crc, check_crc, 10); if (!fail_is_ok)
{
debug("(wmbus) ff a dll crc first (calculated %04x) did not match (expected %04x) for bytes 0-%zu!\n", calc_crc, check_crc, 10);
}
return false; return false;
} }
out.insert(out.end(), payload.begin(), payload.begin()+10); out.insert(out.end(), payload.begin(), payload.begin()+10);
@ -4276,10 +4345,7 @@ bool trimCRCsFrameFormatAInternal(std::vector<uchar> &payload, bool fail_is_ok)
} }
} }
if (fail_is_ok) debugPayload("(wmbus) trimming frame A", payload);
{
debugPayload("(wmbus) trimming frame A", payload);
}
out[0] = out.size()-1; out[0] = out.size()-1;
size_t new_len = out[0]+1; size_t new_len = out[0]+1;
@ -4361,10 +4427,7 @@ bool trimCRCsFrameFormatBInternal(std::vector<uchar> &payload, bool fail_is_ok)
} }
} }
if (fail_is_ok) debugPayload("(wmbus) trimming frame B", payload);
{
debugPayload("(wmbus) trimming frame B", payload);
}
out[0] = out.size()-1; out[0] = out.size()-1;
size_t new_len = out[0]+1; size_t new_len = out[0]+1;
@ -4397,7 +4460,8 @@ bool trimCRCsFrameFormatB(std::vector<uchar> &payload)
FrameStatus checkWMBusFrame(vector<uchar> &data, FrameStatus checkWMBusFrame(vector<uchar> &data,
size_t *frame_length, size_t *frame_length,
int *payload_len_out, int *payload_len_out,
int *payload_offset) int *payload_offset,
bool only_test)
{ {
// Nice clean: 2A442D2C998734761B168D2021D0871921|58387802FF2071000413F81800004413F8180000615B // Nice clean: 2A442D2C998734761B168D2021D0871921|58387802FF2071000413F81800004413F8180000615B
// Ugly: 00615B2A442D2C998734761B168D2021D0871921|58387802FF2071000413F81800004413F8180000615B // Ugly: 00615B2A442D2C998734761B168D2021D0871921|58387802FF2071000413F81800004413F8180000615B
@ -4414,17 +4478,17 @@ FrameStatus checkWMBusFrame(vector<uchar> &data,
int type = data[1]; int type = data[1];
int offset = 1; int offset = 1;
if (type != 0x44) if (!isValidWMBusCField(type))
{ {
// Ouch, we are out of sync with the wmbus frames that we expect! // Ouch, we are out of sync with the wmbus frames that we expect!
// Since we currently do not handle any other type of frame, we can // Since we currently do not handle any other type of frame, we can
// look for the byte 0x44 in the buffer. If we find a 0x44 byte and // look for a valid c field (ie 0x44 0x46 etc) in the buffer.
// the length byte before it maps to the end of the buffer, // If we find such a byte and the length byte before maps to the end
// then we have found a valid telegram. // of the buffer, then we have found a valid telegram.
bool found = false; bool found = false;
for (size_t i = 0; i < data.size()-2; ++i) for (size_t i = 0; i < data.size()-2; ++i)
{ {
if (data[i+1] == 0x44) if (isValidWMBusCField(data[i+1]))
{ {
payload_len = data[i]; payload_len = data[i];
size_t remaining = data.size()-i; size_t remaining = data.size()-i;
@ -4440,8 +4504,15 @@ FrameStatus checkWMBusFrame(vector<uchar> &data,
if (!found) if (!found)
{ {
// No sensible telegram in the buffer. Flush it! // No sensible telegram in the buffer. Flush it!
verbose("(wmbus) no sensible telegram found, clearing buffer.\n"); if (!only_test)
data.clear(); {
verbose("(wmbus) no sensible telegram found, clearing buffer.\n");
data.clear();
}
else
{
debug("(wmbus) not a proper wmbus frame.\n");
}
return ErrorInFrame; return ErrorInFrame;
} }
} }
@ -4450,18 +4521,25 @@ FrameStatus checkWMBusFrame(vector<uchar> &data,
*frame_length = payload_len+offset; *frame_length = payload_len+offset;
if (data.size() < *frame_length) if (data.size() < *frame_length)
{ {
debug("(wmbus) not enough bytes, partial frame %d %d\n", data.size(), *frame_length); if (!only_test)
{
debug("(wmbus) not enough bytes, partial frame %d %d\n", data.size(), *frame_length);
}
return PartialFrame; return PartialFrame;
} }
debug("(wmbus) received full frame.\n"); if (!only_test)
{
debug("(wmbus) received full frame.\n");
}
return FullFrame; return FullFrame;
} }
FrameStatus checkMBusFrame(vector<uchar> &data, FrameStatus checkMBusFrame(vector<uchar> &data,
size_t *frame_length, size_t *frame_length,
int *payload_len_out, int *payload_len_out,
int *payload_offset) int *payload_offset,
bool only_test)
{ {
// Example: // Example:
// E5 // E5
@ -4471,48 +4549,78 @@ FrameStatus checkMBusFrame(vector<uchar> &data,
// 5E checksum // 5E checksum
// 16 stop // 16 stop
debugPayload("(wmbus) checkMBUSFrame\n", data); debugPayload("(mbus) checkMBUSFrame\n", data);
if (data.size() > 0 && data[0] == 0xe5) if (data.size() > 0 && data[0] == 0xe5)
{ {
// Single character confirmation frame. // Single character confirmation frame.
if (only_test)
{
// For testing purposes we require the frame to be a single char as well.
// This happens when we pass a frame on the command line or in a simulation file.
// Otherwise a normal wmbus telegram with length e5 will triggere this mbus command.
// (When reading from a serial line, we might have data coming after the e5,
// but then we know that we are talking mbus.)
if (data.size() != 1)
{
return ErrorInFrame;
}
}
*payload_len_out = 0; *payload_len_out = 0;
*payload_offset = 0; *payload_offset = 0;
*frame_length = 1; *frame_length = 1;
debug("(wmbus) received E5 single byte frame.\n"); if (!only_test)
{
debug("(mbus) received E5 single byte frame.\n");
}
return FullFrame; return FullFrame;
} }
if (data.size() < 6) if (data.size() < 6)
{ {
// 4 byte start, 1 checksum, 1 stop // 4 byte start, 1 checksum, 1 stop
debug("(wmbus) less than 6 bytes, partial frame\n"); if (!only_test)
{
debug("(mbus) less than 6 bytes, partial frame\n");
}
return PartialFrame; return PartialFrame;
} }
if (data[0] != 0x68 && data[3] != 0x68) if (data[0] != 0x68 && data[3] != 0x68)
{ {
verbose("(wmbus) no 0x68 byte found, clearing buffer.\n"); if (!only_test)
data.clear(); {
verbose("(mbus) no 0x68 byte found, clearing buffer.\n");
data.clear();
}
return ErrorInFrame; return ErrorInFrame;
} }
if (data[1] != data[2]) if (data[1] != data[2])
{ {
verbose("(wmbus) lengths not matching, clearing buffer.\n"); if (!only_test)
data.clear(); {
verbose("(mbus) lengths not matching, clearing buffer.\n");
data.clear();
}
return ErrorInFrame; return ErrorInFrame;
} }
int payload_len = data[1]; int payload_len = data[1];
*frame_length = payload_len+4+1+1; // start(4)+cs(1)+stop(1) *frame_length = payload_len+4+1+1; // start(4)+cs(1)+stop(1)
if (data.size() < *frame_length) if (data.size() < *frame_length)
{ {
debug("(wmbus) not enough bytes, partial frame %d %d\n", data.size(), *frame_length); if (!only_test)
{
debug("(mbus) not enough bytes, partial frame %d %d\n", data.size(), *frame_length);
}
return PartialFrame; return PartialFrame;
} }
uchar stop = data[*frame_length-1]; uchar stop = data[*frame_length-1];
if (stop != 0x16) if (stop != 0x16)
{ {
verbose("(wmbus) stop byte (0x%02x) at pos %d is not 0x16, clearing buffer.\n", stop, *frame_length-1); if (!only_test)
data.clear(); {
verbose("(mbus) stop byte (0x%02x) at pos %d is not 0x16, clearing buffer.\n", stop, *frame_length-1);
data.clear();
}
return ErrorInFrame; return ErrorInFrame;
} }
uchar csc = 0; uchar csc = 0;
@ -4520,14 +4628,20 @@ FrameStatus checkMBusFrame(vector<uchar> &data,
uchar cs = data[*frame_length-2]; uchar cs = data[*frame_length-2];
if (cs != csc) if (cs != csc)
{ {
verbose("(wmbus) expected checksum 0x%02x but got 0x%02x, clearing buffer.\n", csc, cs); if (!only_test)
data.clear(); {
verbose("(mbus) expected checksum 0x%02x but got 0x%02x, clearing buffer.\n", csc, cs);
data.clear();
}
return ErrorInFrame; return ErrorInFrame;
} }
*payload_len_out = *frame_length-6; *payload_len_out = *frame_length-6;
*payload_offset = 4; *payload_offset = 4;
debug("(wmbus) received full frame.\n"); if (!only_test)
{
debug("(mbus) received full frame.\n");
}
return FullFrame; return FullFrame;
} }

Wyświetl plik

@ -402,6 +402,7 @@ struct Telegram
int dll_mfct {}; int dll_mfct {};
uchar mbus_primary_address; // Single byte address 0-250 for mbus devices. uchar mbus_primary_address; // Single byte address 0-250 for mbus devices.
uchar mbus_ci; // MBus control information field.
vector<uchar> dll_a; // A field 6 bytes vector<uchar> dll_a; // A field 6 bytes
// The 6 a field bytes are composed of 4 id bytes, version and type. // The 6 a field bytes are composed of 4 id bytes, version and type.
@ -701,6 +702,8 @@ bool isCiFieldOfType(int ci_field, CI_TYPE type);
int ciFieldLength(int ci_field); int ciFieldLength(int ci_field);
string ciType(int ci_field); string ciType(int ci_field);
string cType(int c_field); string cType(int c_field);
bool isValidWMBusCField(int c_field);
bool isValidMBusCField(int c_field);
string ccType(int cc_field); string ccType(int cc_field);
string difType(int dif); string difType(int dif);
double vifScale(int vif); double vifScale(int vif);
@ -726,12 +729,14 @@ enum FrameStatus { PartialFrame, FullFrame, ErrorInFrame, TextAndNotFrame };
FrameStatus checkWMBusFrame(vector<uchar> &data, FrameStatus checkWMBusFrame(vector<uchar> &data,
size_t *frame_length, size_t *frame_length,
int *payload_len_out, int *payload_len_out,
int *payload_offset); int *payload_offset,
bool only_test);
FrameStatus checkMBusFrame(vector<uchar> &data, FrameStatus checkMBusFrame(vector<uchar> &data,
size_t *frame_length, size_t *frame_length,
int *payload_len_out, int *payload_len_out,
int *payload_offset); int *payload_offset,
bool only_test);
AccessCheck reDetectDevice(Detected *detected, shared_ptr<SerialCommunicationManager> handler); AccessCheck reDetectDevice(Detected *detected, shared_ptr<SerialCommunicationManager> handler);
@ -760,6 +765,7 @@ bool warned_for_telegram_before(Telegram *t, vector<uchar> &dll_a);
////////////////// MBUS ////////////////// MBUS
string mbusCField(uchar c_field); const char *mbusCField(uchar c_field);
const char *mbusCiField(uchar ci_field);
#endif #endif

Wyświetl plik

@ -423,13 +423,13 @@ FrameStatus WMBusAmber::checkAMB8465Frame(vector<uchar> &data,
size_t offset = 0; size_t offset = 0;
// The data[0] must be at least 10 bytes. C MM AAAA V T Ci // The data[0] must be at least 10 bytes. C MM AAAA V T Ci
// And C must be 0x44. // And C must be a valid wmbus c field.
while ((payload_len = data[offset]) < 10 || data[offset+1] != 0x44) while ((payload_len = data[offset]) < 10 || !isValidWMBusCField(data[offset+1]))
{ {
offset++; offset++;
if (offset + 2 >= data.size()) { if (offset + 2 >= data.size()) {
// No sensible telegram in the buffer. Flush it! // No sensible telegram in the buffer. Flush it!
// But not the last char, because the next char could be a 0x44 // But not the last char, because the next char could be a valid c field.
verbose("(amb8465) no sensible telegram found, clearing buffer.\n"); verbose("(amb8465) no sensible telegram found, clearing buffer.\n");
uchar last = data[data.size()-1]; uchar last = data[data.size()-1];
data.clear(); data.clear();

Wyświetl plik

@ -196,7 +196,7 @@ void WMBusRawTTY::processSerialData()
for (;;) for (;;)
{ {
FrameStatus status = checkWMBusFrame(data_buffer_, &frame_length, &payload_len, &payload_offset); FrameStatus status = checkWMBusFrame(data_buffer_, &frame_length, &payload_len, &payload_offset, false);
if (status == PartialFrame) if (status == PartialFrame)
{ {

Wyświetl plik

@ -306,7 +306,7 @@ void WMBusRC1180::processSerialData()
for (;;) for (;;)
{ {
FrameStatus status = checkWMBusFrame(read_buffer_, &frame_length, &payload_len, &payload_offset); FrameStatus status = checkWMBusFrame(read_buffer_, &frame_length, &payload_len, &payload_offset, false);
if (status == PartialFrame) if (status == PartialFrame)
{ {

Wyświetl plik

@ -174,15 +174,42 @@ void WMBusSimulator::simulate()
{ {
error("Not a valid string of hex bytes! \"%s\"\n", l.c_str()); error("Not a valid string of hex bytes! \"%s\"\n", l.c_str());
} }
AboutTelegram about("", 0, FrameType::WMBUS);
// Since this is a simulation, try to remove any frame format A or B
// data link layer crcs. These might remain if we have received the telegram
// to be simulated, from a CUL device or some other devices that does not remove the crcs.
// Normally the dongle (im871a/amb8465/rc1180/rtlwmbus/rtl443) removes the dll-crcs.
// Removing dll-crcs are also done explicitly in the wmbus_cul.cc driver.
removeAnyDLLCRCs(payload);
handleTelegram(about, payload); size_t frame_length;
int payload_len, payload_offset;
bool is_mbus = FullFrame == checkMBusFrame(payload, &frame_length, &payload_len, &payload_offset, true);
bool is_wmbus = FullFrame == checkWMBusFrame(payload, &frame_length, &payload_len, &payload_offset, true);
debug("(simulator) is_mbus=%s is_wmbus=%s\n",
is_mbus?"true":"false",
is_wmbus?"true":"false");
if (is_mbus && is_wmbus)
{
warning("(mbus) telegram matches both mbus and wmbus! Assuming it is wmbus only.\n");
is_mbus = false;
}
if (is_mbus)
{
debug("(simulator) is mbus telegram.\n");
AboutTelegram about("", 0, FrameType::MBUS);
handleTelegram(about, payload);
}
if (is_wmbus)
{
debug("(simulator) is wmbus telegram.\n");
AboutTelegram about("", 0, FrameType::WMBUS);
// Since this is a simulation, try to remove any frame format A or B
// data link layer crcs. These might remain if we have received the telegram
// to be simulated, from a CUL device or some other devices that does not remove the crcs.
// Normally the dongle (im871a/amb8465/rc1180/rtlwmbus/rtl443) removes the dll-crcs.
// Removing dll-crcs are also done explicitly in the wmbus_cul.cc driver.
removeAnyDLLCRCs(payload);
handleTelegram(about, payload);
}
} }
manager_->stop(); manager_->stop();
} }