kopia lustrzana https://github.com/weetmuts/wmbusmeters
Added FlowIQ3100 which is almost identical to Multical21.
rodzic
406af2cff0
commit
ba40403776
|
@ -26,7 +26,7 @@ Add --verbose for detailed debug information.
|
|||
Specifying auto as the device will automatically look for usb
|
||||
wmbus dongles on /dev/im871a and /dev/amb8465.
|
||||
|
||||
The meter type: multical21 (a water meter) is supported.
|
||||
The meter types: multical21 and flowiq3100 (water meters) are supported.
|
||||
The meter types: multical302 (heat) and omnipower (electricity)
|
||||
are work in progress.
|
||||
```
|
||||
|
|
36
dvparser.cc
36
dvparser.cc
|
@ -18,6 +18,12 @@
|
|||
#include"dvparser.h"
|
||||
#include"util.h"
|
||||
|
||||
// The parser should not crash on invalid data, but yeah, when I
|
||||
// need to debug it because it crashes on invalid data, then
|
||||
// I enable the following define...
|
||||
//#define DEBUG_PARSER(...) fprintf(stderr, __VA_ARGS__)
|
||||
#define DEBUG_PARSER(...)
|
||||
|
||||
using namespace std;
|
||||
|
||||
bool parseDV(Telegram *t,
|
||||
|
@ -26,13 +32,14 @@ bool parseDV(Telegram *t,
|
|||
map<string,pair<int,string>> *values,
|
||||
vector<uchar>::iterator *format,
|
||||
size_t format_len,
|
||||
uint16_t *format_hash)
|
||||
uint16_t *format_hash,
|
||||
function<int(int,int,int)> overrideDifLen)
|
||||
{
|
||||
map<string,int> dv_count;
|
||||
vector<uchar> format_bytes;
|
||||
vector<uchar> data_bytes;
|
||||
string dv, key;
|
||||
size_t parsed_len = t->parsed.size();
|
||||
size_t start_parse_here = t->parsed.size();
|
||||
vector<uchar>::iterator data_start = data;
|
||||
vector<uchar>::iterator data_end = data+data_len;
|
||||
vector<uchar>::iterator format_end;
|
||||
|
@ -62,14 +69,17 @@ bool parseDV(Telegram *t,
|
|||
|
||||
format_bytes.clear();
|
||||
for (;;) {
|
||||
DEBUG_PARSER("Remaining format data %ju\n", std::distance(*format,format_end));
|
||||
if (*format == format_end) break;
|
||||
uchar dif = **format;
|
||||
|
||||
DEBUG_PARSER("dif=%02x\n", dif);
|
||||
if (dif == 0x2f) {
|
||||
t->addExplanation(*format, 1, "%02X skip", dif);
|
||||
continue;
|
||||
}
|
||||
int len = difLenBytes(dif);
|
||||
DEBUG_PARSER("len=%d\n", len);
|
||||
if (full_header) {
|
||||
format_bytes.push_back(dif);
|
||||
t->addExplanation(*format, 1, "%02X dif (%s)", dif, difType(dif).c_str());
|
||||
|
@ -80,6 +90,7 @@ bool parseDV(Telegram *t,
|
|||
if (*format == format_end) { warning("(dvparser) warning: unexpected end of data (vif expected)"); break; }
|
||||
|
||||
uchar vif = **format;
|
||||
DEBUG_PARSER("vif=%02x\n", vif);
|
||||
if (full_header) {
|
||||
format_bytes.push_back(vif);
|
||||
t->addExplanation(*format, 1, "%02X vif (%s)", vif, vifType(vif).c_str());
|
||||
|
@ -87,11 +98,20 @@ bool parseDV(Telegram *t,
|
|||
(*format)++;
|
||||
}
|
||||
|
||||
if (overrideDifLen) {
|
||||
int new_len = overrideDifLen(dif, vif, len);
|
||||
if (new_len != len) {
|
||||
DEBUG_PARSER("changing len %d to %d for dif=%02x vif=%02x\n", len, new_len, dif, vif);
|
||||
len = new_len;
|
||||
}
|
||||
}
|
||||
|
||||
strprintf(dv, "%02X%02X", dif, vif);
|
||||
if ((vif&0x80) == 0x80) {
|
||||
// vif extension
|
||||
if (*format == format_end) { warning("(dvparser) warning: unexpected end of data (vife expected)"); break; }
|
||||
uchar vife = **format;
|
||||
DEBUG_PARSER("vife=%02x\n", vife);
|
||||
if (full_header) {
|
||||
format_bytes.push_back(vife);
|
||||
t->addExplanation(*format, 1, "%02X vife (%s)", vife, vifeType(vif, vife).c_str());
|
||||
|
@ -101,15 +121,23 @@ bool parseDV(Telegram *t,
|
|||
strprintf(dv, "%02X%02X%02X", dif, vif, vife);
|
||||
}
|
||||
|
||||
int count = ++dv_count[key];
|
||||
int count = ++dv_count[dv];
|
||||
DEBUG_PARSER("DifVif key is %s and its count is %d\n", dv.c_str(), count);
|
||||
if (count > 1) {
|
||||
strprintf(key, "%s_%d", dv.c_str(), count);
|
||||
} else {
|
||||
strprintf(key, "%s", dv.c_str());
|
||||
}
|
||||
|
||||
int remaining = std::distance(data,data_end);
|
||||
DEBUG_PARSER("remaining data %d len=%d\n", remaining, len);
|
||||
if (remaining < len) {
|
||||
warning("(dvparser) warning: unexpected end of data\n");
|
||||
len = remaining;
|
||||
}
|
||||
|
||||
string value = bin2hex(data, len);
|
||||
(*values)[key] = { parsed_len+data-data_start, value };
|
||||
(*values)[key] = { start_parse_here+data-data_start, value };
|
||||
t->addExplanation(data, len, "%s", value.c_str());
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,9 @@ bool parseDV(Telegram *t,
|
|||
std::map<std::string,std::pair<int,std::string>> *values,
|
||||
std::vector<uchar>::iterator *format = NULL,
|
||||
size_t format_len = 0,
|
||||
uint16_t *format_hash = NULL);
|
||||
uint16_t *format_hash = NULL,
|
||||
std::function<int(int,int,int)> overrideDifLen = NULL
|
||||
);
|
||||
|
||||
bool extractDVuint16(std::map<std::string,std::pair<int,std::string>> *values,
|
||||
std::string key,
|
||||
|
|
8
main.cc
8
main.cc
|
@ -48,7 +48,7 @@ int main(int argc, char **argv)
|
|||
" or 10m for ten minutes or 5s for five seconds.\n");
|
||||
printf("Specifying auto as the device will automatically look for usb\n");
|
||||
printf("wmbus dongles on /dev/im871a and /dev/amb8465\n\n");
|
||||
printf("The meter type: multical21 (a water meter) is supported.\n"
|
||||
printf("The meter types: multical21 and flowiq3100 (water meters) are supported.\n"
|
||||
"The meter types: multical302 (heat) and omnipower (electricity)\n"
|
||||
"are work in progress.\n\n");
|
||||
exit(0);
|
||||
|
@ -108,9 +108,13 @@ int main(int argc, char **argv)
|
|||
for (auto &m : cmdline->meters) {
|
||||
switch (toMeterType(m.type)) {
|
||||
case MULTICAL21_METER:
|
||||
m.meter = createMultical21(wmbus, m.name, m.id, m.key);
|
||||
m.meter = createMultical21(wmbus, m.name, m.id, m.key, MULTICAL21_METER);
|
||||
verbose("(multical21) configured \"%s\" \"multical21\" \"%s\" \"%s\"\n", m.name, m.id, m.key);
|
||||
break;
|
||||
case FLOWIQ3100_METER:
|
||||
m.meter = createMultical21(wmbus, m.name, m.id, m.key, FLOWIQ3100_METER);
|
||||
verbose("(flowiq3100) configured \"%s\" \"flowiq3100\" \"%s\" \"%s\"\n", m.name, m.id, m.key);
|
||||
break;
|
||||
case MULTICAL302_METER:
|
||||
m.meter = createMultical302(wmbus, m.name, m.id, m.key);
|
||||
verbose("(multical302) configured \"%s\" \"multical302\" \"%s\" \"%s\"\n", m.name, m.id, m.key);
|
||||
|
|
|
@ -45,7 +45,7 @@ using namespace std;
|
|||
#define INFO_CODE_BURST_SHIFT (4+9)
|
||||
|
||||
struct MeterMultical21 : public virtual WaterMeter, public virtual MeterCommonImplementation {
|
||||
MeterMultical21(WMBus *bus, const char *name, const char *id, const char *key);
|
||||
MeterMultical21(WMBus *bus, const char *name, const char *id, const char *key, MeterType mt);
|
||||
|
||||
// Total water counted through the meter
|
||||
float totalWaterConsumption();
|
||||
|
@ -86,11 +86,23 @@ private:
|
|||
bool has_target_volume_ {};
|
||||
float max_flow_ {};
|
||||
bool has_max_flow_ {};
|
||||
|
||||
const char *meter_name_; // multical21 or flowiq3100
|
||||
int expected_version_ {}; // 0x1b for Multical21 and 0x1d for FlowIQ3100
|
||||
};
|
||||
|
||||
MeterMultical21::MeterMultical21(WMBus *bus, const char *name, const char *id, const char *key) :
|
||||
MeterCommonImplementation(bus, name, id, key, MULTICAL21_METER, MANUFACTURER_KAM, 0x16)
|
||||
MeterMultical21::MeterMultical21(WMBus *bus, const char *name, const char *id, const char *key, MeterType mt) :
|
||||
MeterCommonImplementation(bus, name, id, key, mt, MANUFACTURER_KAM, 0x16)
|
||||
{
|
||||
if (type() == MULTICAL21_METER) {
|
||||
expected_version_ = 0x1b;
|
||||
meter_name_ = "multical21";
|
||||
} else if (type() == FLOWIQ3100_METER) {
|
||||
expected_version_ = 0x1d;
|
||||
meter_name_ = "flowiq3100";
|
||||
} else {
|
||||
assert(0);
|
||||
}
|
||||
MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*));
|
||||
}
|
||||
|
||||
|
@ -125,9 +137,12 @@ bool MeterMultical21::hasMaxFlow()
|
|||
return has_max_flow_;
|
||||
}
|
||||
|
||||
WaterMeter *createMultical21(WMBus *bus, const char *name, const char *id, const char *key)
|
||||
WaterMeter *createMultical21(WMBus *bus, const char *name, const char *id, const char *key, MeterType mt)
|
||||
{
|
||||
return new MeterMultical21(bus,name,id,key);
|
||||
if (mt != MULTICAL21_METER && mt != FLOWIQ3100_METER) {
|
||||
error("Internal error! Not a proper meter type when creating a multical21 style meter.\n");
|
||||
}
|
||||
return new MeterMultical21(bus,name,id,key,mt);
|
||||
}
|
||||
|
||||
void MeterMultical21::handleTelegram(Telegram *t)
|
||||
|
@ -137,20 +152,23 @@ void MeterMultical21::handleTelegram(Telegram *t)
|
|||
return;
|
||||
}
|
||||
|
||||
verbose("(multical21) telegram for %s %02x%02x%02x%02x\n",
|
||||
verbose("(%s) telegram for %s %02x%02x%02x%02x\n", meter_name_,
|
||||
name().c_str(),
|
||||
t->a_field_address[0], t->a_field_address[1], t->a_field_address[2],
|
||||
t->a_field_address[3]);
|
||||
|
||||
if (t->a_field_device_type != 0x16) {
|
||||
warning("(multical21) expected telegram for water media, but got \"%s\"!\n",
|
||||
warning("(%s) expected telegram for water media, but got \"%s\"!\n", meter_name_,
|
||||
mediaType(t->m_field, t->a_field_device_type).c_str());
|
||||
}
|
||||
|
||||
if (t->m_field != manufacturer() ||
|
||||
t->a_field_version != 0x1b) {
|
||||
warning("(multical21) expected telegram from KAM meter with version 0x1b, but got \"%s\" version 0x2x !\n",
|
||||
manufacturerFlag(t->m_field).c_str(), t->a_field_version);
|
||||
t->a_field_version != expected_version_) {
|
||||
warning("(%s) expected telegram from KAM meter with version 0x%02x, "
|
||||
"but got \"%s\" meter with version 0x%02x !\n", meter_name_,
|
||||
expected_version_,
|
||||
manufacturerFlag(t->m_field).c_str(),
|
||||
t->a_field_version);
|
||||
}
|
||||
|
||||
if (useAes()) {
|
||||
|
@ -159,11 +177,14 @@ void MeterMultical21::handleTelegram(Telegram *t)
|
|||
} else {
|
||||
t->content = t->payload;
|
||||
}
|
||||
logTelegram("(multical21) log", t->parsed, t->content);
|
||||
char log_prefix[256];
|
||||
snprintf(log_prefix, 255, "(%s) log", meter_name_);
|
||||
logTelegram(log_prefix, t->parsed, t->content);
|
||||
int content_start = t->parsed.size();
|
||||
processContent(t);
|
||||
if (isDebugEnabled()) {
|
||||
t->explainParse("(multical21)", content_start);
|
||||
snprintf(log_prefix, 255, "(%s)", meter_name_);
|
||||
t->explainParse(log_prefix, content_start);
|
||||
}
|
||||
triggerUpdate(t);
|
||||
}
|
||||
|
@ -194,7 +215,7 @@ void MeterMultical21::processContent(Telegram *t)
|
|||
if (frame_type == 0x79)
|
||||
{
|
||||
if (t->content.size() != 15) {
|
||||
warning("(multical21) warning: Unexpected length of short frame %zu. Expected 15 bytes! ",
|
||||
warning("(%s) warning: Unexpected length of short frame %zu. Expected 15 bytes! ", meter_name_,
|
||||
t->content.size());
|
||||
padWithZeroesTo(&t->content, 15, &t->content);
|
||||
warning("\n");
|
||||
|
@ -221,7 +242,13 @@ void MeterMultical21::processContent(Telegram *t)
|
|||
vector<uchar>::iterator format = format_bytes.begin();
|
||||
|
||||
map<string,pair<int,string>> values;
|
||||
parseDV(t, t->content.begin()+7, t->content.size()-7, &values, &format, format_bytes.size());
|
||||
parseDV(t, t->content.begin()+7, t->content.size()-7, &values, &format, format_bytes.size(),
|
||||
NULL,
|
||||
[](int dif, int vif, int len) {
|
||||
// Override len for 4413 to len 2 for compact frame!
|
||||
if (dif==0x44 && vif==0x13) { return 2; }
|
||||
return len;
|
||||
});
|
||||
|
||||
int offset;
|
||||
|
||||
|
@ -240,13 +267,13 @@ void MeterMultical21::processContent(Telegram *t)
|
|||
if (frame_type == 0x78)
|
||||
{
|
||||
if (t->content.size() != 22) {
|
||||
warning("(multical21) warning: Unexpected length of long frame %zu. Expected 22 bytes! ", t->content.size());
|
||||
warning("(%s) warning: Unexpected length of long frame %zu. Expected 22 bytes! ", meter_name_, t->content.size());
|
||||
padWithZeroesTo(&t->content, 22, &t->content);
|
||||
warning("\n");
|
||||
}
|
||||
|
||||
map<string,pair<int,string>> values;
|
||||
parseDV(t, t->content.begin()+3, t->content.size()-3-4, &values);
|
||||
parseDV(t, t->content.begin()+3, t->content.size()-3-2, &values);
|
||||
|
||||
// There are two more bytes in the data. Unknown purpose.
|
||||
int val0 = t->content[20];
|
||||
|
@ -269,7 +296,7 @@ void MeterMultical21::processContent(Telegram *t)
|
|||
vector<uchar>::iterator unknowns = t->content.begin()+20;
|
||||
t->addExplanation(unknowns, 2, "%02x%02x unknown", val0, val1);
|
||||
} else {
|
||||
warning("(multical21) warning: unknown frame %02x (did you use the correct encryption key?)\n", frame_type);
|
||||
warning("(%s) warning: unknown frame %02x (did you use the correct encryption key?)\n", meter_name_, frame_type);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -371,7 +398,7 @@ string MeterMultical21::statusHumanReadable()
|
|||
string MeterMultical21::decodeTime(int time)
|
||||
{
|
||||
if (time>7) {
|
||||
warning("(multical21) warning: Cannot decode time %d should be 0-7.\n", time);
|
||||
warning("(%s) warning: Cannot decode time %d should be 0-7.\n", meter_name_, time);
|
||||
}
|
||||
switch (time) {
|
||||
case 0:
|
||||
|
|
|
@ -93,6 +93,7 @@ string MeterCommonImplementation::datetimeOfUpdateRobot()
|
|||
MeterType toMeterType(const char *type)
|
||||
{
|
||||
if (!strcmp(type, "multical21")) return MULTICAL21_METER;
|
||||
if (!strcmp(type, "flowiq3100")) return FLOWIQ3100_METER;
|
||||
if (!strcmp(type, "multical302")) return MULTICAL302_METER;
|
||||
if (!strcmp(type, "omnipower")) return OMNIPOWER_METER;
|
||||
if (!strcmp(type, "water")) return MULTICAL21_METER;
|
||||
|
|
4
meters.h
4
meters.h
|
@ -24,7 +24,7 @@
|
|||
#include<string>
|
||||
#include<vector>
|
||||
|
||||
#define LIST_OF_METERS X(MULTICAL21_METER)X(MULTICAL302_METER)X(OMNIPOWER_METER)X(UNKNOWN_METER)
|
||||
#define LIST_OF_METERS X(MULTICAL21_METER)X(FLOWIQ3100_METER)X(MULTICAL302_METER)X(OMNIPOWER_METER)X(UNKNOWN_METER)
|
||||
|
||||
enum MeterType {
|
||||
#define X(name) name,
|
||||
|
@ -88,7 +88,7 @@ struct ElectricityMeter : public virtual Meter {
|
|||
|
||||
|
||||
MeterType toMeterType(const char *type);
|
||||
WaterMeter *createMultical21(WMBus *bus, const char *name, const char *id, const char *key);
|
||||
WaterMeter *createMultical21(WMBus *bus, const char *name, const char *id, const char *key, MeterType mt);
|
||||
HeatMeter *createMultical302(WMBus *bus, const char *name, const char *id, const char *key);
|
||||
ElectricityMeter *createOmnipower(WMBus *bus, const char *name, const char *id, const char *key);
|
||||
|
||||
|
|
6
test.sh
6
test.sh
|
@ -4,9 +4,9 @@ PROG="$1"
|
|||
|
||||
cat simulation.txt | grep '^{' > test_expected.txt
|
||||
$PROG --robot=json simulation.txt \
|
||||
MyTapWater water 76348799 "" \
|
||||
MyHeater heat 12345678 "" \
|
||||
MyElectricity electricity 15947107 "" \
|
||||
MyTapWater multical21 76348799 "" \
|
||||
MyHeater multical302 12345678 "" \
|
||||
MyElectricity omnipower 15947107 "" \
|
||||
> test_output.txt
|
||||
if [ "$?" == "0" ]
|
||||
then
|
||||
|
|
9
wmbus.cc
9
wmbus.cc
|
@ -444,9 +444,12 @@ int difLenBytes(int dif)
|
|||
{
|
||||
if (dif == 0x44) {
|
||||
// Special functions: 0x0F, 0x1F, 0x2F, 0x7F or dif >= 0x3F and dif <= 0x6F
|
||||
// 0x44 is used in the Kamstrup Multical21 for the lower 16 bits of data
|
||||
// to be combined with the upper 16 bits of a previous value.
|
||||
return 2;
|
||||
// 0x44 is used in short frames in the Kamstrup Multical21/FlowIQ3100 to
|
||||
// denote the lower 16 bits of data to be combined with the upper 16 bits of a previous value.
|
||||
// For long frames in the same meters however, the full 32 bits are stored.
|
||||
// Let us return 4 byte here and deal with the truncated version in the meter code for compact frames,
|
||||
// through a length override.
|
||||
return 4;
|
||||
}
|
||||
|
||||
int t = dif & 0x0f;
|
||||
|
|
Ładowanie…
Reference in New Issue