kopia lustrzana https://github.com/weetmuts/wmbusmeters
Added support for the Amber amb8465 wmbus usb dongle.
rodzic
0443c17677
commit
fd88627667
|
@ -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.
|
3
Makefile
3
Makefile
|
@ -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
|
||||
|
|
29
README.md
29
README.md
|
@ -8,8 +8,8 @@ utility meter readings.
|
|||
|Linux G++| [](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:
|
||||
|
||||
|
|
24
cmdline.cc
24
cmdline.cc
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
28
cmdline.h
28
cmdline.h
|
@ -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
65
main.cc
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
190
serial.cc
|
@ -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;
|
||||
}
|
||||
|
|
8
serial.h
8
serial.h
|
@ -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
77
util.cc
|
@ -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
19
util.h
|
@ -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
|
||||
|
|
88
wmbus.cc
88
wmbus.cc
|
@ -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
30
wmbus.h
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
231
wmbus_im871a.cc
231
wmbus_im871a.cc
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
Ładowanie…
Reference in New Issue