Added --oneshot and Telegram::print

pull/5/head
weetmuts 2017-09-02 23:26:57 +02:00
rodzic 547eed7df8
commit 0f9edf9620
15 zmienionych plików z 729 dodań i 146 usunięć

Wyświetl plik

@ -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/.*<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/&auml;/ä/g' \
-e 's/&uuml;/ü/g' \
-e 's/&ouml;/ö/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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -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<string.h>
#include<vector>
@ -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<MeterInfo> meters;
};
CommandLine *parseCommandLine(int argc, char **argv);
#endif

90
main.cc
Wyświetl plik

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

230
manufacturers.h 100644
Wyświetl plik

@ -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'),&nbsp;)\
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

Wyświetl plik

@ -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<void(Meter*)> 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<uchar> id_;
vector<uchar> key_;
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) :
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<void(Meter*)> 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<uchar> 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<uchar> 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<uchar> 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<uchar> 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<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) {
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<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());
// 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<uchar> &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<uchar> &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<uchar> &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<uchar> &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 {

Wyświetl plik

@ -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<void(Meter*)> cb) = 0;
virtual int numUpdates() = 0;
};
Meter *createMultical21(WMBus *bus, const char *name, const char *id, const char *key);

89
printer.cc 100644
Wyświetl plik

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

37
printer.h 100644
Wyświetl plik

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

Wyświetl plik

@ -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<uchar> tmp;
return hex2bin(key, &tmp);

1
util.h
Wyświetl plik

@ -22,6 +22,7 @@
#define UTIL_H
#include<signal.h>
#include<stdint.h>
#include<functional>
#include<vector>

Wyświetl plik

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

17
wmbus.h
Wyświetl plik

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

Wyświetl plik

@ -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<uchar> &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<uchar> &payload)
t.ci_field=payload[9];
t.payload.clear();
t.payload.insert(t.payload.end(), payload.begin()+10, payload.end());
verbose("C field: %02x\n", t.c_field);
verbose("M field: %04x\n", t.m_field);
verbose("A field version: %02x\n", t.a_field_version);
verbose("A field dev type: %02x\n", t.a_field_device_type);
verbose("Ci field: %02x\n", t.ci_field);
for (auto f : telegram_listeners_) {
if (f) f(&t);
}