kopia lustrzana https://github.com/weetmuts/wmbusmeters
Added --oneshot and Telegram::print
rodzic
547eed7df8
commit
0f9edf9620
37
Makefile
37
Makefile
|
@ -38,6 +38,7 @@ METERS_OBJS:=\
|
||||||
$(BUILD)/main.o \
|
$(BUILD)/main.o \
|
||||||
$(BUILD)/meters.o \
|
$(BUILD)/meters.o \
|
||||||
$(BUILD)/meter_multical21.o \
|
$(BUILD)/meter_multical21.o \
|
||||||
|
$(BUILD)/printer.o \
|
||||||
$(BUILD)/serial.o \
|
$(BUILD)/serial.o \
|
||||||
$(BUILD)/util.o \
|
$(BUILD)/util.o \
|
||||||
$(BUILD)/wmbus.o \
|
$(BUILD)/wmbus.o \
|
||||||
|
@ -51,3 +52,39 @@ $(BUILD)/wmbusmeters: $(METERS_OBJS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f build/* build_arm/* build_debug/* build_arm_debug/* *~
|
rm -f build/* build_arm/* build_debug/* build_arm_debug/* *~
|
||||||
|
|
||||||
|
update_manufacturers:
|
||||||
|
wget http://www.m-bus.de/man.html
|
||||||
|
echo '// Data downloaded from http://www.m-bus.de/man.html' > m.h
|
||||||
|
echo -n '// ' >> m.h
|
||||||
|
date --rfc-3339=date >> m.h
|
||||||
|
echo >> m.h
|
||||||
|
echo '#ifndef MANUFACTURERS_H' >> m.h
|
||||||
|
echo '#define MANUFACTURERS_H' >> m.h
|
||||||
|
echo '#define MANFCODE(a,b,c) ((a-64)*1024+(b-64)*32+(c-64))' >> m.h
|
||||||
|
echo '#define LIST_OF_MANUFACTURERS \' >> m.h
|
||||||
|
cat man.html | tr -d '\r\n' | sed \
|
||||||
|
-e 's/.*<table>//' \
|
||||||
|
-e 's/<\/table>.*//' \
|
||||||
|
-e 's/<tr>/X(/g' \
|
||||||
|
-e 's/<script[^<]*<\/script>//g' \
|
||||||
|
-e 's/<a href=[^>]*>//g' \
|
||||||
|
-e 's/<\/a>//g' \
|
||||||
|
-e 's/<a name[^>]*>//g' \
|
||||||
|
-e 's/<td>/\t/g' \
|
||||||
|
-e 's/<\/td>//g' \
|
||||||
|
-e 's/ä/ä/g' \
|
||||||
|
-e 's/ü/ü/g' \
|
||||||
|
-e 's/ö/ö/g' \
|
||||||
|
-e 's/,/ /g' \
|
||||||
|
-e 's/<\/tr>/)\\\n/g' | \
|
||||||
|
grep -v '<caption>' | tr -s ' ' | tr -s '\t' | tr '\t' '|' > tmpfile
|
||||||
|
cat tmpfile | sed -e "s/X(|\(.\)\(.\)\(.\)/X(\1\2\3|MANFCODE('\1','\2','\3')|/g" | \
|
||||||
|
tr -s '|' ',' >> m.h
|
||||||
|
echo >> m.h
|
||||||
|
cat tmpfile | sed -e "s/X(|\(.\)\(.\)\(.\).*/#define MANUFACTURER_\1\2\3 MANFCODE('\1','\2','\3')/g" \
|
||||||
|
>> m.h
|
||||||
|
echo >> m.h
|
||||||
|
echo '#endif' >> m.h
|
||||||
|
rm tmpfile
|
||||||
|
mv m.h manufacturers.h
|
||||||
|
|
24
README.md
24
README.md
|
@ -9,14 +9,16 @@ Usage: wmbusmeters [--verbose] [--robot] [usbdevice] { [meter_name] [meter_id] [
|
||||||
|
|
||||||
Add more meter triplets to listen to more meters.
|
Add more meter triplets to listen to more meters.
|
||||||
Add --verbose for detailed debug information.
|
Add --verbose for detailed debug information.
|
||||||
--robot for json output.
|
--robot for json output.
|
||||||
--meterfiles to create status files below tmp,
|
--meterfiles to create status files below tmp,
|
||||||
named /tmp/meter_name, containing the latest reading.
|
named /tmp/meter_name, containing the latest reading.
|
||||||
|
--oneshot wait for an update from each meter, then quit.
|
||||||
```
|
```
|
||||||
|
|
||||||
No meter triplets means listen for telegram traffic and print any id heard.
|
No meter triplets means listen for telegram traffic and print any id heard.
|
||||||
|
|
||||||
Builds and runs on GNU/Linux:
|
# Builds and runs on GNU/Linux:
|
||||||
|
|
||||||
```
|
```
|
||||||
make
|
make
|
||||||
./build/wmbusmeters /dev/ttyUSB0 MyTapWater 12345678 00112233445566778899AABBCCDDEEFF
|
./build/wmbusmeters /dev/ttyUSB0 MyTapWater 12345678 00112233445566778899AABBCCDDEEFF
|
||||||
|
@ -44,20 +46,32 @@ Binary generated: `./build_debug/wmbusmeters`
|
||||||
|
|
||||||
Binary generated: `./build_arm_debug/wmbusmeters`
|
Binary generated: `./build_arm_debug/wmbusmeters`
|
||||||
|
|
||||||
|
If the meter does not use encryption of its meter data, then enter an empty key on the command line.
|
||||||
|
(you must enter "")
|
||||||
|
|
||||||
|
`./build/wmbusmeters --robot --meterfiles /dev/ttyUSB0 MyTapWater 12345678 ""`
|
||||||
|
|
||||||
|
# System configuration
|
||||||
|
|
||||||
Add yourself to the dialout group to get access to the newly plugged in im871A USB stick.
|
Add yourself to the dialout group to get access to the newly plugged in im871A USB stick.
|
||||||
Or even better, add this udev rule:
|
Or even better, add this udev rule:
|
||||||
|
|
||||||
Create the file: `/etc/udev/rules.d/99-usb-serial.rules` with the content
|
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}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="im871a",MODE="0660", GROUP="yourowngroup"
|
||||||
```
|
```
|
||||||
This will create a symlink named `/dev/im871a` to the particular USB port that the dongle got assigned.
|
This will create a symlink named `/dev/im871a` to the particular USB port that the dongle got assigned.
|
||||||
|
|
||||||
|
# Limitations
|
||||||
|
|
||||||
Currently only supports the USB stick receiver im871A
|
Currently only supports the USB stick receiver im871A
|
||||||
and the water meter Multical21. The source code is modular
|
and the water meter Multical21. The source code is modular
|
||||||
and it should be relatively straightforward to add
|
and it should be relatively straightforward to add
|
||||||
more receivers and meters.
|
more receivers and meters.
|
||||||
|
|
||||||
Good documents on the wireless mbus protocol:
|
# Good documents on the wireless mbus protocol:
|
||||||
|
|
||||||
|
http://www.m-bus.com/files/w4b21021.pdf
|
||||||
|
|
||||||
https://www.infineon.com/dgdl/TDA5340_AN_WMBus_v1.0.pdf
|
https://www.infineon.com/dgdl/TDA5340_AN_WMBus_v1.0.pdf
|
||||||
|
|
||||||
|
@ -65,6 +79,8 @@ http://fastforward.ag/downloads/docu/FAST_EnergyCam-Protocol-wirelessMBUS.pdf
|
||||||
|
|
||||||
http://www.multical.hu/WiredMBus-water.pdf
|
http://www.multical.hu/WiredMBus-water.pdf
|
||||||
|
|
||||||
|
http://uu.diva-portal.org/smash/get/diva2:847898/FULLTEXT02.pdf
|
||||||
|
|
||||||
The AES source code is copied from:
|
The AES source code is copied from:
|
||||||
|
|
||||||
https://github.com/kokke/tiny-AES128-C
|
https://github.com/kokke/tiny-AES128-C
|
||||||
|
|
|
@ -52,6 +52,11 @@ CommandLine *parseCommandLine(int argc, char **argv) {
|
||||||
i++;
|
i++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!strcmp(argv[i], "--oneshot")) {
|
||||||
|
c->oneshot = true;
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (!strcmp(argv[i], "--")) {
|
if (!strcmp(argv[i], "--")) {
|
||||||
i++;
|
i++;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -18,6 +18,10 @@
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
// SOFTWARE.
|
// SOFTWARE.
|
||||||
|
|
||||||
|
#ifndef CMDLINE_H
|
||||||
|
#define CMDLINE_H
|
||||||
|
|
||||||
|
#include"meters.h"
|
||||||
#include<string.h>
|
#include<string.h>
|
||||||
#include<vector>
|
#include<vector>
|
||||||
|
|
||||||
|
@ -27,7 +31,8 @@ struct MeterInfo {
|
||||||
char *name;
|
char *name;
|
||||||
char *id;
|
char *id;
|
||||||
char *key;
|
char *key;
|
||||||
|
Meter *meter;
|
||||||
|
|
||||||
MeterInfo(char *n, char *i, char *k) {
|
MeterInfo(char *n, char *i, char *k) {
|
||||||
name = n;
|
name = n;
|
||||||
id = i;
|
id = i;
|
||||||
|
@ -40,9 +45,11 @@ struct CommandLine {
|
||||||
bool verbose;
|
bool verbose;
|
||||||
bool meterfiles;
|
bool meterfiles;
|
||||||
bool robot;
|
bool robot;
|
||||||
|
bool oneshot;
|
||||||
char *usb_device;
|
char *usb_device;
|
||||||
vector<MeterInfo> meters;
|
vector<MeterInfo> meters;
|
||||||
};
|
};
|
||||||
|
|
||||||
CommandLine *parseCommandLine(int argc, char **argv);
|
CommandLine *parseCommandLine(int argc, char **argv);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
90
main.cc
90
main.cc
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
#include"cmdline.h"
|
#include"cmdline.h"
|
||||||
#include"meters.h"
|
#include"meters.h"
|
||||||
|
#include"printer.h"
|
||||||
#include"serial.h"
|
#include"serial.h"
|
||||||
#include"util.h"
|
#include"util.h"
|
||||||
#include"wmbus.h"
|
#include"wmbus.h"
|
||||||
|
@ -28,76 +29,21 @@
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
CommandLine *cmdline;
|
void oneshotCheck(CommandLine *cmdline, SerialCommunicationManager *manager, Meter *meter);
|
||||||
|
|
||||||
void printMeter(Meter *meter) {
|
|
||||||
printf("%s\t%s\t% 3.3f m3\t%s\t% 3.3f m3\t%s\n",
|
|
||||||
meter->name().c_str(),
|
|
||||||
meter->id().c_str(),
|
|
||||||
meter->totalWaterConsumption(),
|
|
||||||
meter->datetimeOfUpdateHumanReadable().c_str(),
|
|
||||||
meter->targetWaterConsumption(),
|
|
||||||
meter->statusHumanReadable().c_str());
|
|
||||||
|
|
||||||
// targetWaterConsumption: The total consumption at the start of the previous 30 day period.
|
|
||||||
// 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.
|
|
||||||
}
|
|
||||||
|
|
||||||
#define Q(x,y) "\""#x"\":"#y","
|
|
||||||
#define QS(x,y) "\""#x"\":\""#y"\","
|
|
||||||
#define QSE(x,y) "\""#x"\":\""#y"\""
|
|
||||||
|
|
||||||
void printMeterJSON(Meter *meter) {
|
|
||||||
FILE *output = stdout;
|
|
||||||
|
|
||||||
if (cmdline->meterfiles) {
|
|
||||||
char filename[128];
|
|
||||||
memset(filename, 0, sizeof(filename));
|
|
||||||
snprintf(filename, 127, "/tmp/%s", meter->name().c_str());
|
|
||||||
output = fopen(filename, "w");
|
|
||||||
}
|
|
||||||
fprintf(output, "{"
|
|
||||||
QS(name,%s)
|
|
||||||
QS(id,%s)
|
|
||||||
Q(total_m3,%.3f)
|
|
||||||
Q(target_m3,%.3f)
|
|
||||||
QS(current_status,%s)
|
|
||||||
QS(time_dry,%s)
|
|
||||||
QS(time_reversed,%s)
|
|
||||||
QS(time_leaking,%s)
|
|
||||||
QS(time_bursting,%s)
|
|
||||||
QSE(timestamp,%s)
|
|
||||||
"}\n",
|
|
||||||
meter->name().c_str(),
|
|
||||||
meter->id().c_str(),
|
|
||||||
meter->totalWaterConsumption(),
|
|
||||||
meter->targetWaterConsumption(),
|
|
||||||
meter->status().c_str(), // DRY REVERSED LEAK BURST
|
|
||||||
meter->timeDry().c_str(),
|
|
||||||
meter->timeReversed().c_str(),
|
|
||||||
meter->timeLeaking().c_str(),
|
|
||||||
meter->timeBursting().c_str(),
|
|
||||||
meter->datetimeOfUpdateRobot().c_str());
|
|
||||||
|
|
||||||
if (cmdline->meterfiles) {
|
|
||||||
fclose(output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
cmdline = parseCommandLine(argc, argv);
|
CommandLine *cmdline = parseCommandLine(argc, argv);
|
||||||
|
|
||||||
if (cmdline->need_help) {
|
if (cmdline->need_help) {
|
||||||
printf("wmbusmeters version: " WMBUSMETERS_VERSION "\n");
|
printf("wmbusmeters version: " WMBUSMETERS_VERSION "\n");
|
||||||
printf("Usage: wmbusmeters [--verbose] [--robot] [usbdevice] { [meter_name] [meter_id] [meter_key] }* \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("\nAdd more meter quadruplets to listen to more meters.\n");
|
||||||
printf("Add --verbose for detailed debug information.\n");
|
printf("Add --verbose for detailed debug information.\n");
|
||||||
printf(" --robot for json output.\n");
|
printf(" --robot for json output.\n");
|
||||||
printf(" --meterfiles to create status files below tmp,\n"
|
printf(" --meterfiles to create status files below tmp,\n"
|
||||||
" named /tmp/meter_name, containing the latest reading.\n");
|
" named /tmp/meter_name, containing the latest reading.\n");
|
||||||
|
printf(" --oneshot wait for an update from each meter, then quit.\n");
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,13 +60,15 @@ int main(int argc, char **argv)
|
||||||
|
|
||||||
// We want the data visible in the log file asap!
|
// We want the data visible in the log file asap!
|
||||||
setbuf(stdout, NULL);
|
setbuf(stdout, NULL);
|
||||||
|
|
||||||
|
Printer *output = new Printer(cmdline->robot, cmdline->meterfiles);
|
||||||
|
|
||||||
if (cmdline->meters.size() > 0) {
|
if (cmdline->meters.size() > 0) {
|
||||||
for (auto &m : cmdline->meters) {
|
for (auto &m : cmdline->meters) {
|
||||||
verbose("Configuring meter: \"%s\" \"%s\" \"%s\"\n", m.name, m.id, m.key);
|
verbose("Configuring meter: \"%s\" \"%s\" \"%s\"\n", m.name, m.id, m.key);
|
||||||
Meter *meter=createMultical21(wmbus, m.name, m.id, m.key);
|
m.meter = createMultical21(wmbus, m.name, m.id, m.key);
|
||||||
if (cmdline->robot) meter->onUpdate(printMeterJSON);
|
m.meter->onUpdate(calll(output,print,Meter*));
|
||||||
else meter->onUpdate(printMeter);
|
m.meter->onUpdate([cmdline,manager](Meter*meter) { oneshotCheck(cmdline,manager,meter); });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
printf("No meters configured. Printing id:s of all telegrams heard! \n");
|
printf("No meters configured. Printing id:s of all telegrams heard! \n");
|
||||||
|
@ -129,11 +77,19 @@ int main(int argc, char **argv)
|
||||||
printf(" id is the 8 digits printed on 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(" key is 32 hex digits with the aes key.\n\n");
|
||||||
|
|
||||||
wmbus->onTelegram([](Telegram *t){
|
wmbus->onTelegram([](Telegram *t){t->print();});
|
||||||
printf("Received telegram from id: %02x%02x%02x%02x \n",
|
|
||||||
t->a_field_address[0], t->a_field_address[1], t->a_field_address[2], t->a_field_address[3]);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
// All meters have received at least one update! Stop!
|
||||||
|
manager->stop();
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,230 @@
|
||||||
|
// Data downloaded from http://www.m-bus.de/man.html
|
||||||
|
// 2017-09-02
|
||||||
|
|
||||||
|
#ifndef MANUFACTURERS_H
|
||||||
|
#define MANUFACTURERS_H
|
||||||
|
#define MANFCODE(a,b,c) ((a-64)*1024+(b-64)*32+(c-64))
|
||||||
|
#define LIST_OF_MANUFACTURERS \
|
||||||
|
X(ABB,MANFCODE('A','B','B'),ABB AB P.O. Box 1005 SE-61129 Nyköping Nyköping Sweden )\
|
||||||
|
X(ACE,MANFCODE('A','C','E'),Actaris (Elektrizität))\
|
||||||
|
X(ACG,MANFCODE('A','C','G'),Actaris (Gas))\
|
||||||
|
X(ACW,MANFCODE('A','C','W'),Actaris (Wasser und Wärme))\
|
||||||
|
X(AEG,MANFCODE('A','E','G'),AEG)\
|
||||||
|
X(AEL,MANFCODE('A','E','L'),Kohler Türkei)\
|
||||||
|
X(AEM,MANFCODE('A','E','M'),S.C. AEM S.A. Romania)\
|
||||||
|
X(AMP,MANFCODE('A','M','P'),Ampy Automation Digilog Ltd)\
|
||||||
|
X(AMT,MANFCODE('A','M','T'),Aquametro)\
|
||||||
|
X(APS,MANFCODE('A','P','S'),Apsis Kontrol Sistemleri Türkei)\
|
||||||
|
X(BEC,MANFCODE('B','E','C'),Berg Energiekontrollsysteme GmbH)\
|
||||||
|
X(BER,MANFCODE('B','E','R'),Bernina Electronic AG)\
|
||||||
|
X(BSE,MANFCODE('B','S','E'),Basari Elektronik A.S. Türkei)\
|
||||||
|
X(BST,MANFCODE('B','S','T'),BESTAS Elektronik Optik Türkei)\
|
||||||
|
X(CBI,MANFCODE('C','B','I'),Circuit Breaker Industries Südafrika)\
|
||||||
|
X(CLO,MANFCODE('C','L','O'),Clorius Raab Karcher Energie Service A/S)\
|
||||||
|
X(CON,MANFCODE('C','O','N'),Conlog)\
|
||||||
|
X(CZM,MANFCODE('C','Z','M'),Cazzaniga S.p.A.)\
|
||||||
|
X(DAN,MANFCODE('D','A','N'),Danubia)\
|
||||||
|
X(DFS,MANFCODE('D','F','S'),Danfoss A/S)\
|
||||||
|
X(DME,MANFCODE('D','M','E'),DIEHL Metering Industriestrasse 13 91522 Ansbach Germany )\
|
||||||
|
X(DZG,MANFCODE('D','Z','G'),Deutsche Zählergesellschaft)\
|
||||||
|
X(EDM,MANFCODE('E','D','M'),EDMI Pty.Ltd.)\
|
||||||
|
X(EFE,MANFCODE('E','F','E'),Engelmann Sensor GmbH)\
|
||||||
|
X(EKT,MANFCODE('E','K','T'),PA KVANT J.S. Russland)\
|
||||||
|
X(ELM,MANFCODE('E','L','M'),Elektromed Elektronik Ltd Türkei)\
|
||||||
|
X(ELS,MANFCODE('E','L','S'),ELSTER Produktion GmbH)\
|
||||||
|
X(EMH,MANFCODE('E','M','H'),EMH Elektrizitätszähler GmbH & CO KG)\
|
||||||
|
X(EMU,MANFCODE('E','M','U'),EMU Elektronik AG)\
|
||||||
|
X(EMO,MANFCODE('E','M','O'),Enermet)\
|
||||||
|
X(END,MANFCODE('E','N','D'),ENDYS GmbH)\
|
||||||
|
X(ENP,MANFCODE('E','N','P'),Kiev Polytechnical Scientific Research)\
|
||||||
|
X(ENT,MANFCODE('E','N','T'),ENTES Elektronik Türkei)\
|
||||||
|
X(ERL,MANFCODE('E','R','L'),Erelsan Elektrik ve Elektronik Türkei)\
|
||||||
|
X(ESM,MANFCODE('E','S','M'),Starion Elektrik ve Elektronik Türkei)\
|
||||||
|
X(EUR,MANFCODE('E','U','R'),Eurometers Ltd)\
|
||||||
|
X(EWT,MANFCODE('E','W','T'),Elin Wasserwerkstechnik)\
|
||||||
|
X(FED,MANFCODE('F','E','D'),Federal Elektrik Türkei)\
|
||||||
|
X(FML,MANFCODE('F','M','L'),Siemens Measurements Ltd.( Formerly FML Ltd.))\
|
||||||
|
X(GBJ,MANFCODE('G','B','J'),Grundfoss A/S)\
|
||||||
|
X(GEC,MANFCODE('G','E','C'),GEC Meters Ltd.)\
|
||||||
|
X(GSP,MANFCODE('G','S','P'),Ingenieurbuero Gasperowicz)\
|
||||||
|
X(GWF,MANFCODE('G','W','F'),Gas- u. Wassermessfabrik Luzern)\
|
||||||
|
X(HEG,MANFCODE('H','E','G'),Hamburger Elektronik Gesellschaft)\
|
||||||
|
X(HEL,MANFCODE('H','E','L'),Heliowatt)\
|
||||||
|
X(HTC,MANFCODE('H','T','C'),Horstmann Timers and Controls Ltd.)\
|
||||||
|
X(HYD,MANFCODE('H','Y','D'),Hydrometer GmbH)\
|
||||||
|
X(ICM,MANFCODE('I','C','M'),Intracom Griechenland)\
|
||||||
|
X(IDE,MANFCODE('I','D','E'),IMIT S.p.A.)\
|
||||||
|
X(INV,MANFCODE('I','N','V'),Invensys Metering Systems AG)\
|
||||||
|
X(ISK,MANFCODE('I','S','K'),Iskraemeco Slovenia)\
|
||||||
|
X(IST,MANFCODE('I','S','T'),ista Deutschland (bis 2005 Viterra Energy Services))\
|
||||||
|
X(ITR,MANFCODE('I','T','R'),Itron)\
|
||||||
|
X(IWK,MANFCODE('I','W','K'),IWK Regler und Kompensatoren GmbH)\
|
||||||
|
X(KAM,MANFCODE('K','A','M'),Kamstrup Energie A/S)\
|
||||||
|
X(KHL,MANFCODE('K','H','L'),Kohler Türkei)\
|
||||||
|
X(KKE,MANFCODE('K','K','E'),KK-Electronic A/S)\
|
||||||
|
X(KNX,MANFCODE('K','N','X'),KONNEX-based users (Siemens Regensburg))\
|
||||||
|
X(KRO,MANFCODE('K','R','O'),Kromschröder)\
|
||||||
|
X(KST,MANFCODE('K','S','T'),Kundo SystemTechnik GmbH)\
|
||||||
|
X(LEM,MANFCODE('L','E','M'),LEM HEME Ltd. UK)\
|
||||||
|
X(LGB,MANFCODE('L','G','B'),Landis & Gyr Energy Management (UK) Ltd.)\
|
||||||
|
X(LGD,MANFCODE('L','G','D'),Landis & Gyr Deutschland)\
|
||||||
|
X(LGZ,MANFCODE('L','G','Z'),Landis & Gyr Zug)\
|
||||||
|
X(LHA,MANFCODE('L','H','A'),Atlantic Meters Südafrika)\
|
||||||
|
X(LML,MANFCODE('L','M','L'),LUMEL Polen)\
|
||||||
|
X(LSE,MANFCODE('L','S','E'),Landis & Staefa electronic)\
|
||||||
|
X(LSP,MANFCODE('L','S','P'),Landis & Staefa production)\
|
||||||
|
X(LUG,MANFCODE('L','U','G'),Landis & Staefa)\
|
||||||
|
X(LSZ,MANFCODE('L','S','Z'),Siemens Building Technologies)\
|
||||||
|
X(MAD,MANFCODE('M','A','D'),Maddalena S.r.I. Italien)\
|
||||||
|
X(MEI,MANFCODE('M','E','I'),H. Meinecke AG (jetzt Invensys Metering Systems AG))\
|
||||||
|
X(MKS,MANFCODE('M','K','S'),MAK-SAY Elektrik Elektronik Türkei)\
|
||||||
|
X(MNS,MANFCODE('M','N','S'),MANAS Elektronik Türkei)\
|
||||||
|
X(MPS,MANFCODE('M','P','S'),Multiprocessor Systems Ltd Bulgarien)\
|
||||||
|
X(MTC,MANFCODE('M','T','C'),Metering Technology Corporation USA)\
|
||||||
|
X(NIS,MANFCODE('N','I','S'),Nisko Industries Israel)\
|
||||||
|
X(NMS,MANFCODE('N','M','S'),Nisko Advanced Metering Solutions Israel)\
|
||||||
|
X(NRM,MANFCODE('N','R','M'),Norm Elektronik Türkei)\
|
||||||
|
X(ONR,MANFCODE('O','N','R'),ONUR Elektroteknik Türkei)\
|
||||||
|
X(PAD,MANFCODE('P','A','D'),PadMess GmbH)\
|
||||||
|
X(PMG,MANFCODE('P','M','G'),Spanner-Pollux GmbH (jetzt Invensys Metering Systems AG))\
|
||||||
|
X(PRI,MANFCODE('P','R','I'),Polymeters Response International Ltd.)\
|
||||||
|
X(RAS,MANFCODE('R','A','S'),Hydrometer GmbH)\
|
||||||
|
X(REL,MANFCODE('R','E','L'),Relay GmbH)\
|
||||||
|
X(RKE,MANFCODE('R','K','E'),Raab Karcher ES (jetzt ista Deutschland))\
|
||||||
|
X(SAP,MANFCODE('S','A','P'),Sappel)\
|
||||||
|
X(SCH,MANFCODE('S','C','H'),Schnitzel GmbH)\
|
||||||
|
X(SEN,MANFCODE('S','E','N'),Sensus GmbH)\
|
||||||
|
X(SMC,MANFCODE('S','M','C'), )\
|
||||||
|
X(SME,MANFCODE('S','M','E'),Siame Tunesien)\
|
||||||
|
X(SML,MANFCODE('S','M','L'),Siemens Measurements Ltd.)\
|
||||||
|
X(SIE,MANFCODE('S','I','E'),Siemens AG)\
|
||||||
|
X(SLB,MANFCODE('S','L','B'),Schlumberger Industries Ltd.)\
|
||||||
|
X(SON,MANFCODE('S','O','N'),Sontex SA)\
|
||||||
|
X(SOF,MANFCODE('S','O','F'),softflow.de GmbH)\
|
||||||
|
X(SPL,MANFCODE('S','P','L'),Sappel)\
|
||||||
|
X(SPX,MANFCODE('S','P','X'),Spanner Pollux GmbH (jetzt Invensys Metering Systems AG))\
|
||||||
|
X(SVM,MANFCODE('S','V','M'),AB Svensk Värmemätning SVM)\
|
||||||
|
X(TCH,MANFCODE('T','C','H'),Techem Service AG)\
|
||||||
|
X(TIP,MANFCODE('T','I','P'),TIP Thüringer Industrie Produkte GmbH)\
|
||||||
|
X(UAG,MANFCODE('U','A','G'),Uher)\
|
||||||
|
X(UGI,MANFCODE('U','G','I'),United Gas Industries)\
|
||||||
|
X(VES,MANFCODE('V','E','S'),ista Deutschland (bis 2005 Viterra Energy Services))\
|
||||||
|
X(VPI,MANFCODE('V','P','I'),Van Putten Instruments B.V.)\
|
||||||
|
X(WMO,MANFCODE('W','M','O'),Westermo Teleindustri AB Schweden)\
|
||||||
|
X(YTE,MANFCODE('Y','T','E'),Yuksek Teknoloji Türkei)\
|
||||||
|
X(ZAG,MANFCODE('Z','A','G'),Zellwerg Uster AG)\
|
||||||
|
X(ZAP,MANFCODE('Z','A','P'),Zaptronix)\
|
||||||
|
X(ZIV,MANFCODE('Z','I','V'),ZIV Aplicaciones y Tecnologia S.A.)\
|
||||||
|
|
||||||
|
#define MANUFACTURER_ABB MANFCODE('A','B','B')
|
||||||
|
#define MANUFACTURER_ACE MANFCODE('A','C','E')
|
||||||
|
#define MANUFACTURER_ACG MANFCODE('A','C','G')
|
||||||
|
#define MANUFACTURER_ACW MANFCODE('A','C','W')
|
||||||
|
#define MANUFACTURER_AEG MANFCODE('A','E','G')
|
||||||
|
#define MANUFACTURER_AEL MANFCODE('A','E','L')
|
||||||
|
#define MANUFACTURER_AEM MANFCODE('A','E','M')
|
||||||
|
#define MANUFACTURER_AMP MANFCODE('A','M','P')
|
||||||
|
#define MANUFACTURER_AMT MANFCODE('A','M','T')
|
||||||
|
#define MANUFACTURER_APS MANFCODE('A','P','S')
|
||||||
|
#define MANUFACTURER_BEC MANFCODE('B','E','C')
|
||||||
|
#define MANUFACTURER_BER MANFCODE('B','E','R')
|
||||||
|
#define MANUFACTURER_BSE MANFCODE('B','S','E')
|
||||||
|
#define MANUFACTURER_BST MANFCODE('B','S','T')
|
||||||
|
#define MANUFACTURER_CBI MANFCODE('C','B','I')
|
||||||
|
#define MANUFACTURER_CLO MANFCODE('C','L','O')
|
||||||
|
#define MANUFACTURER_CON MANFCODE('C','O','N')
|
||||||
|
#define MANUFACTURER_CZM MANFCODE('C','Z','M')
|
||||||
|
#define MANUFACTURER_DAN MANFCODE('D','A','N')
|
||||||
|
#define MANUFACTURER_DFS MANFCODE('D','F','S')
|
||||||
|
#define MANUFACTURER_DME MANFCODE('D','M','E')
|
||||||
|
#define MANUFACTURER_DZG MANFCODE('D','Z','G')
|
||||||
|
#define MANUFACTURER_EDM MANFCODE('E','D','M')
|
||||||
|
#define MANUFACTURER_EFE MANFCODE('E','F','E')
|
||||||
|
#define MANUFACTURER_EKT MANFCODE('E','K','T')
|
||||||
|
#define MANUFACTURER_ELM MANFCODE('E','L','M')
|
||||||
|
#define MANUFACTURER_ELS MANFCODE('E','L','S')
|
||||||
|
#define MANUFACTURER_EMH MANFCODE('E','M','H')
|
||||||
|
#define MANUFACTURER_EMU MANFCODE('E','M','U')
|
||||||
|
#define MANUFACTURER_EMO MANFCODE('E','M','O')
|
||||||
|
#define MANUFACTURER_END MANFCODE('E','N','D')
|
||||||
|
#define MANUFACTURER_ENP MANFCODE('E','N','P')
|
||||||
|
#define MANUFACTURER_ENT MANFCODE('E','N','T')
|
||||||
|
#define MANUFACTURER_ERL MANFCODE('E','R','L')
|
||||||
|
#define MANUFACTURER_ESM MANFCODE('E','S','M')
|
||||||
|
#define MANUFACTURER_EUR MANFCODE('E','U','R')
|
||||||
|
#define MANUFACTURER_EWT MANFCODE('E','W','T')
|
||||||
|
#define MANUFACTURER_FED MANFCODE('F','E','D')
|
||||||
|
#define MANUFACTURER_FML MANFCODE('F','M','L')
|
||||||
|
#define MANUFACTURER_GBJ MANFCODE('G','B','J')
|
||||||
|
#define MANUFACTURER_GEC MANFCODE('G','E','C')
|
||||||
|
#define MANUFACTURER_GSP MANFCODE('G','S','P')
|
||||||
|
#define MANUFACTURER_GWF MANFCODE('G','W','F')
|
||||||
|
#define MANUFACTURER_HEG MANFCODE('H','E','G')
|
||||||
|
#define MANUFACTURER_HEL MANFCODE('H','E','L')
|
||||||
|
#define MANUFACTURER_HTC MANFCODE('H','T','C')
|
||||||
|
#define MANUFACTURER_HYD MANFCODE('H','Y','D')
|
||||||
|
#define MANUFACTURER_ICM MANFCODE('I','C','M')
|
||||||
|
#define MANUFACTURER_IDE MANFCODE('I','D','E')
|
||||||
|
#define MANUFACTURER_INV MANFCODE('I','N','V')
|
||||||
|
#define MANUFACTURER_ISK MANFCODE('I','S','K')
|
||||||
|
#define MANUFACTURER_IST MANFCODE('I','S','T')
|
||||||
|
#define MANUFACTURER_ITR MANFCODE('I','T','R')
|
||||||
|
#define MANUFACTURER_IWK MANFCODE('I','W','K')
|
||||||
|
#define MANUFACTURER_KAM MANFCODE('K','A','M')
|
||||||
|
#define MANUFACTURER_KHL MANFCODE('K','H','L')
|
||||||
|
#define MANUFACTURER_KKE MANFCODE('K','K','E')
|
||||||
|
#define MANUFACTURER_KNX MANFCODE('K','N','X')
|
||||||
|
#define MANUFACTURER_KRO MANFCODE('K','R','O')
|
||||||
|
#define MANUFACTURER_KST MANFCODE('K','S','T')
|
||||||
|
#define MANUFACTURER_LEM MANFCODE('L','E','M')
|
||||||
|
#define MANUFACTURER_LGB MANFCODE('L','G','B')
|
||||||
|
#define MANUFACTURER_LGD MANFCODE('L','G','D')
|
||||||
|
#define MANUFACTURER_LGZ MANFCODE('L','G','Z')
|
||||||
|
#define MANUFACTURER_LHA MANFCODE('L','H','A')
|
||||||
|
#define MANUFACTURER_LML MANFCODE('L','M','L')
|
||||||
|
#define MANUFACTURER_LSE MANFCODE('L','S','E')
|
||||||
|
#define MANUFACTURER_LSP MANFCODE('L','S','P')
|
||||||
|
#define MANUFACTURER_LUG MANFCODE('L','U','G')
|
||||||
|
#define MANUFACTURER_LSZ MANFCODE('L','S','Z')
|
||||||
|
#define MANUFACTURER_MAD MANFCODE('M','A','D')
|
||||||
|
#define MANUFACTURER_MEI MANFCODE('M','E','I')
|
||||||
|
#define MANUFACTURER_MKS MANFCODE('M','K','S')
|
||||||
|
#define MANUFACTURER_MNS MANFCODE('M','N','S')
|
||||||
|
#define MANUFACTURER_MPS MANFCODE('M','P','S')
|
||||||
|
#define MANUFACTURER_MTC MANFCODE('M','T','C')
|
||||||
|
#define MANUFACTURER_NIS MANFCODE('N','I','S')
|
||||||
|
#define MANUFACTURER_NMS MANFCODE('N','M','S')
|
||||||
|
#define MANUFACTURER_NRM MANFCODE('N','R','M')
|
||||||
|
#define MANUFACTURER_ONR MANFCODE('O','N','R')
|
||||||
|
#define MANUFACTURER_PAD MANFCODE('P','A','D')
|
||||||
|
#define MANUFACTURER_PMG MANFCODE('P','M','G')
|
||||||
|
#define MANUFACTURER_PRI MANFCODE('P','R','I')
|
||||||
|
#define MANUFACTURER_RAS MANFCODE('R','A','S')
|
||||||
|
#define MANUFACTURER_REL MANFCODE('R','E','L')
|
||||||
|
#define MANUFACTURER_RKE MANFCODE('R','K','E')
|
||||||
|
#define MANUFACTURER_SAP MANFCODE('S','A','P')
|
||||||
|
#define MANUFACTURER_SCH MANFCODE('S','C','H')
|
||||||
|
#define MANUFACTURER_SEN MANFCODE('S','E','N')
|
||||||
|
#define MANUFACTURER_SMC MANFCODE('S','M','C')
|
||||||
|
#define MANUFACTURER_SME MANFCODE('S','M','E')
|
||||||
|
#define MANUFACTURER_SML MANFCODE('S','M','L')
|
||||||
|
#define MANUFACTURER_SIE MANFCODE('S','I','E')
|
||||||
|
#define MANUFACTURER_SLB MANFCODE('S','L','B')
|
||||||
|
#define MANUFACTURER_SON MANFCODE('S','O','N')
|
||||||
|
#define MANUFACTURER_SOF MANFCODE('S','O','F')
|
||||||
|
#define MANUFACTURER_SPL MANFCODE('S','P','L')
|
||||||
|
#define MANUFACTURER_SPX MANFCODE('S','P','X')
|
||||||
|
#define MANUFACTURER_SVM MANFCODE('S','V','M')
|
||||||
|
#define MANUFACTURER_TCH MANFCODE('T','C','H')
|
||||||
|
#define MANUFACTURER_TIP MANFCODE('T','I','P')
|
||||||
|
#define MANUFACTURER_UAG MANFCODE('U','A','G')
|
||||||
|
#define MANUFACTURER_UGI MANFCODE('U','G','I')
|
||||||
|
#define MANUFACTURER_VES MANFCODE('V','E','S')
|
||||||
|
#define MANUFACTURER_VPI MANFCODE('V','P','I')
|
||||||
|
#define MANUFACTURER_WMO MANFCODE('W','M','O')
|
||||||
|
#define MANUFACTURER_YTE MANFCODE('Y','T','E')
|
||||||
|
#define MANUFACTURER_ZAG MANFCODE('Z','A','G')
|
||||||
|
#define MANUFACTURER_ZAP MANFCODE('Z','A','P')
|
||||||
|
#define MANUFACTURER_ZIV MANFCODE('Z','I','V')
|
||||||
|
|
||||||
|
#endif
|
|
@ -48,8 +48,22 @@ struct MeterMultical21 : public Meter {
|
||||||
|
|
||||||
string id();
|
string id();
|
||||||
string name();
|
string name();
|
||||||
|
// Total water counted through the meter
|
||||||
float totalWaterConsumption();
|
float totalWaterConsumption();
|
||||||
|
bool hasTotalWaterConsumption();
|
||||||
|
|
||||||
|
// Meter sends target water consumption or max flow, depending on meter configuration
|
||||||
|
// 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();
|
float targetWaterConsumption();
|
||||||
|
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.
|
||||||
string statusHumanReadable();
|
string statusHumanReadable();
|
||||||
string status();
|
string status();
|
||||||
string timeDry();
|
string timeDry();
|
||||||
|
@ -60,6 +74,7 @@ struct MeterMultical21 : public Meter {
|
||||||
string datetimeOfUpdateHumanReadable();
|
string datetimeOfUpdateHumanReadable();
|
||||||
string datetimeOfUpdateRobot();
|
string datetimeOfUpdateRobot();
|
||||||
void onUpdate(function<void(Meter*)> cb);
|
void onUpdate(function<void(Meter*)> cb);
|
||||||
|
int numUpdates();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void handleTelegram(Telegram*t);
|
void handleTelegram(Telegram*t);
|
||||||
|
@ -68,22 +83,40 @@ private:
|
||||||
|
|
||||||
int info_codes_;
|
int info_codes_;
|
||||||
float total_water_consumption_;
|
float total_water_consumption_;
|
||||||
float target_volume_;
|
bool has_total_water_consumption_;
|
||||||
|
float target_volume_;
|
||||||
|
bool has_target_volume_;
|
||||||
|
float max_flow_;
|
||||||
|
bool has_max_flow_;
|
||||||
|
|
||||||
time_t datetime_of_update_;
|
time_t datetime_of_update_;
|
||||||
|
|
||||||
string name_;
|
string name_;
|
||||||
vector<uchar> id_;
|
vector<uchar> id_;
|
||||||
vector<uchar> key_;
|
vector<uchar> key_;
|
||||||
WMBus *bus_;
|
WMBus *bus_;
|
||||||
function<void(Meter*)> on_update_;
|
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) :
|
MeterMultical21::MeterMultical21(WMBus *bus, const char *name, const char *id, const char *key) :
|
||||||
info_codes_(0), total_water_consumption_(0), target_volume_(0), name_(name), bus_(bus)
|
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)
|
||||||
{
|
{
|
||||||
hex2bin(id, &id_);
|
if (strlen(id) == 0) {
|
||||||
hex2bin(key, &key_);
|
matches_any_id_ = true;
|
||||||
|
} else {
|
||||||
|
hex2bin(id, &id_);
|
||||||
|
}
|
||||||
|
if (strlen(key) == 0) {
|
||||||
|
use_aes_ = false;
|
||||||
|
} else {
|
||||||
|
hex2bin(key, &key_);
|
||||||
|
}
|
||||||
bus_->onTelegram(calll(this,handleTelegram,Telegram*));
|
bus_->onTelegram(calll(this,handleTelegram,Telegram*));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +132,12 @@ string MeterMultical21::name()
|
||||||
|
|
||||||
void MeterMultical21::onUpdate(function<void(Meter*)> cb)
|
void MeterMultical21::onUpdate(function<void(Meter*)> cb)
|
||||||
{
|
{
|
||||||
on_update_ = cb;
|
on_update_.push_back(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
int MeterMultical21::numUpdates()
|
||||||
|
{
|
||||||
|
return num_updates_;
|
||||||
}
|
}
|
||||||
|
|
||||||
float MeterMultical21::totalWaterConsumption()
|
float MeterMultical21::totalWaterConsumption()
|
||||||
|
@ -107,11 +145,31 @@ float MeterMultical21::totalWaterConsumption()
|
||||||
return total_water_consumption_;
|
return total_water_consumption_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MeterMultical21::hasTotalWaterConsumption()
|
||||||
|
{
|
||||||
|
return has_total_water_consumption_;
|
||||||
|
}
|
||||||
|
|
||||||
float MeterMultical21::targetWaterConsumption()
|
float MeterMultical21::targetWaterConsumption()
|
||||||
{
|
{
|
||||||
return target_volume_;
|
return target_volume_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MeterMultical21::hasTargetWaterConsumption()
|
||||||
|
{
|
||||||
|
return has_target_volume_;
|
||||||
|
}
|
||||||
|
|
||||||
|
float MeterMultical21::maxFlow()
|
||||||
|
{
|
||||||
|
return max_flow_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MeterMultical21::hasMaxFlow()
|
||||||
|
{
|
||||||
|
return has_max_flow_;
|
||||||
|
}
|
||||||
|
|
||||||
string MeterMultical21::datetimeOfUpdateHumanReadable()
|
string MeterMultical21::datetimeOfUpdateHumanReadable()
|
||||||
{
|
{
|
||||||
char datetime[40];
|
char datetime[40];
|
||||||
|
@ -124,6 +182,7 @@ string MeterMultical21::datetimeOfUpdateRobot()
|
||||||
{
|
{
|
||||||
char datetime[40];
|
char datetime[40];
|
||||||
memset(datetime, 0, sizeof(datetime));
|
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_));
|
strftime(datetime, sizeof(datetime), "%FT%TZ", gmtime(&datetime_of_update_));
|
||||||
return string(datetime);
|
return string(datetime);
|
||||||
}
|
}
|
||||||
|
@ -134,38 +193,59 @@ Meter *createMultical21(WMBus *bus, const char *name, const char *id, const char
|
||||||
|
|
||||||
void MeterMultical21::handleTelegram(Telegram *t) {
|
void MeterMultical21::handleTelegram(Telegram *t) {
|
||||||
|
|
||||||
if (t->a_field_address[3] != id_[3] ||
|
if (matches_any_id_ || (
|
||||||
t->a_field_address[2] != id_[2] ||
|
t->m_field == MANUFACTURER_KAM &&
|
||||||
t->a_field_address[1] != id_[1] ||
|
t->a_field_address[3] == id_[3] &&
|
||||||
t->a_field_address[0] != id_[0]) {
|
t->a_field_address[2] == id_[2] &&
|
||||||
|
t->a_field_address[1] == id_[1] &&
|
||||||
verbose("Meter %s ignores message with id %02x%02x%02x%02x \n",
|
t->a_field_address[0] == id_[0])) {
|
||||||
name_.c_str(),
|
|
||||||
t->a_field_address[0], t->a_field_address[1], t->a_field_address[2], t->a_field_address[3]);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
verbose("Meter %s receives update with id %02x%02x%02x%02x!\n",
|
verbose("Meter %s receives update with id %02x%02x%02x%02x!\n",
|
||||||
name_.c_str(),
|
name_.c_str(),
|
||||||
t->a_field_address[0], t->a_field_address[1], t->a_field_address[2], t->a_field_address[3]);
|
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]);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cc_field = t->payload[0];
|
|
||||||
//int acc = t->payload[1];
|
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
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");
|
||||||
|
|
||||||
|
int acc = t->payload[1];
|
||||||
|
verbose("ACC field=%02x\n", acc);
|
||||||
|
|
||||||
uchar sn[4];
|
uchar sn[4];
|
||||||
sn[0] = t->payload[2];
|
sn[0] = t->payload[2];
|
||||||
sn[1] = t->payload[3];
|
sn[1] = t->payload[3];
|
||||||
sn[2] = t->payload[4];
|
sn[2] = t->payload[4];
|
||||||
sn[3] = t->payload[5];
|
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");
|
||||||
|
else verbose("? %d\n", sn[3] & SN_ENC_BITS);
|
||||||
|
|
||||||
|
// The content begins with the Payload CRC at offset 6.
|
||||||
vector<uchar> content;
|
vector<uchar> content;
|
||||||
content.insert(content.end(), t->payload.begin()+6, t->payload.end());
|
content.insert(content.end(), t->payload.begin()+6, t->payload.end());
|
||||||
while (content.size() < 16) { content.push_back(0); }
|
size_t remaining = content.size();
|
||||||
|
if (remaining > 16) remaining = 16;
|
||||||
|
|
||||||
uchar iv[16];
|
uchar iv[16];
|
||||||
int i=0;
|
int i=0;
|
||||||
// M-field
|
// M-field
|
||||||
iv[i++] = t->m_field>>8; iv[i++] = t->m_field&255;
|
iv[i++] = t->m_field&255; iv[i++] = t->m_field>>8;
|
||||||
// A-field
|
// A-field
|
||||||
for (int j=0; j<6; ++j) { iv[i++] = t->a_field[j]; }
|
for (int j=0; j<6; ++j) { iv[i++] = t->a_field[j]; }
|
||||||
// CC-field
|
// CC-field
|
||||||
|
@ -177,62 +257,85 @@ void MeterMultical21::handleTelegram(Telegram *t) {
|
||||||
// BC
|
// BC
|
||||||
iv[i++] = 0;
|
iv[i++] = 0;
|
||||||
|
|
||||||
vector<uchar> ivv(iv, iv+16);
|
|
||||||
string s = bin2hex(ivv);
|
|
||||||
verbose("IV %s\n", s.c_str());
|
|
||||||
|
|
||||||
uchar xordata[16];
|
if (use_aes_) {
|
||||||
AES_ECB_encrypt(iv, &key_[0], xordata, 16);
|
vector<uchar> ivv(iv, iv+16);
|
||||||
|
verbose("Decrypting\n");
|
||||||
|
|
||||||
uchar decrypt[16];
|
string s = bin2hex(ivv);
|
||||||
xorit(xordata, &content[0], decrypt, 16);
|
verbose("IV %s\n", s.c_str());
|
||||||
|
|
||||||
vector<uchar> dec(decrypt, decrypt+16);
|
uchar xordata[16];
|
||||||
string answer = bin2hex(dec);
|
AES_ECB_encrypt(iv, &key_[0], xordata, 16);
|
||||||
verbose("Decrypted >%s<\n", answer.c_str());
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
if (content.size() > 22) {
|
||||||
|
fprintf(stderr, "Received too many bytes of content from a Multical21 meter!\n"
|
||||||
|
"Got %zu bytes, expected at most 22.\n", content.size());
|
||||||
|
}
|
||||||
|
if (content.size() > 16) {
|
||||||
|
// Yay! Lets decrypt a second block. Full frame content is 22 bytes.
|
||||||
|
// 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());
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
if (content.size() > 22) {
|
// Append the second decrypted block to the first.
|
||||||
fprintf(stderr, "Received too many bytes of content from a Multical21 meter!\n"
|
dec.insert(dec.end(), dec2.begin(), dec2.end());
|
||||||
"Got %zu bytes, expected at most 22.\n", content.size());
|
}
|
||||||
}
|
content.clear();
|
||||||
if (content.size() > 16) {
|
content.insert(content.end(), dec.begin(), dec.end());
|
||||||
// Yay! Lets decrypt a second block. Full frame content is 22 bytes.
|
|
||||||
// So a second block should enough for everyone!
|
|
||||||
size_t 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());
|
|
||||||
|
|
||||||
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());
|
|
||||||
|
|
||||||
// Append the second decrypted block to the first.
|
|
||||||
dec.insert(dec.end(), dec2.begin(), dec2.end());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
processContent(dec);
|
processContent(content);
|
||||||
datetime_of_update_ = time(NULL);
|
datetime_of_update_ = time(NULL);
|
||||||
|
|
||||||
if (on_update_) on_update_(this);
|
num_updates_++;
|
||||||
|
for (auto &cb : on_update_) if (cb) cb(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
float getScaleFactor(int vif) {
|
||||||
|
switch (vif) {
|
||||||
|
case 0x13: return 1000.0;
|
||||||
|
case 0x14: return 100.0;
|
||||||
|
case 0x15: return 10.0;
|
||||||
|
case 0x16: return 10.0;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "Warning! Unknown vif code %d for scale factor.\n", vif);
|
||||||
|
return 1000.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeterMultical21::processContent(vector<uchar> &c) {
|
void MeterMultical21::processContent(vector<uchar> &c) {
|
||||||
/* int crc0 = c[0];
|
int crc0 = c[0];
|
||||||
int crc1 = c[1]; */
|
int crc1 = c[1];
|
||||||
int frame_type = c[2];
|
int frame_type = c[2];
|
||||||
|
verbose("CRC16: %02x%02x\n", crc1, crc0);
|
||||||
|
/*
|
||||||
|
uint16_t crc = crc16(&(c[2]), c.size()-2);
|
||||||
|
verbose("CRC16 calc: %04x\n", crc);
|
||||||
|
*/
|
||||||
if (frame_type == 0x79) {
|
if (frame_type == 0x79) {
|
||||||
verbose("Short frame %d bytes\n", c.size());
|
verbose("Short frame %d bytes\n", c.size());
|
||||||
if (c.size() != 16) {
|
if (c.size() != 15) {
|
||||||
fprintf(stderr, "Warning! Unexpected length of frame %zu. Expected 16 bytes!\n", c.size());
|
fprintf(stderr, "Warning! Unexpected length of frame %zu. Expected 15 bytes!\n", c.size());
|
||||||
}
|
}
|
||||||
/*int ecrc0 = c[3];
|
/*int ecrc0 = c[3];
|
||||||
int ecrc1 = c[4];
|
int ecrc1 = c[4];
|
||||||
|
@ -246,24 +349,24 @@ void MeterMultical21::processContent(vector<uchar> &c) {
|
||||||
int rec2val3 = c[12];
|
int rec2val3 = c[12];
|
||||||
int rec3val0 = c[13];
|
int rec3val0 = c[13];
|
||||||
int rec3val1 = c[14];
|
int rec3val1 = c[14];
|
||||||
int rec3val2 = c[15];
|
|
||||||
|
|
||||||
info_codes_ = rec1val1*256+rec1val0;
|
info_codes_ = rec1val1*256+rec1val0;
|
||||||
verbose("short rec1 %02x %02x info codes\n", rec1val1, rec1val0);
|
verbose("short rec1 %02x %02x info codes\n", rec1val1, rec1val0);
|
||||||
|
|
||||||
int consumption_raw = rec2val3*256*256*256 + rec2val2*256*256 + rec2val1*256 + rec2val0;
|
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("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
|
// 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;
|
||||||
|
|
||||||
// The short frame target volume supplies two low bytes,
|
// The short frame target volume supplies two low bytes,
|
||||||
// the remaining two hi bytes are >>probably<< picked from rec2!
|
// the remaining two hi bytes are >>probably<< picked from rec2!
|
||||||
int target_volume_raw = rec2val3*256*256*256 + rec2val2*256*256 + rec3val1*256 + rec3val0;
|
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("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);
|
target_volume_ = ((float)target_volume_raw) / ((float)1000);
|
||||||
|
has_target_volume_ = true;
|
||||||
// Which leaves one unknown byte. Takes on all possible values.
|
|
||||||
verbose("short rec3 %02x = unknown\n", rec3val2);
|
|
||||||
} else
|
} else
|
||||||
if (frame_type == 0x78) {
|
if (frame_type == 0x78) {
|
||||||
verbose("Full frame %d bytes\n", c.size());
|
verbose("Full frame %d bytes\n", c.size());
|
||||||
|
@ -314,7 +417,8 @@ void MeterMultical21::processContent(vector<uchar> &c) {
|
||||||
verbose("full rec2 %02x %02x %02x %02x = %d total consumption\n", rec2val3, rec2val2, rec2val1, rec2val0, consumption_raw);
|
verbose("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
|
// 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) {
|
if (rec3dif != 0x44 || rec3vif != 0x13) {
|
||||||
fprintf(stderr, "Warning unexpecte field! Expected target volume (ie volume recorded on first day of month)\n"
|
fprintf(stderr, "Warning unexpecte 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);
|
"with dif=0x44 vif=0x13 but got dif=%02x vif=%02x\n", rec3dif, rec3vif);
|
||||||
|
@ -324,7 +428,8 @@ void MeterMultical21::processContent(vector<uchar> &c) {
|
||||||
verbose("full rec3 %02x %02x %02x %02x = %d target volume\n", rec3val3, rec3val2, rec3val1, rec3val0, target_volume_raw);
|
verbose("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);
|
target_volume_ = ((float)target_volume_raw) / ((float)1000);
|
||||||
|
has_target_volume_ = true;
|
||||||
|
|
||||||
// To unknown bytes, seems to be very constant.
|
// To unknown bytes, seems to be very constant.
|
||||||
verbose("full rec4 %02x %02x = unknown\n", rec4val1, rec4val0);
|
verbose("full rec4 %02x %02x = unknown\n", rec4val1, rec4val0);
|
||||||
} else {
|
} else {
|
||||||
|
|
7
meters.h
7
meters.h
|
@ -34,8 +34,14 @@ typedef unsigned char uchar;
|
||||||
struct Meter {
|
struct Meter {
|
||||||
virtual string id() = 0;
|
virtual string id() = 0;
|
||||||
virtual string name() = 0;
|
virtual string name() = 0;
|
||||||
|
|
||||||
virtual float totalWaterConsumption() = 0;
|
virtual float totalWaterConsumption() = 0;
|
||||||
|
virtual bool hasTotalWaterConsumption() = 0;
|
||||||
virtual float targetWaterConsumption() = 0;
|
virtual float targetWaterConsumption() = 0;
|
||||||
|
virtual bool hasTargetWaterConsumption() = 0;
|
||||||
|
virtual float maxFlow() = 0;
|
||||||
|
virtual bool hasMaxFlow() = 0;
|
||||||
|
|
||||||
virtual string statusHumanReadable() = 0;
|
virtual string statusHumanReadable() = 0;
|
||||||
virtual string status() = 0;
|
virtual string status() = 0;
|
||||||
virtual string timeDry() = 0;
|
virtual string timeDry() = 0;
|
||||||
|
@ -47,6 +53,7 @@ struct Meter {
|
||||||
virtual string datetimeOfUpdateRobot() = 0;
|
virtual string datetimeOfUpdateRobot() = 0;
|
||||||
|
|
||||||
virtual void onUpdate(function<void(Meter*)> cb) = 0;
|
virtual void onUpdate(function<void(Meter*)> cb) = 0;
|
||||||
|
virtual int numUpdates() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
Meter *createMultical21(WMBus *bus, const char *name, const char *id, const char *key);
|
Meter *createMultical21(WMBus *bus, const char *name, const char *id, const char *key);
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
// 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
|
||||||
|
// 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"printer.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
Printer::Printer(bool robot, bool meterfiles)
|
||||||
|
{
|
||||||
|
robot_ = robot;
|
||||||
|
meterfiles_ = meterfiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Printer::print(Meter *meter)
|
||||||
|
{
|
||||||
|
FILE *output = stdout;
|
||||||
|
|
||||||
|
if (meterfiles_) {
|
||||||
|
char filename[128];
|
||||||
|
memset(filename, 0, sizeof(filename));
|
||||||
|
snprintf(filename, 127, "/tmp/%s", meter->name().c_str());
|
||||||
|
output = fopen(filename, "w");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (robot_) printMeterJSON(output, meter);
|
||||||
|
else printMeterHumanReadable(output, meter);
|
||||||
|
|
||||||
|
if (output != stdout) {
|
||||||
|
fclose(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Printer::printMeterHumanReadable(FILE *output, Meter *meter)
|
||||||
|
{
|
||||||
|
fprintf(output, "%s\t%s\t% 3.3f m3\t%s\t% 3.3f m3\t%s\n",
|
||||||
|
meter->name().c_str(),
|
||||||
|
meter->id().c_str(),
|
||||||
|
meter->totalWaterConsumption(),
|
||||||
|
meter->datetimeOfUpdateHumanReadable().c_str(),
|
||||||
|
meter->targetWaterConsumption(),
|
||||||
|
meter->statusHumanReadable().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#define Q(x,y) "\""#x"\":"#y","
|
||||||
|
#define QS(x,y) "\""#x"\":\""#y"\","
|
||||||
|
#define QSE(x,y) "\""#x"\":\""#y"\""
|
||||||
|
|
||||||
|
void Printer::printMeterJSON(FILE *output, Meter *meter)
|
||||||
|
{
|
||||||
|
fprintf(output, "{"
|
||||||
|
QS(name,%s)
|
||||||
|
QS(id,%s)
|
||||||
|
Q(total_m3,%.3f)
|
||||||
|
Q(target_m3,%.3f)
|
||||||
|
QS(current_status,%s)
|
||||||
|
QS(time_dry,%s)
|
||||||
|
QS(time_reversed,%s)
|
||||||
|
QS(time_leaking,%s)
|
||||||
|
QS(time_bursting,%s)
|
||||||
|
QSE(timestamp,%s)
|
||||||
|
"}\n",
|
||||||
|
meter->name().c_str(),
|
||||||
|
meter->id().c_str(),
|
||||||
|
meter->totalWaterConsumption(),
|
||||||
|
meter->targetWaterConsumption(),
|
||||||
|
meter->status().c_str(), // DRY REVERSED LEAK BURST
|
||||||
|
meter->timeDry().c_str(),
|
||||||
|
meter->timeReversed().c_str(),
|
||||||
|
meter->timeLeaking().c_str(),
|
||||||
|
meter->timeBursting().c_str(),
|
||||||
|
meter->datetimeOfUpdateRobot().c_str());
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
// 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
|
||||||
|
// 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"cmdline.h"
|
||||||
|
#include"meters.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
struct Printer {
|
||||||
|
Printer(bool robot, bool meterfiles);
|
||||||
|
|
||||||
|
void print(Meter *meter);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool robot_, meterfiles_;
|
||||||
|
|
||||||
|
void printMeterHumanReadable(FILE *output, Meter *meter);
|
||||||
|
void printMeterJSON(FILE *output, Meter *meter);
|
||||||
|
};
|
2
util.cc
2
util.cc
|
@ -134,6 +134,7 @@ void verbose(const char* fmt, ...) {
|
||||||
|
|
||||||
bool isValidId(char *id)
|
bool isValidId(char *id)
|
||||||
{
|
{
|
||||||
|
if (strlen(id) == 0) return true;
|
||||||
if (strlen(id) != 8) return false;
|
if (strlen(id) != 8) return false;
|
||||||
for (int i=0; i<8; ++i) {
|
for (int i=0; i<8; ++i) {
|
||||||
if (id[i]<'0' || id[i]>'9') return false;
|
if (id[i]<'0' || id[i]>'9') return false;
|
||||||
|
@ -143,6 +144,7 @@ bool isValidId(char *id)
|
||||||
|
|
||||||
bool isValidKey(char *key)
|
bool isValidKey(char *key)
|
||||||
{
|
{
|
||||||
|
if (strlen(key) == 0) return true;
|
||||||
if (strlen(key) != 32) return false;
|
if (strlen(key) != 32) return false;
|
||||||
vector<uchar> tmp;
|
vector<uchar> tmp;
|
||||||
return hex2bin(key, &tmp);
|
return hex2bin(key, &tmp);
|
||||||
|
|
1
util.h
1
util.h
|
@ -22,6 +22,7 @@
|
||||||
#define UTIL_H
|
#define UTIL_H
|
||||||
|
|
||||||
#include<signal.h>
|
#include<signal.h>
|
||||||
|
#include<stdint.h>
|
||||||
#include<functional>
|
#include<functional>
|
||||||
#include<vector>
|
#include<vector>
|
||||||
|
|
||||||
|
|
69
wmbus.cc
69
wmbus.cc
|
@ -26,3 +26,72 @@ LIST_OF_LINK_MODES
|
||||||
#undef X
|
#undef X
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Manufacturer {
|
||||||
|
char code[4];
|
||||||
|
int m_field;
|
||||||
|
char name[64];
|
||||||
|
};
|
||||||
|
|
||||||
|
Manufacturer manufacturers[] = {
|
||||||
|
#define X(key,code,name) {#key,code,#name},
|
||||||
|
LIST_OF_MANUFACTURERS
|
||||||
|
#undef X
|
||||||
|
{"",0,""}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Initializer { Initializer(); };
|
||||||
|
|
||||||
|
static Initializer initializser_;
|
||||||
|
|
||||||
|
Initializer::Initializer() {
|
||||||
|
for (auto &m : manufacturers) {
|
||||||
|
m.m_field = \
|
||||||
|
(m.code[0]-64)*1024 +
|
||||||
|
(m.code[1]-64)*32 +
|
||||||
|
(m.code[2]-64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Telegram::print() {
|
||||||
|
printf("Received telegram from id: %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",
|
||||||
|
manufacturer(m_field).c_str());
|
||||||
|
printf(" device type: %s\n",
|
||||||
|
deviceType(m_field, a_field_device_type).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
string manufacturer(int m_field) {
|
||||||
|
for (auto &m : manufacturers) {
|
||||||
|
if (m.m_field == m_field) return m.name;
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
string deviceType(int m_field, int a_field_device_type) {
|
||||||
|
switch (a_field_device_type) {
|
||||||
|
case 0: return "Other";
|
||||||
|
case 1: return "Oil meter";
|
||||||
|
case 2: return "Electricity meter";
|
||||||
|
case 3: return "Gas meter";
|
||||||
|
case 4: return "Heat meter";
|
||||||
|
case 5: return "Steam meter";
|
||||||
|
case 6: return "Warm Water (30°C-90°C) meter";
|
||||||
|
case 7: return "Water meter";
|
||||||
|
case 8: return "Heat Cost Allocator";
|
||||||
|
case 9: return "Compressed air meter";
|
||||||
|
case 0x0a: return "Cooling load volume at outlet meter";
|
||||||
|
case 0x0b: return "Cooling load volume at inlet meter";
|
||||||
|
case 0x0c: return "Heat volume at inlet meter";
|
||||||
|
case 0x0d: return "Heat/Cooling load meter";
|
||||||
|
case 0x0e: return "Bus/System component";
|
||||||
|
case 0x0f: return "Unknown";
|
||||||
|
case 0x15: return "Hot water (>=90°C) meter";
|
||||||
|
case 0x16: return "Cold water meter";
|
||||||
|
case 0x17: return "Hot/Cold water meter";
|
||||||
|
case 0x18: return "Pressure meter";
|
||||||
|
case 0x19: return "A/D converter";
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
17
wmbus.h
17
wmbus.h
|
@ -21,6 +21,7 @@
|
||||||
#ifndef WMBUS_H
|
#ifndef WMBUS_H
|
||||||
#define WMBUS_H
|
#define WMBUS_H
|
||||||
|
|
||||||
|
#include"manufacturers.h"
|
||||||
#include"serial.h"
|
#include"serial.h"
|
||||||
#include"util.h"
|
#include"util.h"
|
||||||
|
|
||||||
|
@ -35,6 +36,17 @@ LIST_OF_LINK_MODES
|
||||||
#undef X
|
#undef X
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define CC_B_BIDIRECTIONAL_BIT 0x80
|
||||||
|
#define CC_RD_RESPONSE_DELAY_BIT 0x40
|
||||||
|
#define CC_S_SYNCH_FRAME_BIT 0x20
|
||||||
|
#define CC_R_RELAYED_BIT 0x10
|
||||||
|
#define CC_P_HIGH_PRIO_BIT 0x08
|
||||||
|
|
||||||
|
// Bits 31-29 in SN, ie 0xc0 of the final byte in the stream,
|
||||||
|
// since the bytes arrive with the least significant first
|
||||||
|
// aka little endian.
|
||||||
|
#define SN_ENC_BITS 0xc0
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
extern const char *LinkModeNames[];
|
extern const char *LinkModeNames[];
|
||||||
|
@ -53,6 +65,8 @@ struct Telegram {
|
||||||
|
|
||||||
// The id as written on the physical meter device.
|
// The id as written on the physical meter device.
|
||||||
string id() { return bin2hex(a_field_address); }
|
string id() { return bin2hex(a_field_address); }
|
||||||
|
|
||||||
|
void print();
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WMBus {
|
struct WMBus {
|
||||||
|
@ -66,4 +80,7 @@ struct WMBus {
|
||||||
|
|
||||||
WMBus *openIM871A(string device, SerialCommunicationManager *handler);
|
WMBus *openIM871A(string device, SerialCommunicationManager *handler);
|
||||||
|
|
||||||
|
string manufacturer(int m_field);
|
||||||
|
string deviceType(int a_field, int );
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -140,7 +140,7 @@ LinkMode WMBusIM871A::getLinkMode() {
|
||||||
waitForResponse();
|
waitForResponse();
|
||||||
LinkMode lm = UNKNOWN_LINKMODE;
|
LinkMode lm = UNKNOWN_LINKMODE;
|
||||||
if (received_command_ == DEVMGMT_MSG_GET_CONFIG_RSP) {
|
if (received_command_ == DEVMGMT_MSG_GET_CONFIG_RSP) {
|
||||||
verbose( "Receivd config size %zu\n", received_payload_.size());
|
verbose( "Received config size %zu\n", received_payload_.size());
|
||||||
int iff1 = received_payload_[0];
|
int iff1 = received_payload_[0];
|
||||||
bool has_device_mode = (iff1&1)==1;
|
bool has_device_mode = (iff1&1)==1;
|
||||||
bool has_link_mode = (iff1&2)==2;
|
bool has_link_mode = (iff1&2)==2;
|
||||||
|
@ -380,9 +380,8 @@ void WMBusIM871A::handleRadioLink(int msgid, vector<uchar> &payload)
|
||||||
verbose("Radiolink received (%zu bytes): ", payload.size());
|
verbose("Radiolink received (%zu bytes): ", payload.size());
|
||||||
for (auto c : payload) verbose("%02x ", c);
|
for (auto c : payload) verbose("%02x ", c);
|
||||||
verbose("\n");
|
verbose("\n");
|
||||||
|
|
||||||
t.c_field = payload[0];
|
t.c_field = payload[0];
|
||||||
t.m_field = payload[1]<<8 | payload[2];
|
t.m_field = payload[2]<<8 | payload[1];
|
||||||
t.a_field.resize(6);
|
t.a_field.resize(6);
|
||||||
t.a_field_address.resize(4);
|
t.a_field_address.resize(4);
|
||||||
for (int i=0; i<6; ++i) {
|
for (int i=0; i<6; ++i) {
|
||||||
|
@ -394,6 +393,12 @@ void WMBusIM871A::handleRadioLink(int msgid, vector<uchar> &payload)
|
||||||
t.ci_field=payload[9];
|
t.ci_field=payload[9];
|
||||||
t.payload.clear();
|
t.payload.clear();
|
||||||
t.payload.insert(t.payload.end(), payload.begin()+10, payload.end());
|
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);
|
||||||
|
|
||||||
for (auto f : telegram_listeners_) {
|
for (auto f : telegram_listeners_) {
|
||||||
if (f) f(&t);
|
if (f) f(&t);
|
||||||
}
|
}
|
||||||
|
|
Ładowanie…
Reference in New Issue