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)/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/ä/ä/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 --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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
90
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();
|
||||
}
|
||||
|
|
|
@ -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 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 {
|
||||
|
|
7
meters.h
7
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<void(Meter*)> cb) = 0;
|
||||
virtual int numUpdates() = 0;
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
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
1
util.h
|
@ -22,6 +22,7 @@
|
|||
#define UTIL_H
|
||||
|
||||
#include<signal.h>
|
||||
#include<stdint.h>
|
||||
#include<functional>
|
||||
#include<vector>
|
||||
|
||||
|
|
69
wmbus.cc
69
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";
|
||||
}
|
||||
|
||||
|
|
17
wmbus.h
17
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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue