Added FlowIQ3100 which is almost identical to Multical21.

pull/5/head
weetmuts 2018-04-16 20:47:27 +02:00
rodzic 406af2cff0
commit ba40403776
9 zmienionych plików z 99 dodań i 34 usunięć

Wyświetl plik

@ -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.
```

Wyświetl plik

@ -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());
}

Wyświetl plik

@ -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,

Wyświetl plik

@ -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);

Wyświetl plik

@ -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:

Wyświetl plik

@ -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;

Wyświetl plik

@ -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);

Wyświetl plik

@ -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

Wyświetl plik

@ -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;