Added support for the Amber amb8465 wmbus usb dongle.

pull/5/head
weetmuts 2018-02-28 22:14:16 +01:00
rodzic 0443c17677
commit fd88627667
17 zmienionych plików z 1080 dodań i 333 usunięć

9
CHANGES 100644
Wyświetl plik

@ -0,0 +1,9 @@
Version 0.3:
Added support for wmbus USB receiver Amber AMB8465.
Version 0.2:
Initial working release supporting wmbus USB receiver IMST im871a and the meter Multical21.

Wyświetl plik

@ -27,7 +27,7 @@ endif
$(shell mkdir -p $(BUILD))
CXXFLAGS := $(DEBUG_FLAGS) -Wall -fmessage-length=0 -std=c++11 -Wno-unused-function "-DWMBUSMETERS_VERSION=\"0.2\""
CXXFLAGS := $(DEBUG_FLAGS) -Wall -fmessage-length=0 -std=c++11 -Wno-unused-function "-DWMBUSMETERS_VERSION=\"0.3\""
$(BUILD)/%.o: %.cc $(wildcard %.h)
$(CXX) $(CXXFLAGS) $< -c -o $@
@ -42,6 +42,7 @@ METERS_OBJS:=\
$(BUILD)/serial.o \
$(BUILD)/util.o \
$(BUILD)/wmbus.o \
$(BUILD)/wmbus_amb8465.o \
$(BUILD)/wmbus_im871a.o \
all: $(BUILD)/wmbusmeters

Wyświetl plik

@ -8,8 +8,8 @@ utility meter readings.
|Linux G++| [![Build Status](https://travis-ci.org/weetmuts/wmbusmeters.svg?branch=master)](https://travis-ci.org/weetmuts/wmbusmeters) |
```
wmbusmeters version: 0.2
Usage: wmbusmeters {options} [usbdevice] { [meter_name] [meter_id] [meter_key] }*
wmbusmeters version: 0.3
Usage: wmbusmeters {options} (auto | /dev/ttyUSBx)] { [meter_name] [meter_id] [meter_key] }*
Add more meter triplets to listen to more meters.
Add --verbose for detailed debug information.
@ -17,6 +17,9 @@ Add --verbose for detailed debug information.
--meterfiles to create status files below tmp,
named /tmp/meter_name, containing the latest reading.
--oneshot wait for an update from each meter, then quit.
Specifying auto as the device will automatically look for usb
wmbus dongles on /dev/im871a and /dev/amb8465
```
No meter triplets means listen for telegram traffic and print any id heard.
@ -28,16 +31,20 @@ make
./build/wmbusmeters /dev/ttyUSB0 MyTapWater 12345678 00112233445566778899AABBCCDDEEFF
```
wmbusmeters will detect which kind of dongle is connected to /dev/ttyUSB0. It can be either an IMST 871a dongle or an Amber Wireless AMB8465. If you have setup the udev rules below, then you can use auto instead of /dev/ttyUSB0.
Example output:
`MyTapWater 12345678 6.375 m3 2017-08-31 09:09.08 3.040 m3 DRY(dry 22-31 days)`
`./build/wmbusmeters --verbose /dev/ttyUSB0 MyTapWater 12345678 00112233445566778899AABBCCDDEEFF`
`./build/wmbusmeters --robot /dev/ttyUSB0 MyTapWater 12345678 00112233445566778899AABBCCDDEEFF`
`./build/wmbusmeters --robot auto /dev/ttyUSB1 12345678 00112233445566778899AABBCCDDEEFF`
Robot output:
`{"name":"MyTapWater","id":"12345678","total_m3":6.375,"target_m3":3.040,"current_status":"","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"2017-08-31T09:07:18Z"}`
You can use `--debug` to get both verbose output and the actual data bytes sent back and forth with the wmbus usb dongle.
`make HOST=arm`
Binary generated: `./build_arm/wmbusmeters`
@ -63,15 +70,21 @@ Or even better, add this udev rule:
Create the file: `/etc/udev/rules.d/99-usb-serial.rules` with the content
```
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="im871a",MODE="0660", GROUP="yourowngroup"
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="amb8465",MODE="0660", GROUP="yourowngroup"
```
This will create a symlink named `/dev/im871a` to the particular USB port that the dongle got assigned.
When you insert the wmbus USB dongle, a properly named symlink will be
created: either `/dev/im871a` or `/dev/amb8465`. These symlinks are
necessary if you want to pass "auto" to wmbusmeters instead of the
exact serial port /dev/ttyUSBx.
# Limitations
Currently only supports the USB stick receiver im871A
and the water meter Multical21. The source code is modular
and it should be relatively straightforward to add
more receivers and meters.
Two usb wmbus receivers are supported: IMST im871A and Amber Wireless AMB8465.
One supported meter: Multical21.
The source code is modular and it should be relatively straightforward to add more receivers and meters.
# Good documents on the wireless mbus protocol:

Wyświetl plik

@ -1,15 +1,15 @@
// Copyright (c) 2017 Fredrik Öhrström
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -26,7 +26,7 @@ using namespace std;
CommandLine *parseCommandLine(int argc, char **argv) {
CommandLine * c = new CommandLine;
int i=1;
if (argc < 2) {
c->need_help = true;
@ -37,11 +37,21 @@ CommandLine *parseCommandLine(int argc, char **argv) {
c->need_help = true;
return c;
}
if (!strcmp(argv[i], "--silence")) {
c->silence = true;
i++;
continue;
}
if (!strcmp(argv[i], "--verbose")) {
c->verbose = true;
i++;
continue;
}
if (!strcmp(argv[i], "--debug")) {
c->debug = true;
i++;
continue;
}
if (!strcmp(argv[i], "--robot")) {
c->robot = true;
i++;
@ -63,7 +73,7 @@ CommandLine *parseCommandLine(int argc, char **argv) {
}
error("Unknown option \"%s\"\n", argv[i]);
}
c->usb_device = argv[i];
i++;
if (!c->usb_device) error("You must supply the usb device to which the wmbus dongle is connected.\n");
@ -79,7 +89,7 @@ CommandLine *parseCommandLine(int argc, char **argv) {
char *name = argv[m*3+i+0];
char *id = argv[m*3+i+1];
char *key = argv[m*3+i+2];
if (!isValidId(id)) error("Not a valid meter id \"%s\"\n", id);
if (!isValidKey(key)) error("Not a valid meter key \"%s\"\n", key);
c->meters.push_back(MeterInfo(name,id,key));
@ -87,5 +97,3 @@ CommandLine *parseCommandLine(int argc, char **argv) {
return c;
}

Wyświetl plik

@ -1,15 +1,15 @@
// Copyright (c) 2017 Fredrik Öhrström
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -32,24 +32,26 @@ struct MeterInfo {
char *id;
char *key;
Meter *meter;
MeterInfo(char *n, char *i, char *k) {
name = n;
id = i;
key = k;
}
};
struct CommandLine {
bool need_help;
bool verbose;
bool meterfiles;
bool robot;
bool oneshot;
char *usb_device;
bool need_help {};
bool silence {};
bool verbose {};
bool debug {};
bool meterfiles {};
bool robot {};
bool oneshot {};
char *usb_device {};
vector<MeterInfo> meters;
};
CommandLine *parseCommandLine(int argc, char **argv);
#endif

65
main.cc
Wyświetl plik

@ -1,15 +1,15 @@
// Copyright (c) 2017 Fredrik Öhrström
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -34,59 +34,76 @@ void oneshotCheck(CommandLine *cmdline, SerialCommunicationManager *manager, Met
int main(int argc, char **argv)
{
CommandLine *cmdline = parseCommandLine(argc, argv);
if (cmdline->need_help) {
printf("wmbusmeters version: " WMBUSMETERS_VERSION "\n");
printf("Usage: wmbusmeters [--verbose] [--robot] [usbdevice] { [meter_name] [meter_id] [meter_key] }* \n");
printf("\nAdd more meter quadruplets to listen to more meters.\n");
printf("Add --verbose for detailed debug information.\n");
printf("Usage: wmbusmeters [options] (auto | /dev/ttyUSBx) { [meter_name] [meter_id] [meter_key] }* \n\n");
printf("Add more meter triplets to listen to more meters.\n");
printf("Add --verbose for more detailed information on communication.\n");
printf(" --robot for json output.\n");
printf(" --meterfiles to create status files below tmp,\n"
" named /tmp/meter_name, containing the latest reading.\n");
printf(" --oneshot wait for an update from each meter, then quit.\n");
printf(" --oneshot wait for an update from each meter, then quit.\n\n");
printf("Specifying auto as the device will automatically look for usb\n");
printf("wmbus dongles on /dev/im871a and /dev/amb8465\n\n");
exit(0);
}
// We want the data visible in the log file asap!
setbuf(stdout, NULL);
warningSilenced(cmdline->silence);
verboseEnabled(cmdline->verbose);
debugEnabled(cmdline->debug);
auto manager = createSerialCommunicationManager();
onExit(call(manager,stop));
auto wmbus = openIM871A(cmdline->usb_device, manager);
wmbus->setLinkMode(C1a);
if (wmbus->getLinkMode()!=C1a) error("Could not set link mode to C1a\n");
WMBus *wmbus = NULL;
// We want the data visible in the log file asap!
setbuf(stdout, NULL);
auto type_and_device = detectMBusDevice(cmdline->usb_device, manager);
switch (type_and_device.first) {
case DEVICE_IM871A:
verbose("(im871a) detected on %s\n", type_and_device.second.c_str());
wmbus = openIM871A(type_and_device.second, manager);
break;
case DEVICE_AMB8465:
verbose("(amb8465) detected on %s\n", type_and_device.second.c_str());
wmbus = openAMB8465(type_and_device.second, manager);
break;
case DEVICE_UNKNOWN:
error("No wmbus device found!\n");
exit(1);
break;
}
wmbus->setLinkMode(LinkModeC1);
if (wmbus->getLinkMode()!=LinkModeC1) error("Could not set link mode to receive C1 telegrams.\n");
Printer *output = new Printer(cmdline->robot, cmdline->meterfiles);
if (cmdline->meters.size() > 0) {
for (auto &m : cmdline->meters) {
verbose("Configuring meter: \"%s\" \"%s\" \"%s\"\n", m.name, m.id, m.key);
m.meter = createMultical21(wmbus, m.name, m.id, m.key);
verbose("(multical21) configured \"%s\" \"%s\" \"%s\"\n", m.name, m.id, m.key);
m.meter->onUpdate(calll(output,print,Meter*));
m.meter->onUpdate([cmdline,manager](Meter*meter) { oneshotCheck(cmdline,manager,meter); });
}
} else {
printf("No meters configured. Printing id:s of all telegrams heard! \n");
printf("To configure a meter, add a triplet to the command line: name id key\n");
printf("Where name is your string identifying the meter.\n");
printf(" id is the 8 digits printed on the meter.\n");
printf(" key is 32 hex digits with the aes key.\n\n");
printf("No meters configured. Printing id:s of all telegrams heard!\n\n");
wmbus->onTelegram([](Telegram *t){t->print();});
}
manager->waitForStop();
manager->waitForStop();
}
void oneshotCheck(CommandLine *cmdline, SerialCommunicationManager *manager, Meter *meter)
{
if (!cmdline->oneshot) return;
for (auto &m : cmdline->meters) {
if (m.meter->numUpdates() == 0) return;
}

Wyświetl plik

@ -1,15 +1,15 @@
// Copyright (c) 2017 Fredrik Öhrström
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -41,7 +41,7 @@ using namespace std;
#define INFO_CODE_LEAK_SHIFT (4+6)
#define INFO_CODE_BURST 0x08
#define INFO_CODE_BURST_SHIFT (4+9)
#define INFO_CODE_BURST_SHIFT (4+9)
struct MeterMultical21 : public Meter {
MeterMultical21(WMBus *bus, const char *name, const char *id, const char *key);
@ -56,62 +56,56 @@ struct MeterMultical21 : public Meter {
// We can see which was sent inside the wmbus message!
// Target water consumption: The total consumption at the start of the previous 30 day period.
float targetWaterConsumption();
bool hasTargetWaterConsumption();
bool hasTargetWaterConsumption();
// Max flow during last month or last 24 hours depending on meter configuration.
float maxFlow();
bool hasMaxFlow();
// statusHumanReadable: DRY,REVERSED,LEAK,BURST if that status is detected right now, followed by
// (dry 15-21 days) which means that, even it DRY is not active right now,
// DRY has been active for 15-21 days during the last 30 days.
// DRY has been active for 15-21 days during the last 30 days.
string statusHumanReadable();
string status();
string timeDry();
string timeReversed();
string timeLeaking();
string timeBursting();
string datetimeOfUpdateHumanReadable();
string datetimeOfUpdateRobot();
void onUpdate(function<void(Meter*)> cb);
int numUpdates();
private:
void handleTelegram(Telegram*t);
void processContent(vector<uchar> &d);
string decodeTime(int time);
int info_codes_;
int info_codes_;
float total_water_consumption_;
bool has_total_water_consumption_;
float target_volume_;
bool has_target_volume_;
float max_flow_;
bool has_max_flow_;
time_t datetime_of_update_;
string name_;
vector<uchar> id_;
vector<uchar> key_;
WMBus *bus_;
vector<function<void(Meter*)>> on_update_;
int num_updates_;
bool matches_any_id_;
bool use_aes_;
};
MeterMultical21::MeterMultical21(WMBus *bus, const char *name, const char *id, const char *key) :
info_codes_(0), total_water_consumption_(0), has_total_water_consumption_(false),
target_volume_(0), has_target_volume_(false),
max_flow_(0), has_max_flow_(false), name_(name), bus_(bus), num_updates_(0),
matches_any_id_(false), use_aes_(true)
max_flow_(0), has_max_flow_(false), name_(name), bus_(bus), num_updates_(0), use_aes_(true)
{
if (strlen(id) == 0) {
matches_any_id_ = true;
} else {
hex2bin(id, &id_);
}
hex2bin(id, &id_);
if (strlen(key) == 0) {
use_aes_ = false;
} else {
@ -182,8 +176,8 @@ string MeterMultical21::datetimeOfUpdateRobot()
{
char datetime[40];
memset(datetime, 0, sizeof(datetime));
// This is the date time in the Greenwich timezone, dont get surprised!
strftime(datetime, sizeof(datetime), "%FT%TZ", gmtime(&datetime_of_update_));
// This is the date time in the Greenwich timezone (Zulu time), dont get surprised!
strftime(datetime, sizeof(datetime), "%FT%TZ", gmtime(&datetime_of_update_));
return string(datetime);
}
@ -193,38 +187,35 @@ Meter *createMultical21(WMBus *bus, const char *name, const char *id, const char
void MeterMultical21::handleTelegram(Telegram *t) {
if (matches_any_id_ || (
t->m_field == MANUFACTURER_KAM &&
t->a_field_address[3] == id_[3] &&
t->a_field_address[2] == id_[2] &&
t->a_field_address[1] == id_[1] &&
t->a_field_address[0] == id_[0])) {
verbose("Meter %s receives update with id %02x%02x%02x%02x!\n",
name_.c_str(),
t->a_field_address[0], t->a_field_address[1], t->a_field_address[2],
t->a_field_address[3]);
} else {
verbose("Meter %s ignores message with id %02x%02x%02x%02x \n",
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->m_field != MANUFACTURER_KAM ||
t->a_field_address[3] != id_[3] ||
t->a_field_address[2] != id_[2] ||
t->a_field_address[1] != id_[1] ||
t->a_field_address[0] != id_[0])
{
// This telegram is not intended for this meter.
return;
}
verbose("(multical21) %s %02x%02x%02x%02x ",
name_.c_str(),
t->a_field_address[0], t->a_field_address[1], t->a_field_address[2],
t->a_field_address[3]);
// This is part of the wmbus protocol, should be moved to wmbus source files!
int cc_field = t->payload[0];
verbose("CC field=%02x ( ", cc_field);
verbose("CC-field=%02x ( ", cc_field);
if (cc_field & CC_B_BIDIRECTIONAL_BIT) verbose("bidir ");
if (cc_field & CC_RD_RESPONSE_DELAY_BIT) verbose("fast_res ");
else verbose("slow_res ");
if (cc_field & CC_S_SYNCH_FRAME_BIT) verbose("synch ");
if (cc_field & CC_R_RELAYED_BIT) verbose("relayed "); // Relayed by a repeater
if (cc_field & CC_P_HIGH_PRIO_BIT) verbose("prio ");
verbose(")\n");
verbose(") ");
int acc = t->payload[1];
verbose("ACC field=%02x\n", acc);
verbose("ACC-field=%02x ", acc);
uchar sn[4];
sn[0] = t->payload[2];
sn[1] = t->payload[3];
@ -232,16 +223,19 @@ void MeterMultical21::handleTelegram(Telegram *t) {
sn[3] = t->payload[5];
verbose("SN=%02x%02x%02x%02x encrypted=", sn[3], sn[2], sn[1], sn[0]);
if ((sn[3] & SN_ENC_BITS) == 0) verbose("no\n");
else if ((sn[3] & SN_ENC_BITS) == 0x40) verbose("yes\n");
// Here is a bug, since it always reports no encryption, but the multicals21
// so far have all have encryption enabled.
if ((sn[3] & SN_ENC_BITS) == 0) verbose("no");
else if ((sn[3] & SN_ENC_BITS) == 0x40) verbose("yes");
else verbose("? %d\n", sn[3] & SN_ENC_BITS);
verbose("\n");
// The content begins with the Payload CRC at offset 6.
vector<uchar> content;
content.insert(content.end(), t->payload.begin()+6, t->payload.end());
size_t remaining = content.size();
if (remaining > 16) remaining = 16;
uchar iv[16];
int i=0;
// M-field
@ -257,26 +251,22 @@ void MeterMultical21::handleTelegram(Telegram *t) {
// BC
iv[i++] = 0;
if (use_aes_) {
vector<uchar> ivv(iv, iv+16);
verbose("Decrypting\n");
string s = bin2hex(ivv);
verbose("IV %s\n", s.c_str());
debug("(multical21) IV %s\n", s.c_str());
uchar xordata[16];
AES_ECB_encrypt(iv, &key_[0], xordata, 16);
uchar decrypt[16];
xorit(xordata, &content[0], decrypt, remaining);
vector<uchar> dec(decrypt, decrypt+remaining);
string answer = bin2hex(dec);
verbose("Decrypted >%s<\n", answer.c_str());
debugPayload("(multical21) decrypted", dec);
if (content.size() > 22) {
fprintf(stderr, "Received too many bytes of content from a Multical21 meter!\n"
warning("(multical21) warning: Received too many bytes of content! "
"Got %zu bytes, expected at most 22.\n", content.size());
}
if (content.size() > 16) {
@ -284,19 +274,18 @@ void MeterMultical21::handleTelegram(Telegram *t) {
// So a second block should enough for everyone!
remaining = content.size()-16;
if (remaining > 16) remaining = 16; // Should not happen.
incrementIV(iv, sizeof(iv));
vector<uchar> ivv2(iv, iv+16);
string s2 = bin2hex(ivv2);
verbose("IV+1 %s\n", s2.c_str());
debug("(multical21) IV+1 %s\n", s2.c_str());
AES_ECB_encrypt(iv, &key_[0], xordata, 16);
xorit(xordata, &content[16], decrypt, remaining);
vector<uchar> dec2(decrypt, decrypt+remaining);
string answer2 = bin2hex(dec2);
verbose("Decrypted second block >%s<\n", answer2.c_str());
debugPayload("(multical21) decrypted", dec2);
// Append the second decrypted block to the first.
dec.insert(dec.end(), dec2.begin(), dec2.end());
@ -319,23 +308,23 @@ float getScaleFactor(int vif) {
case 0x15: return 10.0;
case 0x16: return 10.0;
}
fprintf(stderr, "Warning! Unknown vif code %d for scale factor.\n", vif);
warning("(multical21) warning: Unknown vif code %d for scale factor, using 1000.0 instead.\n", vif);
return 1000.0;
}
void MeterMultical21::processContent(vector<uchar> &c) {
int crc0 = c[0];
int crc1 = c[1];
int crc1 = c[1];
int frame_type = c[2];
verbose("CRC16: %02x%02x\n", crc1, crc0);
verbose("(multical21) CRC16: %02x%02x\n", crc1, crc0);
/*
uint16_t crc = crc16(&(c[2]), c.size()-2);
verbose("CRC16 calc: %04x\n", crc);
verbose("(multical21) CRC16 calc: %04x\n", crc);
*/
if (frame_type == 0x79) {
verbose("Short frame %d bytes\n", c.size());
verbose("(multical21) Short frame %d bytes\n", c.size());
if (c.size() != 15) {
fprintf(stderr, "Warning! Unexpected length of frame %zu. Expected 15 bytes!\n", c.size());
warning("(multical21) warning: Unexpected length of frame %zu. Expected 15 bytes!\n", c.size());
}
/*int ecrc0 = c[3];
int ecrc1 = c[4];
@ -351,11 +340,11 @@ void MeterMultical21::processContent(vector<uchar> &c) {
int rec3val1 = c[14];
info_codes_ = rec1val1*256+rec1val0;
verbose("short rec1 %02x %02x info codes\n", rec1val1, rec1val0);
verbose("(multical21) short rec1 %02x %02x info codes\n", rec1val1, rec1val0);
int consumption_raw = rec2val3*256*256*256 + rec2val2*256*256 + rec2val1*256 + rec2val0;
verbose("short rec2 %02x %02x %02x %02x = %d total consumption\n", rec2val3, rec2val2, rec2val1, rec2val0, consumption_raw);
verbose("(multical21) short rec2 %02x %02x %02x %02x = %d total consumption\n", rec2val3, rec2val2, rec2val1, rec2val0, consumption_raw);
// The dif=0x04 vif=0x13 means current volume with scale factor .001
total_water_consumption_ = ((float)consumption_raw) / ((float)1000);
has_total_water_consumption_ = true;
@ -363,31 +352,31 @@ void MeterMultical21::processContent(vector<uchar> &c) {
// The short frame target volume supplies two low bytes,
// the remaining two hi bytes are >>probably<< picked from rec2!
int target_volume_raw = rec2val3*256*256*256 + rec2val2*256*256 + rec3val1*256 + rec3val0;
verbose("short rec3 (%02x %02x) %02x %02x = %d target volume\n", rec2val3, rec2val2, rec3val1, rec3val0, target_volume_raw);
verbose("(multical21) short rec3 (%02x %02x) %02x %02x = %d target volume\n", rec2val3, rec2val2, rec3val1, rec3val0, target_volume_raw);
target_volume_ = ((float)target_volume_raw) / ((float)1000);
has_target_volume_ = true;
} else
if (frame_type == 0x78) {
verbose("Full frame %d bytes\n", c.size());
verbose("(multical21) Full frame %d bytes\n", c.size());
if (c.size() != 22) {
fprintf(stderr, "Warning! Unexpected length of frame %zu. Expected 22 bytes!\n", c.size());
warning("(multical21) warning: Unexpected length of frame %zu. Expected 22 bytes!\n", c.size());
}
int rec1dif = c[3];
int rec1dif = c[3];
int rec1vif = c[4];
int rec1vife = c[5];
int rec1val0 = c[6];
int rec1val1 = c[7];
int rec2dif = c[8];
int rec2dif = c[8];
int rec2vif = c[9];
int rec2val0 = c[10];
int rec2val1 = c[11];
int rec2val2 = c[12];
int rec2val3 = c[13];
int rec3dif = c[14];
int rec3dif = c[14];
int rec3vif = c[15];
int rec3val0 = c[16];
int rec3val1 = c[17];
@ -399,41 +388,41 @@ void MeterMultical21::processContent(vector<uchar> &c) {
int rec4val1 = c[21];
if (rec1dif != 0x02 || rec1vif != 0xff || rec1vife != 0x20 ) {
fprintf(stderr, "Warning unexpected field! Expected info codes\n"
warning("(multical21) warning: Unexpected field! Expected info codes\n"
"with dif=0x02 vif=0xff vife=0x20 but got dif=%02x vif=%02x vife=%02x\n", rec1dif, rec1vif, rec1vife);
}
info_codes_ = rec1val1*256+rec1val0;
verbose("full rec1 dif=%02x vif=%02x vife=%02x\n", rec1dif, rec1vif, rec1vife);
verbose("full rec1 %02x %02x info codes\n", rec1val1, rec1val0);
verbose("(multical21) full rec1 dif=%02x vif=%02x vife=%02x\n", rec1dif, rec1vif, rec1vife);
verbose("(multical21) full rec1 %02x %02x info codes\n", rec1val1, rec1val0);
if (rec2dif != 0x04 || rec2vif != 0x13) {
fprintf(stderr, "Warning unexpected field! Expected current volume\n"
warning("(multical21) warning: Unexpected field! Expected current volume\n"
"with dif=0x04 vif=0x13 but got dif=%02x vif=%02x\n", rec2dif, rec2vif);
}
int consumption_raw = rec2val3*256*256*256 + rec2val2*256*256 + rec2val1*256 + rec2val0;
verbose("full rec2 dif=%02x vif=%02x\n", rec2dif, rec2vif);
verbose("full rec2 %02x %02x %02x %02x = %d total consumption\n", rec2val3, rec2val2, rec2val1, rec2val0, consumption_raw);
verbose("(multical21) full rec2 dif=%02x vif=%02x\n", rec2dif, rec2vif);
verbose("(multical21) full rec2 %02x %02x %02x %02x = %d total consumption\n", rec2val3, rec2val2, rec2val1, rec2val0, consumption_raw);
// The dif=0x04 vif=0x13 means current volume with scale factor .001
total_water_consumption_ = ((float)consumption_raw) / ((float)1000);
total_water_consumption_ = ((float)consumption_raw) / ((float)1000);
has_total_water_consumption_ = true;
if (rec3dif != 0x44 || rec3vif != 0x13) {
fprintf(stderr, "Warning unexpecte field! Expected target volume (ie volume recorded on first day of month)\n"
warning("(multical21) warning: Unexpected field! Expected target volume (ie volume recorded on first day of month)\n"
"with dif=0x44 vif=0x13 but got dif=%02x vif=%02x\n", rec3dif, rec3vif);
}
int target_volume_raw = rec3val3*256*256*256 + rec3val2*256*256 + rec3val1*256 + rec3val0;
verbose("full rec3 dif=%02x vif=%02x\n", rec3dif, rec3vif);
verbose("full rec3 %02x %02x %02x %02x = %d target volume\n", rec3val3, rec3val2, rec3val1, rec3val0, target_volume_raw);
verbose("(multical21) full rec3 dif=%02x vif=%02x\n", rec3dif, rec3vif);
verbose("(multical21) full rec3 %02x %02x %02x %02x = %d target volume\n", rec3val3, rec3val2, rec3val1, rec3val0, target_volume_raw);
target_volume_ = ((float)target_volume_raw) / ((float)1000);
has_target_volume_ = true;
// To unknown bytes, seems to be very constant.
verbose("full rec4 %02x %02x = unknown\n", rec4val1, rec4val0);
verbose("(multical21) full rec4 %02x %02x = unknown\n", rec4val1, rec4val0);
} else {
fprintf(stderr, "Unknown frame %02x\n", frame_type);
warning("(multical21) warning: Unknown frame %02x\n", frame_type);
}
}
@ -528,7 +517,7 @@ string MeterMultical21::statusHumanReadable() {
string MeterMultical21::decodeTime(int time) {
if (time>7) {
fprintf(stderr, "Error when decoding time %d\n", time);
warning("(multical21) warning: Cannot decode time %d should be 0-7.\n", time);
}
switch (time) {
case 0:

190
serial.cc
Wyświetl plik

@ -1,15 +1,15 @@
// Copyright (c) 2017 Fredrik Öhrström
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -21,10 +21,12 @@
#include"util.h"
#include"serial.h"
#include <algorithm>
#include <fcntl.h>
#include <functional>
#include <memory.h>
#include <pthread.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <stdio.h>
@ -42,13 +44,14 @@ struct SerialCommunicationManagerImp : public SerialCommunicationManager {
void stop();
void waitForStop();
bool isRunning();
void opened(SerialDeviceTTY *sd);
void closed(SerialDeviceTTY *sd);
private:
void *eventLoop();
static void *startLoop(void *);
bool running_;
pthread_t thread_;
int max_fd_;
@ -64,21 +67,21 @@ struct SerialDeviceImp : public SerialDevice {
struct SerialDeviceTTY : public SerialDeviceImp {
SerialDeviceTTY(string device, int baud_rate, SerialCommunicationManagerImp *manager);
bool open();
bool open(bool fail_if_not_ok);
void close();
bool send(vector<uchar> &data);
int receive(vector<uchar> *data);
int fd() { return fd_; }
SerialCommunicationManager *manager() { return manager_; }
private:
string device_;
int baud_rate_;
int fd_;
pthread_mutex_t write_lock_ = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t read_lock_ = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t read_lock_ = PTHREAD_MUTEX_INITIALIZER;
SerialCommunicationManagerImp *manager_;
};
@ -89,23 +92,36 @@ SerialDeviceTTY::SerialDeviceTTY(string device, int baud_rate,
manager_ = manager;
}
bool SerialDeviceTTY::open() {
bool SerialDeviceTTY::open(bool fail_if_not_ok)
{
bool ok = checkCharacterDeviceExists(device_.c_str(), fail_if_not_ok);
if (!ok) return false;
fd_ = openSerialTTY(device_.c_str(), baud_rate_);
if (fd_ == -1) {
error("Could not open %s with %d baud N81\n", device_.c_str(), baud_rate_);
if (fail_if_not_ok) {
error("Could not open %s with %d baud N81\n", device_.c_str(), baud_rate_);
} else {
return false;
}
}
manager_->opened(this);
verbose("(serial) opened %s\n", device_.c_str());
return true;
}
void SerialDeviceTTY::close() {
void SerialDeviceTTY::close()
{
::flock(fd_, LOCK_UN);
::close(fd_);
fd_ = -1;
manager_->closed(this);
verbose("(serial) closed %s\n", device_.c_str());
}
bool SerialDeviceTTY::send(vector<uchar> &data)
{
if (data.size() == 0) return true;
pthread_mutex_lock(&write_lock_);
bool rc = true;
@ -120,11 +136,16 @@ bool SerialDeviceTTY::send(vector<uchar> &data)
goto end;
}
if (written == n) break;
}
}
if (isDebugEnabled()) {
string msg = bin2hex(data);
debug("(serial %s) sent \"%s\"\n", device_.c_str(), msg.c_str());
}
end:
pthread_mutex_unlock(&write_lock_);
return rc;
return rc;
}
int SerialDeviceTTY::receive(vector<uchar> *data)
@ -139,7 +160,7 @@ int SerialDeviceTTY::receive(vector<uchar> *data)
if (!available) goto end;
data->resize(available);
while (true) {
int nr = read(fd_, &((*data)[num_read]), available-num_read);
if (nr > 0) num_read += nr;
@ -148,11 +169,15 @@ int SerialDeviceTTY::receive(vector<uchar> *data)
goto end;
}
if (num_read == available) break;
}
}
if (isDebugEnabled()) {
string msg = bin2hex(*data);
debug("(serial %s) received \"%s\"\n", device_.c_str(), msg.c_str());
}
end:
pthread_mutex_unlock(&read_lock_);
return num_read;
return num_read;
}
SerialCommunicationManagerImp::SerialCommunicationManagerImp()
@ -165,8 +190,8 @@ SerialCommunicationManagerImp::SerialCommunicationManagerImp()
void *SerialCommunicationManagerImp::startLoop(void *a) {
auto t = (SerialCommunicationManagerImp*)a;
return t->eventLoop();
}
return t->eventLoop();
}
SerialDevice *SerialCommunicationManagerImp::createSerialDeviceTTY(string device, int baud_rate) {
SerialDevice *sd = new SerialDeviceTTY(device, baud_rate, this);
@ -188,6 +213,9 @@ void SerialCommunicationManagerImp::waitForStop()
while (running_) { usleep(1000*1000);}
pthread_kill(thread_, SIGUSR1);
pthread_join(thread_, NULL);
for (SerialDevice *d : devices_) {
d->close();
}
}
bool SerialCommunicationManagerImp::isRunning()
@ -201,22 +229,35 @@ void SerialCommunicationManagerImp::opened(SerialDeviceTTY *sd) {
pthread_kill(thread_, SIGUSR1);
}
void SerialCommunicationManagerImp::closed(SerialDeviceTTY *sd) {
auto p = find(devices_.begin(), devices_.end(), sd);
if (p != devices_.end()) {
devices_.erase(p);
}
max_fd_ = 0;
for (SerialDevice *d : devices_) {
if (d->fd() > max_fd_) {
max_fd_ = d->fd();
}
}
}
void *SerialCommunicationManagerImp::eventLoop() {
fd_set readfds;
while (running_) {
FD_ZERO(&readfds);
for (SerialDevice *d : devices_) {
for (SerialDevice *d : devices_) {
FD_SET(d->fd(), &readfds);
}
int activity = select(max_fd_+1 , &readfds , NULL , NULL , NULL);
if (!running_) break;
if (activity < 0 && errno!=EINTR) {
error("Error after select! errno=%d\n", errno);
error("(serial) internal error after select! errno=%d\n", errno);
}
if (activity > 0) {
for (SerialDevice *d : devices_) {
for (SerialDevice *d : devices_) {
if (FD_ISSET(d->fd(), &readfds)) {
SerialDeviceImp *si = dynamic_cast<SerialDeviceImp*>(d);
if (si->on_data_) si->on_data_();
@ -224,7 +265,7 @@ void *SerialCommunicationManagerImp::eventLoop() {
}
}
}
verbose("Event loop stopped!\n");
verbose("(serial) event loop stopped!\n");
return NULL;
}
@ -233,57 +274,64 @@ SerialCommunicationManager *createSerialCommunicationManager()
return new SerialCommunicationManagerImp();
}
static int openSerialTTY(const char *tty, int baud_rate)
{
int rc = 0;
speed_t speed = 0;
struct termios tios;
static int openSerialTTY(const char *tty, int baud_rate) {
int rc = 0;
speed_t speed = 0;
struct termios tios;
int fd = open(tty, O_RDWR | O_NOCTTY | O_NDELAY | O_EXCL);
if (fd == -1) {
usleep(1000*1000);
fd = open(tty, O_RDWR | O_NOCTTY | O_NDELAY | O_EXCL);
if (fd == -1) goto err;
}
int fd = open(tty, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) {
usleep(1000*1000);
fd = open(tty, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) goto err;
}
rc = flock(fd, LOCK_EX | LOCK_NB);
if (rc == -1) {
// It is already locked by another wmbusmeter process.
warning("Device %s is already in use and locked.\n", tty);
goto err;
}
switch (baud_rate) {
case 9600: speed = B9600; break;
case 19200: speed = B19200; break;
case 38400: speed = B38400; break;
case 57600: speed = B57600; break;
case 115200: speed = B115200;break;
default:
goto err;
}
memset(&tios, 0, sizeof(tios));
switch (baud_rate) {
case 9600: speed = B9600; break;
case 19200: speed = B19200; break;
case 38400: speed = B38400; break;
case 57600: speed = B57600; break;
case 115200: speed = B115200;break;
default:
goto err;
}
rc = cfsetispeed(&tios, speed);
if (rc < 0) goto err;
rc = cfsetospeed(&tios, speed);
if (rc < 0) goto err;
memset(&tios, 0, sizeof(tios));
rc = cfsetispeed(&tios, speed);
if (rc < 0) goto err;
rc = cfsetospeed(&tios, speed);
if (rc < 0) goto err;
tios.c_cflag |= (CREAD | CLOCAL);
tios.c_cflag &= ~CSIZE;
tios.c_cflag |= CS8;
tios.c_cflag &=~ CSTOPB;
tios.c_cflag &=~ PARENB;
tios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
tios.c_iflag &= ~INPCK;
tios.c_iflag &= ~(IXON | IXOFF | IXANY);
tios.c_oflag &=~ OPOST;
tios.c_cc[VMIN] = 0;
tios.c_cc[VTIME] = 0;
rc = tcsetattr(fd, TCSANOW, &tios);
if (rc < 0) goto err;
tios.c_cflag |= (CREAD | CLOCAL);
tios.c_cflag &= ~CSIZE;
tios.c_cflag |= CS8;
tios.c_cflag &=~ CSTOPB;
tios.c_cflag &=~ PARENB;
return fd;
tios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
tios.c_iflag &= ~INPCK;
err:
if (fd != -1) close(fd);
return -1;
tios.c_iflag &= ~(IXON | IXOFF | IXANY);
tios.c_oflag &=~ OPOST;
tios.c_cc[VMIN] = 0;
tios.c_cc[VTIME] = 0;
rc = tcsetattr(fd, TCSANOW, &tios);
if (rc < 0) goto err;
return fd;
err:
if (fd != -1) close(fd);
return -1;
}

Wyświetl plik

@ -1,15 +1,15 @@
// Copyright (c) 2017 Fredrik Öhrström
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -32,7 +32,7 @@ using namespace std;
struct SerialCommunicationManager;
struct SerialDevice {
virtual bool open() = 0;
virtual bool open(bool fail_if_not_ok) = 0;
virtual void close() = 0;
virtual bool send(vector<uchar> &data) = 0;
virtual int receive(vector<uchar> *data) = 0;

77
util.cc
Wyświetl plik

@ -1,15 +1,15 @@
// Copyright (c) 2017 Fredrik Öhrström
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -25,6 +25,7 @@
#include<stddef.h>
#include<string.h>
#include<string>
#include<sys/stat.h>
using namespace std;
@ -42,7 +43,7 @@ void onExit(function<void()> cb)
{
exit_handler = cb;
struct sigaction new_action, old_action;
new_action.sa_handler = exitHandler;
sigemptyset (&new_action.sa_mask);
new_action.sa_flags = 0;
@ -117,12 +118,40 @@ void error(const char* fmt, ...) {
exit(1);
}
bool warning_enabled_ = true;
bool verbose_enabled_ = false;
bool debug_enabled_ = false;
void warningSilenced(bool b) {
warning_enabled_ = !b;
}
void verboseEnabled(bool b) {
verbose_enabled_ = b;
}
void debugEnabled(bool b) {
debug_enabled_ = b;
if (debug_enabled_) verbose_enabled_ = true;
}
bool isVerboseEnabled() {
return verbose_enabled_;
}
bool isDebugEnabled() {
return debug_enabled_;
}
void warning(const char* fmt, ...) {
if (warning_enabled_) {
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
}
}
void verbose(const char* fmt, ...) {
if (verbose_enabled_) {
va_list args;
@ -132,6 +161,15 @@ void verbose(const char* fmt, ...) {
}
}
void debug(const char* fmt, ...) {
if (debug_enabled_) {
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
}
}
bool isValidId(char *id)
{
if (strlen(id) == 0) return true;
@ -163,3 +201,34 @@ void incrementIV(uchar *iv, size_t len) {
p--;
}
}
bool checkCharacterDeviceExists(const char *tty, bool fail_if_not)
{
struct stat info;
int rc = stat(tty, &info);
if (rc != 0) {
if (fail_if_not) {
error("Device %s does not exist.\n", tty);
} else {
return false;
}
}
if (!S_ISCHR(info.st_mode)) {
if (fail_if_not) {
error("Device %s is not a character device.\n", tty);
} else {
return false;
}
}
return true;
}
void debugPayload(string intro, vector<uchar> &payload)
{
if (isDebugEnabled())
{
string msg = bin2hex(payload);
debug("%s payload \"%s\"\n", intro.c_str(), msg.c_str());
}
}

19
util.h
Wyświetl plik

@ -1,15 +1,15 @@
// Copyright (c) 2017 Fredrik Öhrström
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -40,13 +40,24 @@ void xorit(uchar *srca, uchar *srcb, uchar *dest, int len);
void error(const char* fmt, ...);
void verbose(const char* fmt, ...);
void debug(const char* fmt, ...);
void warning(const char* fmt, ...);
void warningSilenced(bool b);
void verboseEnabled(bool b);
void debugEnabled(bool b);
bool isVerboseEnabled();
bool isDebugEnabled();
void debugPayload(std::string intro, std::vector<uchar> &payload);
bool isValidId(char *id);
bool isValidKey(char *key);
void incrementIV(uchar *iv, size_t len);
#endif
bool checkCharacterDeviceExists(const char *tty, bool fail_if_not);
#endif

Wyświetl plik

@ -1,15 +1,15 @@
// Copyright (c) 2017 Fredrik Öhrström
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -19,6 +19,7 @@
// SOFTWARE.
#include"wmbus.h"
#include<unistd.h>
const char *LinkModeNames[] = {
#define X(name) #name ,
@ -53,12 +54,26 @@ Initializer::Initializer() {
}
void Telegram::print() {
printf("Received telegram from id: %02x%02x%02x%02x\n",
printf("Received telegram from: %02x%02x%02x%02x\n",
a_field_address[0], a_field_address[1], a_field_address[2], a_field_address[3]);
printf(" manufacturer: %s\n",
printf(" manufacturer: (%s) %s\n",
manufacturerFlag(m_field).c_str(),
manufacturer(m_field).c_str());
printf(" device type: %s\n",
deviceType(m_field, a_field_device_type).c_str());
printf(" device type: %s\n",
deviceType(m_field, a_field_device_type).c_str());
}
void Telegram::verboseFields() {
string man = manufacturerFlag(m_field);
verbose(" %02x%02x%02x%02x C-field=%02x M-field=%04x (%s) A-field-version=%02x A-field-dev-type=%02x (%s) Ci-field=%02x\n",
a_field_address[0], a_field_address[1], a_field_address[2], a_field_address[3],
c_field,
m_field,
man.c_str(),
a_field_version,
a_field_device_type,
deviceType(m_field, a_field_device_type).c_str(),
ci_field);
}
string manufacturer(int m_field) {
@ -68,6 +83,18 @@ string manufacturer(int m_field) {
return "Unknown";
}
string manufacturerFlag(int m_field) {
char a = (m_field/1024)%32+64;
char b = (m_field/32)%32+64;
char c = (m_field)%32+64;
string flag;
flag += a;
flag += b;
flag += c;
return flag;
}
string deviceType(int m_field, int a_field_device_type) {
switch (a_field_device_type) {
case 0: return "Other";
@ -95,3 +122,50 @@ string deviceType(int m_field, int a_field_device_type) {
return "Unknown";
}
bool detectIM871A(string device, SerialCommunicationManager *handler);
bool detectAMB8465(string device, SerialCommunicationManager *handler);
pair<MBusDeviceType,string> detectMBusDevice(string device, SerialCommunicationManager *handler)
{
// If auto, then assume that uev has been configured with
// with the file: `/etc/udev/rules.d/99-usb-serial.rules` containing
// SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="im871a",MODE="0660", GROUP="yourowngroup"
// SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="amb8465",MODE="0660", GROUP="yourowngroup"
if (device == "auto")
{
if (detectIM871A("/dev/im871a", handler))
{
return { DEVICE_IM871A, "/dev/im871a" };
}
if (detectAMB8465("/dev/amb8465", handler))
{
return { DEVICE_AMB8465, "/dev/amb8465" };
}
return { DEVICE_UNKNOWN, "" };
}
// If not auto, then test the device, is it a character device?
checkCharacterDeviceExists(device.c_str(), true);
// If im87a is tested first, a delay of 1s must be inserted
// before amb8465 is tested, lest it will not respond properly.
// It really should not matter, but perhaps is the uart of the amber
// confused by the 57600 speed....or maybe there is some other reason.
// Anyway by testing for the amb8465 first, we can immediately continue
// with the test for the im871a, without the need for a 1s delay.
// Talk amb8465 with it...
// assumes this device is configured for 9600 bps, which seems to be the default.
if (detectAMB8465(device, handler))
{
return { DEVICE_AMB8465, device };
}
// Talk im871a with it...
// assumes this device is configured for 57600 bps, which seems to be the default.
if (detectIM871A(device, handler))
{
return { DEVICE_IM871A, device };
}
return { DEVICE_UNKNOWN, "" };
}

30
wmbus.h
Wyświetl plik

@ -1,15 +1,15 @@
// Copyright (c) 2017 Fredrik Öhrström
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -27,8 +27,7 @@
#include<inttypes.h>
#define LIST_OF_LINK_MODES X(S1)X(S1m)X(S2)X(T1)X(T2)X(R2)X(C1a)X(C1b)X(C2a)X(C2b)\
X(N1A)X(N2A)X(N1B)X(N2B)X(N1C)X(N2C)X(N1D)X(N2D)X(N1E)X(N2E)X(N1F)X(N2F)X(UNKNOWN_LINKMODE)
#define LIST_OF_LINK_MODES X(LinkModeC1)X(UNKNOWN_LINKMODE)
enum LinkMode {
#define X(name) name,
@ -49,13 +48,13 @@ LIST_OF_LINK_MODES
using namespace std;
extern const char *LinkModeNames[];
//extern const char *LinkModeNames[];
struct Telegram {
int c_field; // 1 byte (0x44=telegram, no response expected!)
int m_field; // Manufacturer 2 bytes
vector<uchar> a_field; // A field 6 bytes
// The 6 a field bytes are composed of:
// The 6 a field bytes are composed of:
vector<uchar> a_field_address; // Address in BCD = 8 decimal 00000000...99999999 digits.
int a_field_version; // 1 byte
int a_field_device_type; // 1 byte
@ -67,6 +66,7 @@ struct Telegram {
string id() { return bin2hex(a_field_address); }
void print();
void verboseFields();
};
struct WMBus {
@ -78,9 +78,23 @@ struct WMBus {
virtual SerialDevice *serial() = 0;
};
WMBus *openIM871A(string device, SerialCommunicationManager *handler);
#define LIST_OF_MBUS_DEVICES X(DEVICE_IM871A)X(DEVICE_AMB8465)X(DEVICE_UNKNOWN)
enum MBusDeviceType {
#define X(name) name,
LIST_OF_MBUS_DEVICES
#undef X
};
// The detect function can be supplied the device "auto" and will try default locations for the device.
// Returned is the type and the found device string.
pair<MBusDeviceType,string> detectMBusDevice(string device, SerialCommunicationManager *manager);
WMBus *openIM871A(string device, SerialCommunicationManager *manager);
WMBus *openAMB8465(string device, SerialCommunicationManager *manager);
string manufacturer(int m_field);
string manufacturerFlag(int m_field);
string deviceType(int a_field, int );
#endif

406
wmbus_amb8465.cc 100644
Wyświetl plik

@ -0,0 +1,406 @@
// Copyright (c) 2018 Fredrik Öhrström
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include"wmbus.h"
#include"wmbus_amb8465.h"
#include"serial.h"
#include<assert.h>
#include<pthread.h>
#include<semaphore.h>
#include <unistd.h>
using namespace std;
enum FrameStatus { PartialFrame, FullFrame, ErrorInFrame };
struct WMBusAmber : public WMBus {
bool ping();
uint32_t getDeviceId();
LinkMode getLinkMode();
void setLinkMode(LinkMode lm);
void onTelegram(function<void(Telegram*)> cb);
void processSerialData();
void getConfiguration();
SerialDevice *serial() { return serial_; }
WMBusAmber(SerialDevice *serial, SerialCommunicationManager *manager);
private:
SerialDevice *serial_;
SerialCommunicationManager *manager_;
vector<uchar> read_buffer_;
pthread_mutex_t command_lock_ = PTHREAD_MUTEX_INITIALIZER;
sem_t command_wait_;
int sent_command_;
int received_command_;
LinkMode link_mode_ = UNKNOWN_LINKMODE;
vector<uchar> received_payload_;
vector<function<void(Telegram*)>> telegram_listeners_;
void waitForResponse();
FrameStatus checkAMB8465Frame(vector<uchar> &data,
size_t *frame_length, int *msgid_out, int *payload_len_out, int *payload_offset);
void handleMessage(int msgid, vector<uchar> &payload);
};
WMBus *openAMB8465(string device, SerialCommunicationManager *manager)
{
SerialDevice *serial = manager->createSerialDeviceTTY(device.c_str(), 9600);
WMBusAmber *imp = new WMBusAmber(serial, manager);
return imp;
}
WMBusAmber::WMBusAmber(SerialDevice *serial, SerialCommunicationManager *manager) :
serial_(serial), manager_(manager)
{
sem_init(&command_wait_, 0, 0);
manager_->listenTo(serial_,call(this,processSerialData));
serial_->open(true);
}
uchar xorChecksum(vector<uchar> msg, int len) {
uchar c = 0;
for (int i=0; i<len; ++i) {
c ^= msg[i];
}
return c;
}
bool WMBusAmber::ping() {
pthread_mutex_lock(&command_lock_);
/*
vector<uchar> msg(4);
msg[0] = AMBER_SERIAL_SOF;
msg[1] = DEVMGMT_ID;
msg[2] = DEVMGMT_MSG_PING_REQ;
msg[3] = 0;
sent_command_ = DEVMGMT_MSG_PING_REQ;
serial()->send(msg);
waitForResponse();
*/
pthread_mutex_unlock(&command_lock_);
return true;
}
uint32_t WMBusAmber::getDeviceId() {
pthread_mutex_lock(&command_lock_);
vector<uchar> msg(4);
msg[0] = AMBER_SERIAL_SOF;
msg[1] = CMD_SERIALNO_REQ;
msg[2] = 0; // No payload
msg[3] = xorChecksum(msg, 3);
assert(msg[3] == 0xf4);
sent_command_ = CMD_SERIALNO_REQ;
verbose("(amb8465) get device id\n");
serial()->send(msg);
waitForResponse();
uint32_t id = 0;
if (received_command_ == (CMD_SERIALNO_REQ | 0x80)) {
id = received_payload_[4] << 24 |
received_payload_[5] << 16 |
received_payload_[6] << 8 |
received_payload_[7];
verbose("(amb8465) device id %08x\n", id);
}
pthread_mutex_unlock(&command_lock_);
return id;
}
LinkMode WMBusAmber::getLinkMode() {
// It is not possible to read the volatile mode set using setLinkMode below.
// (It is possible to read the non-volatile settings, but this software
// does not change those.) So we remember the state for the device.
getConfiguration();
return link_mode_;
}
void WMBusAmber::getConfiguration()
{
pthread_mutex_lock(&command_lock_);
vector<uchar> msg(6);
msg[0] = AMBER_SERIAL_SOF;
msg[1] = CMD_GET_REQ;
msg[2] = 0x02;
msg[3] = 0x00;
msg[4] = 0x80;
msg[5] = xorChecksum(msg, 5);
assert(msg[5] == 0x77);
verbose("(amb8465) get config\n");
serial()->send(msg);
waitForResponse();
if (received_command_ == (0x80|CMD_GET_REQ))
{
// These are the non-volatile values stored inside the dongle.
// However the link mode, radio channel etc might not be the one
// that we are actually using! Since setting the link mode
// is possible without changing the non-volatile memory.
// But there seems to be no way of reading out the set link mode....???
// Ie there is a disconnect between the flash and the actual running dongle.
// Oh well.
//
// These are just some random config settings store in non-volatile memory.
verbose("(amb8465) config: uart %02x\n", received_payload_[2]);
verbose("(amb8465) config: radio Channel %02x\n", received_payload_[60+2]);
verbose("(amb8465) config: rssi enabled %02x\n", received_payload_[69+2]);
verbose("(amb8465) config: mode Preselect %02x\n", received_payload_[70+2]);
}
pthread_mutex_unlock(&command_lock_);
}
void WMBusAmber::setLinkMode(LinkMode lm) {
if (lm != LinkModeC1) {
error("LinkMode %d is not implemented\n", (int)lm);
}
pthread_mutex_lock(&command_lock_);
vector<uchar> msg(8);
msg[0] = AMBER_SERIAL_SOF;
msg[1] = CMD_SET_MODE_REQ;
sent_command_ = msg[1];
msg[2] = 1; // Len
msg[3] = 0x0E; // Reception of C1 and C2 messages
msg[4] = xorChecksum(msg, 4);
verbose("(amb8465) set link mode %02x\n", msg[3]);
serial()->send(msg);
waitForResponse();
link_mode_ = LinkModeC1;
pthread_mutex_unlock(&command_lock_);
}
void WMBusAmber::onTelegram(function<void(Telegram*)> cb) {
telegram_listeners_.push_back(cb);
}
void WMBusAmber::waitForResponse() {
while (manager_->isRunning()) {
int rc = sem_wait(&command_wait_);
if (rc==0) break;
if (rc==-1) {
if (errno==EINTR) continue;
break;
}
}
}
FrameStatus WMBusAmber::checkAMB8465Frame(vector<uchar> &data,
size_t *frame_length, int *msgid_out, int *payload_len_out, int *payload_offset)
{
if (data.size() == 0) return PartialFrame;
int payload_len = 0;
if (data[0] == 0xff) {
// A command response begins with 0xff
*msgid_out = data[1];
payload_len = data[2];
*payload_len_out = payload_len;
*payload_offset = 3;
*frame_length = 3+payload_len+1; // expect device to have checksumbyte at end enabled.
// Check checksum here!
if (data.size() < *frame_length) return PartialFrame;
return FullFrame;
}
// If it is not a 0xff we assume it is a message beginning with a length.
// There might be a different mode where the data is wrapped in 0xff. But for the moment
// this is what I see.
payload_len = data[0];
*msgid_out = 0; // 0 is used to signal
*payload_len_out = payload_len;
*payload_offset = 1;
*frame_length = payload_len+1;
if (data.size() < *frame_length) return PartialFrame;
return FullFrame;
}
void WMBusAmber::processSerialData()
{
vector<uchar> data;
// Receive and accumulated serial data until a full frame has been received.
serial_->receive(&data);
read_buffer_.insert(read_buffer_.end(), data.begin(), data.end());
size_t frame_length;
int msgid;
int payload_len, payload_offset;
FrameStatus status = checkAMB8465Frame(read_buffer_, &frame_length, &msgid, &payload_len, &payload_offset);
if (status == ErrorInFrame) {
verbose("(amb8465) protocol error in message received!\n");
string msg = bin2hex(read_buffer_);
debug("(amb8465) protocol error \"%s\"\n", msg.c_str());
read_buffer_.clear();
} else
if (status == FullFrame) {
vector<uchar> payload;
if (payload_len > 0) {
payload.insert(payload.end(), read_buffer_.begin()+payload_offset, read_buffer_.begin()+payload_offset+payload_len);
}
read_buffer_.erase(read_buffer_.begin(), read_buffer_.begin()+frame_length);
handleMessage(msgid, payload);
}
}
void WMBusAmber::handleMessage(int msgid, vector<uchar> &payload)
{
switch (msgid) {
case (0):
{
Telegram t;
t.c_field = payload[0];
t.m_field = payload[2]<<8 | payload[1];
t.a_field.resize(6);
t.a_field_address.resize(4);
for (int i=0; i<6; ++i) {
t.a_field[i] = payload[3+i];
if (i<4) { t.a_field_address[i] = payload[3+3-i]; }
}
t.a_field_version = payload[3+4];
t.a_field_device_type=payload[3+5];
t.ci_field=payload[9];
t.payload.clear();
// TODO! Figure out why there are 4 extra bytes at the end which are not part
// of the message. Is it wmbus crc bytes that the imst dongle removes but
// the amber dongle allows to be visible? Or some other config specific setting?
t.payload.insert(t.payload.end(), payload.begin()+10, payload.end()-4);
verbose("(amb8465) received telegram");
t.verboseFields();
debugPayload("(amb8465) telegram", t.payload);
for (auto f : telegram_listeners_) {
if (f) f(&t);
}
break;
}
case (0x80|CMD_SET_MODE_REQ):
{
verbose("(amb8465) set link mode completed\n");
received_command_ = msgid;
received_payload_.clear();
received_payload_.insert(received_payload_.end(), payload.begin(), payload.end());
debugPayload("(amb8465) set link mode", received_payload_);
sem_post(&command_wait_);
break;
}
case (0x80|CMD_GET_REQ):
{
verbose("(amb8465) get config completed\n");
received_command_ = msgid;
received_payload_.clear();
received_payload_.insert(received_payload_.end(), payload.begin(), payload.end());
debugPayload("(amb8465) get config", received_payload_);
sem_post(&command_wait_);
break;
}
case (0x80|CMD_SERIALNO_REQ):
{
verbose("(amb8465) get device id completed\n");
received_command_ = msgid;
received_payload_.clear();
received_payload_.insert(received_payload_.end(), payload.begin(), payload.end());
debugPayload("(amb8465) get device id", received_payload_);
sem_post(&command_wait_);
break;
}
default:
verbose("(amb8465) unhandled device message %d\n", msgid);
received_payload_.clear();
received_payload_.insert(received_payload_.end(), payload.begin(), payload.end());
debugPayload("(amb8465) unknown", received_payload_);
}
}
bool detectAMB8465(string device, SerialCommunicationManager *manager)
{
// Talk to the device and expect a very specific answer.
SerialDevice *serial = manager->createSerialDeviceTTY(device.c_str(), 9600);
bool ok = serial->open(false);
if (!ok) return false;
vector<uchar> data;
// First clear out any data in the queue.
serial->receive(&data);
data.clear();
vector<uchar> msg(4);
msg[0] = AMBER_SERIAL_SOF;
msg[1] = CMD_SERIALNO_REQ;
msg[2] = 0; // No payload
msg[3] = xorChecksum(msg, 3);
assert(msg[3] == 0xf4);
verbose("(amb8465) are you there?\n");
serial->send(msg);
// Wait for 100ms so that the USB stick have time to prepare a response.
usleep(1000*100);
serial->receive(&data);
int limit = 0;
while (data.size() > 8 && data[0] != 0xff) {
// Eat bytes until a 0xff appears to get in sync with the proper response.
// Extraneous bytes might be due to a partially read telegram.
data.erase(data.begin());
vector<uchar> more;
serial->receive(&more);
if (more.size() > 0) {
data.insert(data.end(), more.begin(), more.end());
}
if (limit++ > 100) break; // Do not wait too long.
}
serial->close();
string sent = bin2hex(msg);
string recv = bin2hex(data);
if (data.size() < 8 ||
data[0] != 0xff ||
data[1] != (0x80 | msg[1]) ||
data[2] != 0x04 ||
data[7] != xorChecksum(data, 7)) {
return false;
}
return true;
}

22
wmbus_amb8465.h 100644
Wyświetl plik

@ -0,0 +1,22 @@
// Defines documented in the Manual for the AMBER wM-Bus Modules Version 2.7
#define AMBER_SERIAL_SOF 0xFF
#define CMD_DATA_REQ 0x00
#define CMD_DATARETRY_REQ 0x02
#define CMD_DATA_IND 0x03
#define CMD_SET_MODE_REQ 0x04
#define CMD_RESET_REQ 0x05
#define CMD_SET_CHANNEL_REQ 0x06
#define CMD_SET_REQ 0x09
#define CMD_GET_REQ 0x0A
#define CMD_SERIALNO_REQ 0x0B
#define CMD_FWV_REQ 0x0C
#define CMD_RSSI_REQ 0x0D
#define CMD_SETUARTSPEED_REQ 0x10
#define CMD_FACTORYRESET_REQ 0x11
#define CMD_DATA_PRELOAD_REQ 0x30
#define CMD_DATA_CLR_PRELOAD_REQ 0x31
#define CMD_SET_AES_KEY_REQ 0x50
#define CMD_CLR_AES_KEY_REQ 0x51
#define CMD_GET_AES_DEV_REQ 0x52

Wyświetl plik

@ -1,15 +1,15 @@
// Copyright (c) 2017 Fredrik Öhrström
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -25,6 +25,7 @@
#include<assert.h>
#include<pthread.h>
#include<semaphore.h>
#include<unistd.h>
using namespace std;
@ -34,10 +35,10 @@ struct WMBusIM871A : public WMBus {
bool ping();
uint32_t getDeviceId();
LinkMode getLinkMode();
void setLinkMode(LinkMode lm);
void setLinkMode(LinkMode lm);
void onTelegram(function<void(Telegram*)> cb);
void processMessage();
void processSerialData();
SerialDevice *serial() { return serial_; }
WMBusIM871A(SerialDevice *serial, SerialCommunicationManager *manager);
@ -51,10 +52,12 @@ private:
int received_command_;
vector<uchar> received_payload_;
vector<function<void(Telegram*)>> telegram_listeners_;
void waitForResponse();
FrameStatus checkFrame(vector<uchar> &data,
size_t *frame_length, int *endpoint_out, int *msgid_out, int *payload_len_out);
static FrameStatus checkFrame(vector<uchar> &data,
size_t *frame_length, int *endpoint_out, int *msgid_out,
int *payload_len_out, int *payload_offset);
friend bool detectIM871A(string device, SerialCommunicationManager *manager);
void handleDevMgmt(int msgid, vector<uchar> &payload);
void handleRadioLink(int msgid, vector<uchar> &payload);
void handleRadioLinkTest(int msgid, vector<uchar> &payload);
@ -72,75 +75,76 @@ WMBusIM871A::WMBusIM871A(SerialDevice *serial, SerialCommunicationManager *manag
serial_(serial), manager_(manager)
{
sem_init(&command_wait_, 0, 0);
manager_->listenTo(serial_,call(this,processMessage));
serial_->open();
manager_->listenTo(serial_,call(this,processSerialData));
serial_->open(true);
}
bool WMBusIM871A::ping() {
pthread_mutex_lock(&command_lock_);
vector<uchar> msg(4);
msg[0] = WMBUS_SERIAL_SOF;
msg[0] = IM871A_SERIAL_SOF;
msg[1] = DEVMGMT_ID;
msg[2] = DEVMGMT_MSG_PING_REQ;
msg[3] = 0;
sent_command_ = DEVMGMT_MSG_PING_REQ;
verbose("(im871a) ping\n");
serial()->send(msg);
waitForResponse();
pthread_mutex_unlock(&command_lock_);
pthread_mutex_unlock(&command_lock_);
return true;
}
uint32_t WMBusIM871A::getDeviceId() {
pthread_mutex_lock(&command_lock_);
vector<uchar> msg(4);
msg[0] = WMBUS_SERIAL_SOF;
msg[0] = IM871A_SERIAL_SOF;
msg[1] = DEVMGMT_ID;
msg[2] = DEVMGMT_MSG_GET_DEVICEINFO_REQ;
msg[3] = 0;
sent_command_ = DEVMGMT_MSG_GET_DEVICEINFO_REQ;
verbose("(im871a) get device info\n");
serial()->send(msg);
waitForResponse();
uint32_t id = 0;
if (received_command_ == DEVMGMT_MSG_GET_DEVICEINFO_RSP) {
verbose("Module Type %02x\n", received_payload_[0]);
verbose( "Device Mode %02x\n", received_payload_[1]);
verbose( "Firmware version %02x\n", received_payload_[2]);
verbose( "HCI Protocol version %02x\n", received_payload_[3]);
verbose("(im871a) device info: module Type %02x\n", received_payload_[0]);
verbose("(im871a) device info: device Mode %02x\n", received_payload_[1]);
verbose("(im871a) device info: firmware version %02x\n", received_payload_[2]);
verbose("(im871a) device info: hci protocol version %02x\n", received_payload_[3]);
id = received_payload_[4] << 24 |
received_payload_[5] << 16 |
received_payload_[6] << 8 |
received_payload_[7];
verbose( "Device id %08x\n", id);
verbose("(im871a) devince info: id %08x\n", id);
}
pthread_mutex_unlock(&command_lock_);
pthread_mutex_unlock(&command_lock_);
return id;
}
LinkMode WMBusIM871A::getLinkMode() {
pthread_mutex_lock(&command_lock_);
vector<uchar> msg(4);
msg[0] = WMBUS_SERIAL_SOF;
msg[0] = IM871A_SERIAL_SOF;
msg[1] = DEVMGMT_ID;
sent_command_ = msg[2] = DEVMGMT_MSG_GET_CONFIG_REQ;
msg[3] = 0;
verbose("(im871a) get config\n");
serial()->send(msg);
waitForResponse();
LinkMode lm = UNKNOWN_LINKMODE;
if (received_command_ == DEVMGMT_MSG_GET_CONFIG_RSP) {
verbose( "Received config size %zu\n", received_payload_.size());
int iff1 = received_payload_[0];
bool has_device_mode = (iff1&1)==1;
bool has_link_mode = (iff1&2)==2;
@ -153,37 +157,42 @@ LinkMode WMBusIM871A::getLinkMode() {
int offset = 1;
if (has_device_mode) {
verbose( "Device mode %02x\n", received_payload_[offset]);
verbose("(im871a) config: device mode %02x\n", received_payload_[offset]);
offset++;
}
if (has_link_mode) {
verbose( "Link mode %02x\n", received_payload_[offset]);
lm = (LinkMode)(int)received_payload_[offset];
verbose("(im871a) config: link mode %02x\n", received_payload_[offset]);
if (received_payload_[offset] == im871a_C1a) {
lm = LinkModeC1;
}
offset++;
}
if (has_wmbus_c_field) {
verbose( "WMBus C-field %02x\n", received_payload_[offset]);
verbose("(im871a) config: wmbus c-field %02x\n", received_payload_[offset]);
offset++;
}
if (has_wmbus_man_id) {
verbose( "WMBus Manufacture id %02x%02x\n", received_payload_[offset+1], received_payload_[offset+0]);
int flagid = 256*received_payload_[offset+1] +received_payload_[offset+0];
string flag = manufacturerFlag(flagid);
verbose("(im871a) config: wmbus mfg id %02x%02x (%s)\n", received_payload_[offset+1], received_payload_[offset+0],
flag.c_str());
offset+=2;
}
if (has_wmbus_device_id) {
verbose( "WMBus Device id %02x%02x%02x%02x\n", received_payload_[offset+3], received_payload_[offset+2],
verbose("(im871a) config: wmbus device id %02x%02x%02x%02x\n", received_payload_[offset+3], received_payload_[offset+2],
received_payload_[offset+1], received_payload_[offset+0]);
offset+=4;
}
if (has_wmbus_version) {
verbose( "WMBus Version %02x\n", received_payload_[offset]);
verbose("(im871a) config: wmbus version %02x\n", received_payload_[offset]);
offset++;
}
if (has_wmbus_device_type) {
verbose( "WMBus Device Type %02x\n", received_payload_[offset]);
verbose("(im871a) config: wmbus device type %02x\n", received_payload_[offset]);
offset++;
}
if (has_radio_channel) {
verbose( "Radio channel %02x\n", received_payload_[offset]);
verbose("(im871a) config: radio channel %02x\n", received_payload_[offset]);
offset++;
}
int iff2 = received_payload_[offset];
@ -197,61 +206,65 @@ LinkMode WMBusIM871A::getLinkMode() {
bool has_led_control = (iff2&64)==64;
bool has_rtc_control = (iff2&128)==128;
if (has_radio_power_level) {
verbose( "Radio power level %02x\n", received_payload_[offset]);
verbose("(im871a) config: radio power level %02x\n", received_payload_[offset]);
offset++;
}
if (has_radio_data_rate) {
verbose( "Radio data rate %02x\n", received_payload_[offset]);
verbose("(im871a) config: radio data rate %02x\n", received_payload_[offset]);
offset++;
}
if (has_radio_rx_window) {
verbose( "Radio rx window %02x\n", received_payload_[offset]);
verbose("(im871a) config: radio rx window %02x\n", received_payload_[offset]);
offset++;
}
if (has_auto_power_saving) {
verbose( "Auto power saving %02x\n", received_payload_[offset]);
verbose("(im871a) config: auto power saving %02x\n", received_payload_[offset]);
offset++;
}
if (has_auto_rssi_attachment) {
verbose( "Auto RSSI attachment %02x\n", received_payload_[offset]);
verbose("(im871a) config: auto RSSI attachment %02x\n", received_payload_[offset]);
offset++;
}
if (has_auto_rx_timestamp_attachment) {
verbose( "Auto rx timestamp attachment %02x\n", received_payload_[offset]);
verbose("(im871a) config: auto rx timestamp attachment %02x\n", received_payload_[offset]);
offset++;
}
if (has_led_control) {
verbose( "LED control %02x\n", received_payload_[offset]);
verbose("(im871a) config: led control %02x\n", received_payload_[offset]);
offset++;
}
if (has_rtc_control) {
verbose( "RTC control %02x\n", received_payload_[offset]);
verbose("(im871a) config: rtc control %02x\n", received_payload_[offset]);
offset++;
}
}
pthread_mutex_unlock(&command_lock_);
pthread_mutex_unlock(&command_lock_);
return lm;
}
void WMBusIM871A::setLinkMode(LinkMode lm) {
void WMBusIM871A::setLinkMode(LinkMode lm)
{
if (lm != LinkModeC1) {
error("LinkMode %d is not implemented\n", (int)lm);
}
pthread_mutex_lock(&command_lock_);
vector<uchar> msg(8);
msg[0] = WMBUS_SERIAL_SOF;
msg[0] = IM871A_SERIAL_SOF;
msg[1] = DEVMGMT_ID;
sent_command_ = msg[2] = DEVMGMT_MSG_SET_CONFIG_REQ;
msg[3] = 3; // Len
msg[4] = 0; // Temporary
msg[5] = 2; // iff1 bits: Set Radio Mode only
msg[6] = (int)lm; // C1a
msg[6] = (int)im871a_C1a;
msg[7] = 0; // iff2 bits: Set nothing
verbose( "SET CONFIG REQ to USB im871a\n\n");
verbose("(im871a) set link mode %02x\n", msg[6]);
serial()->send(msg);
waitForResponse();
pthread_mutex_unlock(&command_lock_);
pthread_mutex_unlock(&command_lock_);
}
void WMBusIM871A::onTelegram(function<void(Telegram*)> cb) {
@ -270,7 +283,8 @@ void WMBusIM871A::waitForResponse() {
}
FrameStatus WMBusIM871A::checkFrame(vector<uchar> &data,
size_t *frame_length, int *endpoint_out, int *msgid_out, int *payload_len_out)
size_t *frame_length, int *endpoint_out, int *msgid_out,
int *payload_len_out, int *payload_offset)
{
if (data.size() == 0) return PartialFrame;
if (data[0] != 0xa5) return ErrorInFrame;
@ -278,56 +292,64 @@ FrameStatus WMBusIM871A::checkFrame(vector<uchar> &data,
if (ctrlbits & 1) return ErrorInFrame; // Bit 1 is reserved, we do not expect it....
bool has_timestamp = ((ctrlbits&2)==2);
bool has_rssi = ((ctrlbits&4)==4);
bool has_crc16 = ((ctrlbits&8)==8);
bool has_crc16 = ((ctrlbits&8)==8);
int endpoint = data[1] & 0x0f;
if (endpoint != DEVMGMT_ID &&
endpoint != RADIOLINK_ID &&
endpoint != RADIOLINKTEST_ID &&
endpoint != HWTEST_ID) return ErrorInFrame;
*endpoint_out = endpoint;
int msgid = data[2];
if (endpoint == DEVMGMT_ID && (msgid<1 || msgid>0x27)) return ErrorInFrame;
if (endpoint == RADIOLINK_ID && (msgid<1 || msgid>0x05)) return ErrorInFrame;
if (endpoint == RADIOLINKTEST_ID && (msgid<1 || msgid>0x07)) return ErrorInFrame; // Are 5 and 6 disallowed?
if (endpoint == HWTEST_ID && (msgid<1 || msgid>0x02)) return ErrorInFrame;
*msgid_out = msgid;
int payload_len = data[3];
*payload_len_out = payload_len;
*frame_length = 4+payload_len+(has_timestamp?4:0)+(has_rssi?1:0)+(has_crc16?2:0);
*payload_offset = 4;
*frame_length = *payload_offset+payload_len+(has_timestamp?4:0)+(has_rssi?1:0)+(has_crc16?2:0);
if (data.size() < *frame_length) return PartialFrame;
return FullFrame;
}
void WMBusIM871A::processMessage() {
void WMBusIM871A::processSerialData()
{
vector<uchar> data;
// Receive and accumulated serial data until a full frame has been received.
serial_->receive(&data);
read_buffer_.insert(read_buffer_.end(), data.begin(), data.end());
size_t frame_length;
int endpoint;
int msgid;
int payload_len;
int payload_len, payload_offset;
FrameStatus status = checkFrame(read_buffer_, &frame_length, &endpoint, &msgid, &payload_len, &payload_offset);
FrameStatus status = checkFrame(read_buffer_, &frame_length, &endpoint, &msgid, &payload_len);
if (status == ErrorInFrame) {
verbose( "Protocol error in message received from iM871A!\n");
verbose("(im871a) protocol error in message received!\n");
string msg = bin2hex(read_buffer_);
debug("(im871a) protocol error \"%s\"\n", msg.c_str());
read_buffer_.clear();
} else
if (status == FullFrame) {
vector<uchar> payload;
if (payload_len > 0) {
payload.insert(payload.begin(), read_buffer_.begin()+4, read_buffer_.begin()+payload_len);
payload.insert(payload.begin(), read_buffer_.begin()+payload_offset, read_buffer_.begin()+payload_len);
}
read_buffer_.erase(read_buffer_.begin(), read_buffer_.begin()+frame_length);
// We now have a proper message in payload. Let us trigger actions based on it.
// It can be wmbus receiver-dongle messages or wmbus remote meter messages received over the radio.
switch (endpoint) {
case DEVMGMT_ID: handleDevMgmt(msgid, payload); break;
case RADIOLINK_ID: handleRadioLink(msgid, payload); break;
@ -341,34 +363,34 @@ void WMBusIM871A::handleDevMgmt(int msgid, vector<uchar> &payload)
{
switch (msgid) {
case DEVMGMT_MSG_PING_RSP: // 0x02
verbose("Received ping response.\n");
verbose("(im871a) pong\n");
received_command_ = msgid;
sem_post(&command_wait_);
break;
case DEVMGMT_MSG_SET_CONFIG_RSP: // 0x04
verbose("Received set device config response.\n");
verbose("(im871a) set config completed\n");
received_command_ = msgid;
received_payload_.clear();
received_payload_.insert(received_payload_.end(), payload.begin(), payload.end());
sem_post(&command_wait_);
break;
case DEVMGMT_MSG_GET_CONFIG_RSP: // 0x06
verbose("Received get device config response.\n");
verbose("(im871a) get config completed\n");
received_command_ = msgid;
received_payload_.clear();
received_payload_.insert(received_payload_.end(), payload.begin(), payload.end());
sem_post(&command_wait_);
break;
case DEVMGMT_MSG_GET_DEVICEINFO_RSP: // 0x10
verbose("Received device info response.\n");
verbose("(im871a) device info completed\n");
received_command_ = msgid;
received_payload_.clear();
received_payload_.insert(received_payload_.end(), payload.begin(), payload.end());
sem_post(&command_wait_);
break;
default:
verbose("Unhandled device management message %d\n", msgid);
}
verbose("(im871a) Unhandled device management message %d\n", msgid);
}
}
void WMBusIM871A::handleRadioLink(int msgid, vector<uchar> &payload)
@ -377,9 +399,6 @@ void WMBusIM871A::handleRadioLink(int msgid, vector<uchar> &payload)
case RADIOLINK_MSG_WMBUSMSG_IND: // 0x03
{
Telegram t;
verbose("Radiolink received (%zu bytes): ", payload.size());
for (auto c : payload) verbose("%02x ", c);
verbose("\n");
t.c_field = payload[0];
t.m_field = payload[2]<<8 | payload[1];
t.a_field.resize(6);
@ -393,36 +412,74 @@ void WMBusIM871A::handleRadioLink(int msgid, vector<uchar> &payload)
t.ci_field=payload[9];
t.payload.clear();
t.payload.insert(t.payload.end(), payload.begin()+10, payload.end());
verbose("C field: %02x\n", t.c_field);
verbose("M field: %04x\n", t.m_field);
verbose("A field version: %02x\n", t.a_field_version);
verbose("A field dev type: %02x\n", t.a_field_device_type);
verbose("Ci field: %02x\n", t.ci_field);
verbose("(im871a) received telegram ");
t.verboseFields();
debugPayload("(im871a) telegram", t.payload);
for (auto f : telegram_listeners_) {
if (f) f(&t);
}
}
break;
default:
verbose("Unhandled radio link message %d\n", msgid);
}
verbose("(im871a) Unhandled radio link message %d\n", msgid);
}
}
void WMBusIM871A::handleRadioLinkTest(int msgid, vector<uchar> &payload)
{
switch (msgid) {
default:
verbose("Unhandled radio link test message %d\n", msgid);
}
verbose("(im871a) Unhandled radio link test message %d\n", msgid);
}
}
void WMBusIM871A::handleHWTest(int msgid, vector<uchar> &payload)
{
switch (msgid) {
default:
verbose("Unhandled hw test message %d\n", msgid);
}
verbose("(im871a) Unhandled hw test message %d\n", msgid);
}
}
bool detectIM871A(string device, SerialCommunicationManager *manager)
{
// Talk to the device and expect a very specific answer.
SerialDevice *serial = manager->createSerialDeviceTTY(device.c_str(), 57600);
bool ok = serial->open(false);
if (!ok) return false;
vector<uchar> data;
// First clear out any data in the queue.
serial->receive(&data);
data.clear();
vector<uchar> msg(4);
msg[0] = IM871A_SERIAL_SOF;
msg[1] = DEVMGMT_ID;
msg[2] = DEVMGMT_MSG_PING_REQ;
msg[3] = 0;
verbose("(im871a) are you there?\n");
serial->send(msg);
// Wait for 100ms so that the USB stick have time to prepare a response.
usleep(1000*100);
serial->receive(&data);
serial->close();
string sent = bin2hex(msg);
string recv = bin2hex(data);
size_t frame_length;
int endpoint, msgid, payload_len, payload_offset;
FrameStatus status = WMBusIM871A::checkFrame(data,
&frame_length, &endpoint, &msgid,
&payload_len, &payload_offset);
if (status != FullFrame ||
endpoint != 1 ||
msgid != 2)
{
return false;
}
return true;
}

Wyświetl plik

@ -1,7 +1,7 @@
// Definitions and source code imported from WMBus_HCI_Spec_V1_6.pdf
// Found here: https://wireless-solutions.de/products/gateways/wirelessadapter.html
#define WMBUS_SERIAL_SOF 0xA5
#define IM871A_SERIAL_SOF 0xA5
#define DEVMGMT_ID 0x01
#define RADIOLINK_ID 0x02
@ -57,4 +57,11 @@
#define HWTEST_MSG_RADIOTEST_REQ 0x01
#define HWTEST_MSG_RADIOTEST_RSP 0x02
#define LIST_OF_IM871A_LINK_MODES X(S1)X(S1m)X(S2)X(T1)X(T2)X(R2)X(C1a)X(C1b)X(C2a)X(C2b) \
X(N1A)X(N2A)X(N1B)X(N2B)X(N1C)X(N2C)X(N1D)X(N2D)X(N1E)X(N2E)X(N1F)X(N2F)X(UNKNOWN_LINKMODE)
enum LinkModeIM871A {
#define X(name) im871a_##name,
LIST_OF_IM871A_LINK_MODES
#undef X
};