From 0f9edf9620c46bc43f67e9f4fe476fb441329116 Mon Sep 17 00:00:00 2001 From: weetmuts Date: Sat, 2 Sep 2017 23:26:57 +0200 Subject: [PATCH] Added --oneshot and Telegram::print --- Makefile | 37 +++++++ README.md | 24 ++++- cmdline.cc | 5 + cmdline.h | 9 +- main.cc | 90 +++++----------- manufacturers.h | 230 +++++++++++++++++++++++++++++++++++++++++ meter_multical21.cc | 247 +++++++++++++++++++++++++++++++------------- meters.h | 7 ++ printer.cc | 89 ++++++++++++++++ printer.h | 37 +++++++ util.cc | 2 + util.h | 1 + wmbus.cc | 69 +++++++++++++ wmbus.h | 17 +++ wmbus_im871a.cc | 11 +- 15 files changed, 729 insertions(+), 146 deletions(-) create mode 100644 manufacturers.h create mode 100644 printer.cc create mode 100644 printer.h diff --git a/Makefile b/Makefile index d4d9395..f0c92c9 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,7 @@ METERS_OBJS:=\ $(BUILD)/main.o \ $(BUILD)/meters.o \ $(BUILD)/meter_multical21.o \ + $(BUILD)/printer.o \ $(BUILD)/serial.o \ $(BUILD)/util.o \ $(BUILD)/wmbus.o \ @@ -51,3 +52,39 @@ $(BUILD)/wmbusmeters: $(METERS_OBJS) clean: 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/.*//' \ + -e 's/<\/table>.*//' \ + -e 's//X(/g' \ + -e 's///g' \ + -e 's/]*>//g' \ + -e 's/<\/a>//g' \ + -e 's/]*>//g' \ + -e 's/
/\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 '
' | 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 diff --git a/README.md b/README.md index c4efc20..c63fa37 100644 --- a/README.md +++ b/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 --verbose for detailed debug information. - --robot for json output. - --meterfiles to create status files below tmp, + --robot for json output. + --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. ``` 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 ./build/wmbusmeters /dev/ttyUSB0 MyTapWater 12345678 00112233445566778899AABBCCDDEEFF @@ -44,20 +46,32 @@ Binary generated: `./build_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. 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" ``` 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 and the water 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: +# 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 @@ -65,6 +79,8 @@ http://fastforward.ag/downloads/docu/FAST_EnergyCam-Protocol-wirelessMBUS.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: https://github.com/kokke/tiny-AES128-C diff --git a/cmdline.cc b/cmdline.cc index 41cfa31..a36db45 100644 --- a/cmdline.cc +++ b/cmdline.cc @@ -52,6 +52,11 @@ CommandLine *parseCommandLine(int argc, char **argv) { i++; continue; } + if (!strcmp(argv[i], "--oneshot")) { + c->oneshot = true; + i++; + continue; + } if (!strcmp(argv[i], "--")) { i++; break; diff --git a/cmdline.h b/cmdline.h index 8638a6d..7de3168 100644 --- a/cmdline.h +++ b/cmdline.h @@ -18,6 +18,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +#ifndef CMDLINE_H +#define CMDLINE_H + +#include"meters.h" #include #include @@ -27,7 +31,8 @@ struct MeterInfo { char *name; char *id; char *key; - + Meter *meter; + MeterInfo(char *n, char *i, char *k) { name = n; id = i; @@ -40,9 +45,11 @@ struct CommandLine { bool verbose; bool meterfiles; bool robot; + bool oneshot; char *usb_device; vector meters; }; CommandLine *parseCommandLine(int argc, char **argv); +#endif diff --git a/main.cc b/main.cc index b3488de..fcf829e 100644 --- a/main.cc +++ b/main.cc @@ -20,6 +20,7 @@ #include"cmdline.h" #include"meters.h" +#include"printer.h" #include"serial.h" #include"util.h" #include"wmbus.h" @@ -28,76 +29,21 @@ using namespace std; -CommandLine *cmdline; - -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); - } -} +void oneshotCheck(CommandLine *cmdline, SerialCommunicationManager *manager, Meter *meter); int main(int argc, char **argv) { - cmdline = parseCommandLine(argc, 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(" --robot for json output.\n"); - printf(" --meterfiles to create status files below tmp,\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"); exit(0); } @@ -114,13 +60,15 @@ int main(int argc, char **argv) // We want the data visible in the log file asap! setbuf(stdout, NULL); + + 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); - Meter *meter=createMultical21(wmbus, m.name, m.id, m.key); - if (cmdline->robot) meter->onUpdate(printMeterJSON); - else meter->onUpdate(printMeter); + m.meter = createMultical21(wmbus, 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"); @@ -129,11 +77,19 @@ int main(int argc, char **argv) printf(" id is the 8 digits printed on the meter.\n"); printf(" key is 32 hex digits with the aes key.\n\n"); - wmbus->onTelegram([](Telegram *t){ - 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]); - }); + wmbus->onTelegram([](Telegram *t){t->print();}); } 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(); +} diff --git a/manufacturers.h b/manufacturers.h new file mode 100644 index 0000000..3227628 --- /dev/null +++ b/manufacturers.h @@ -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 diff --git a/meter_multical21.cc b/meter_multical21.cc index feb289b..42d3f4d 100644 --- a/meter_multical21.cc +++ b/meter_multical21.cc @@ -48,8 +48,22 @@ struct MeterMultical21 : public Meter { string id(); string name(); + // Total water counted through the meter 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(); + 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 status(); string timeDry(); @@ -60,6 +74,7 @@ struct MeterMultical21 : public Meter { string datetimeOfUpdateHumanReadable(); string datetimeOfUpdateRobot(); void onUpdate(function cb); + int numUpdates(); private: void handleTelegram(Telegram*t); @@ -68,22 +83,40 @@ private: int info_codes_; 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_; string name_; vector id_; vector key_; WMBus *bus_; - function on_update_; - + vector> 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), 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_); - hex2bin(key, &key_); + if (strlen(id) == 0) { + 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*)); } @@ -99,7 +132,12 @@ string MeterMultical21::name() void MeterMultical21::onUpdate(function cb) { - on_update_ = cb; + on_update_.push_back(cb); +} + +int MeterMultical21::numUpdates() +{ + return num_updates_; } float MeterMultical21::totalWaterConsumption() @@ -107,11 +145,31 @@ float MeterMultical21::totalWaterConsumption() return total_water_consumption_; } +bool MeterMultical21::hasTotalWaterConsumption() +{ + return has_total_water_consumption_; +} + float MeterMultical21::targetWaterConsumption() { 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() { char datetime[40]; @@ -124,6 +182,7 @@ 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_)); return string(datetime); } @@ -134,38 +193,59 @@ Meter *createMultical21(WMBus *bus, const char *name, const char *id, const char void MeterMultical21::handleTelegram(Telegram *t) { - if (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 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; - } else { + 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]); + 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]; sn[0] = t->payload[2]; sn[1] = t->payload[3]; sn[2] = t->payload[4]; 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 content; 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]; int i=0; // 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 for (int j=0; j<6; ++j) { iv[i++] = t->a_field[j]; } // CC-field @@ -177,62 +257,85 @@ void MeterMultical21::handleTelegram(Telegram *t) { // BC iv[i++] = 0; - vector ivv(iv, iv+16); - string s = bin2hex(ivv); - verbose("IV %s\n", s.c_str()); - uchar xordata[16]; - AES_ECB_encrypt(iv, &key_[0], xordata, 16); + if (use_aes_) { + vector ivv(iv, iv+16); + verbose("Decrypting\n"); - uchar decrypt[16]; - xorit(xordata, &content[0], decrypt, 16); + string s = bin2hex(ivv); + verbose("IV %s\n", s.c_str()); - vector dec(decrypt, decrypt+16); - string answer = bin2hex(dec); - verbose("Decrypted >%s<\n", answer.c_str()); + uchar xordata[16]; + AES_ECB_encrypt(iv, &key_[0], xordata, 16); + + uchar decrypt[16]; + xorit(xordata, &content[0], decrypt, remaining); + + vector 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 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 dec2(decrypt, decrypt+remaining); + string answer2 = bin2hex(dec2); + verbose("Decrypted second block >%s<\n", answer2.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! - size_t remaining = content.size()-16; - if (remaining > 16) remaining = 16; // Should not happen. - - incrementIV(iv, sizeof(iv)); - vector 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 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()); + // Append the second decrypted block to the first. + dec.insert(dec.end(), dec2.begin(), dec2.end()); + } + content.clear(); + content.insert(content.end(), dec.begin(), dec.end()); } - processContent(dec); + processContent(content); 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 &c) { - /* int crc0 = c[0]; - int crc1 = c[1]; */ + int crc0 = c[0]; + int crc1 = c[1]; 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) { verbose("Short frame %d bytes\n", c.size()); - if (c.size() != 16) { - fprintf(stderr, "Warning! Unexpected length of frame %zu. Expected 16 bytes!\n", c.size()); + if (c.size() != 15) { + fprintf(stderr, "Warning! Unexpected length of frame %zu. Expected 15 bytes!\n", c.size()); } /*int ecrc0 = c[3]; int ecrc1 = c[4]; @@ -246,24 +349,24 @@ void MeterMultical21::processContent(vector &c) { int rec2val3 = c[12]; int rec3val0 = c[13]; int rec3val1 = c[14]; - int rec3val2 = c[15]; info_codes_ = rec1val1*256+rec1val0; verbose("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); + // 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; // 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); target_volume_ = ((float)target_volume_raw) / ((float)1000); - - // Which leaves one unknown byte. Takes on all possible values. - verbose("short rec3 %02x = unknown\n", rec3val2); + has_target_volume_ = true; + } else if (frame_type == 0x78) { verbose("Full frame %d bytes\n", c.size()); @@ -314,7 +417,8 @@ void MeterMultical21::processContent(vector &c) { 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 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" "with dif=0x44 vif=0x13 but got dif=%02x vif=%02x\n", rec3dif, rec3vif); @@ -324,7 +428,8 @@ void MeterMultical21::processContent(vector &c) { 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); - + has_target_volume_ = true; + // To unknown bytes, seems to be very constant. verbose("full rec4 %02x %02x = unknown\n", rec4val1, rec4val0); } else { diff --git a/meters.h b/meters.h index 085b14b..5f2e194 100644 --- a/meters.h +++ b/meters.h @@ -34,8 +34,14 @@ typedef unsigned char uchar; struct Meter { virtual string id() = 0; virtual string name() = 0; + virtual float totalWaterConsumption() = 0; + virtual bool hasTotalWaterConsumption() = 0; virtual float targetWaterConsumption() = 0; + virtual bool hasTargetWaterConsumption() = 0; + virtual float maxFlow() = 0; + virtual bool hasMaxFlow() = 0; + virtual string statusHumanReadable() = 0; virtual string status() = 0; virtual string timeDry() = 0; @@ -47,6 +53,7 @@ struct Meter { virtual string datetimeOfUpdateRobot() = 0; virtual void onUpdate(function cb) = 0; + virtual int numUpdates() = 0; }; Meter *createMultical21(WMBus *bus, const char *name, const char *id, const char *key); diff --git a/printer.cc b/printer.cc new file mode 100644 index 0000000..c03ef4f --- /dev/null +++ b/printer.cc @@ -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()); +} diff --git a/printer.h b/printer.h new file mode 100644 index 0000000..f58f907 --- /dev/null +++ b/printer.h @@ -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); +}; diff --git a/util.cc b/util.cc index 2b4f969..71d0c17 100644 --- a/util.cc +++ b/util.cc @@ -134,6 +134,7 @@ void verbose(const char* fmt, ...) { bool isValidId(char *id) { + if (strlen(id) == 0) return true; if (strlen(id) != 8) return false; for (int i=0; i<8; ++i) { if (id[i]<'0' || id[i]>'9') return false; @@ -143,6 +144,7 @@ bool isValidId(char *id) bool isValidKey(char *key) { + if (strlen(key) == 0) return true; if (strlen(key) != 32) return false; vector tmp; return hex2bin(key, &tmp); diff --git a/util.h b/util.h index 9af87c1..ec88c1b 100644 --- a/util.h +++ b/util.h @@ -22,6 +22,7 @@ #define UTIL_H #include +#include #include #include diff --git a/wmbus.cc b/wmbus.cc index f149b9f..59b59aa 100644 --- a/wmbus.cc +++ b/wmbus.cc @@ -26,3 +26,72 @@ LIST_OF_LINK_MODES #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"; +} + diff --git a/wmbus.h b/wmbus.h index 5c0f28e..f63c515 100644 --- a/wmbus.h +++ b/wmbus.h @@ -21,6 +21,7 @@ #ifndef WMBUS_H #define WMBUS_H +#include"manufacturers.h" #include"serial.h" #include"util.h" @@ -35,6 +36,17 @@ LIST_OF_LINK_MODES #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; extern const char *LinkModeNames[]; @@ -53,6 +65,8 @@ struct Telegram { // The id as written on the physical meter device. string id() { return bin2hex(a_field_address); } + + void print(); }; struct WMBus { @@ -66,4 +80,7 @@ struct WMBus { WMBus *openIM871A(string device, SerialCommunicationManager *handler); +string manufacturer(int m_field); +string deviceType(int a_field, int ); + #endif diff --git a/wmbus_im871a.cc b/wmbus_im871a.cc index a015a77..fd12b3c 100644 --- a/wmbus_im871a.cc +++ b/wmbus_im871a.cc @@ -140,7 +140,7 @@ LinkMode WMBusIM871A::getLinkMode() { waitForResponse(); LinkMode lm = UNKNOWN_LINKMODE; 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]; bool has_device_mode = (iff1&1)==1; bool has_link_mode = (iff1&2)==2; @@ -380,9 +380,8 @@ void WMBusIM871A::handleRadioLink(int msgid, vector &payload) 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[1]<<8 | payload[2]; + 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) { @@ -394,6 +393,12 @@ void WMBusIM871A::handleRadioLink(int msgid, vector &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); + for (auto f : telegram_listeners_) { if (f) f(&t); }