kopia lustrzana https://github.com/weetmuts/wmbusmeters
commit
941318aa54
|
@ -1,6 +1,9 @@
|
|||
language: cpp
|
||||
dist: bionic
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
language: cpp
|
||||
sudo: required
|
||||
dist: bionic
|
||||
before_install:
|
||||
- if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get install librtlsdr-dev libusb-dev; else brew install librtlsdr libusb; fi
|
||||
|
|
58
CHANGES
58
CHANGES
|
@ -1,3 +1,61 @@
|
|||
Version 1.0.0: 2020-10-25
|
||||
|
||||
IMPORTANT CHANGES THAT MIGHT AFFECT YOU!
|
||||
|
||||
** BREAKING No udev rules
|
||||
|
||||
Wmbusmeters new default behaviour is to never exit if no wmbus
|
||||
device is found. It stays running all the time even if no
|
||||
wmbus device is detected. As soon as a device is inserted
|
||||
it will detect/configure and start using the device.
|
||||
|
||||
(When reading stdin and files, then wmbusmeters will of course exit
|
||||
as soon as there is no more data or stdin is closed.)
|
||||
|
||||
This means that the udev rules no longer are needed and must be
|
||||
removed. This will be done automatically by the install script.
|
||||
|
||||
** BREAKING The background rtlwmbus/rtl433 command must be wrapped in CMD(...)
|
||||
|
||||
If you used your own command xxx to start rtlsdr+rtlwmbus then that command
|
||||
was previously supplied like this rtlwmbus:xxx
|
||||
You must now type rtlwmbus:CMD(xxx)
|
||||
|
||||
** There is a possibility to identify some dongles with their id.
|
||||
|
||||
The im871a/amb8465/rtlsdr dongles have an id. This id is printed when
|
||||
wmbusmeters starts using the dongle. You can later use the same id to
|
||||
configure different dongles for different frequencies and link modes.
|
||||
|
||||
You could for example have one rtlsdr dongle with an antenna tuned for 434 MHz
|
||||
and use set the id of that dongle to dongleALFA (rtl_eeprom -s dongleALFA)
|
||||
likewise for 868.95 MHz. Then you can start your two dongles at the same time:
|
||||
|
||||
wmbusmeters rtlwmbus[dongleALFA]:434M rtlmbus[dongleBETA]:868M
|
||||
|
||||
The full specification of a wmbus device is this:
|
||||
|
||||
tty/file/stdin : type [ id ] : bps : fq : linkmodes : command
|
||||
|
||||
Not all values are relevant for all dongles though.
|
||||
|
||||
** Use stderr for logging, stdout for data.
|
||||
|
||||
wmbusmeters now uses STDERR as default for info/verbose/debug/trace output.
|
||||
This will not affect you if you run wmbusmeters as a daemon,
|
||||
but if you start wmbusmeters yourself you should check where stderr is going.
|
||||
To use the old style add --usestdoutforlogging.
|
||||
|
||||
** New features
|
||||
|
||||
To list the shell envs for a meter do --listenvs=multical21
|
||||
To list the fields avilable for a meter do --listfields=multical21
|
||||
To list all meters do --listmeters
|
||||
To search for a meter do --listmeters=water or --listmeters=multi
|
||||
|
||||
The wmbus device used to received the telegram and the rssi level
|
||||
is part of the json, eg: "device":"rtlwmbus[1234]","rssi_dbm":-47
|
||||
|
||||
Version 0.9.36: 2020-09-08
|
||||
|
||||
Added support for detection of the proper driver
|
||||
|
|
16
Makefile
16
Makefile
|
@ -1,4 +1,4 @@
|
|||
# Copyright (C) 2017-2019 Fredrik Öhrström
|
||||
# Copyright (C) 2017-2020 Fredrik Öhrström
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -146,8 +146,10 @@ METER_OBJS:=\
|
|||
$(BUILD)/meter_vario451.o \
|
||||
$(BUILD)/meter_waterstarm.o \
|
||||
$(BUILD)/printer.o \
|
||||
$(BUILD)/rtlsdr.o \
|
||||
$(BUILD)/serial.o \
|
||||
$(BUILD)/shell.o \
|
||||
$(BUILD)/threads.o \
|
||||
$(BUILD)/util.o \
|
||||
$(BUILD)/units.o \
|
||||
$(BUILD)/wmbus.o \
|
||||
|
@ -159,7 +161,7 @@ METER_OBJS:=\
|
|||
$(BUILD)/wmbus_rtl433.o \
|
||||
$(BUILD)/wmbus_simulator.o \
|
||||
$(BUILD)/wmbus_rawtty.o \
|
||||
$(BUILD)/wmbus_wmb13u.o \
|
||||
$(BUILD)/wmbus_rc1180.o \
|
||||
$(BUILD)/wmbus_utils.o
|
||||
|
||||
all: $(BUILD)/wmbusmeters $(BUILD)/wmbusmeters-admin $(BUILD)/testinternals
|
||||
|
@ -195,10 +197,10 @@ snapcraft:
|
|||
$(BUILD)/main.o: $(BUILD)/short_manual.h $(BUILD)/version.h
|
||||
|
||||
$(BUILD)/wmbusmeters: $(METER_OBJS) $(BUILD)/main.o $(BUILD)/short_manual.h
|
||||
$(CXX) -o $(BUILD)/wmbusmeters $(METER_OBJS) $(BUILD)/main.o $(LDFLAGS) -lpthread
|
||||
$(CXX) -o $(BUILD)/wmbusmeters $(METER_OBJS) $(BUILD)/main.o $(LDFLAGS) -lrtlsdr -lusb-1.0 -lpthread
|
||||
|
||||
$(BUILD)/wmbusmeters-admin: $(METER_OBJS) $(BUILD)/admin.o $(BUILD)/short_manual.h
|
||||
$(CXX) -o $(BUILD)/wmbusmeters-admin $(METER_OBJS) $(BUILD)/admin.o $(LDFLAGS) -lmenu -lncurses -lpthread
|
||||
$(BUILD)/wmbusmeters-admin: $(METER_OBJS) $(BUILD)/admin.o $(BUILD)/ui.o $(BUILD)/short_manual.h
|
||||
$(CXX) -o $(BUILD)/wmbusmeters-admin $(METER_OBJS) $(BUILD)/admin.o $(BUILD)/ui.o $(LDFLAGS) -lmenu -lform -lncurses -lrtlsdr -lusb-1.0 -lpthread
|
||||
|
||||
$(BUILD)/short_manual.h: README.md
|
||||
echo 'R"MANUAL(' > $(BUILD)/short_manual.h
|
||||
|
@ -208,10 +210,10 @@ $(BUILD)/short_manual.h: README.md
|
|||
echo ')MANUAL";' >> $(BUILD)/short_manual.h
|
||||
|
||||
$(BUILD)/testinternals: $(METER_OBJS) $(BUILD)/testinternals.o
|
||||
$(CXX) -o $(BUILD)/testinternals $(METER_OBJS) $(BUILD)/testinternals.o $(LDFLAGS) -lpthread
|
||||
$(CXX) -o $(BUILD)/testinternals $(METER_OBJS) $(BUILD)/testinternals.o $(LDFLAGS) -lrtlsdr -lusb-1.0 -lpthread
|
||||
|
||||
$(BUILD)/fuzz: $(METER_OBJS) $(BUILD)/fuzz.o
|
||||
$(CXX) -o $(BUILD)/fuzz $(METER_OBJS) $(BUILD)/fuzz.o $(LDFLAGS) -lpthread
|
||||
$(CXX) -o $(BUILD)/fuzz $(METER_OBJS) $(BUILD)/fuzz.o $(LDFLAGS) -lrtlsdr -lpthread
|
||||
|
||||
clean:
|
||||
rm -rf build/* build_arm/* build_debug/* build_arm_debug/* *~
|
||||
|
|
123
README.md
123
README.md
|
@ -29,16 +29,28 @@ Availability of **wmbusmeters** for other Linux distributions can be checked on
|
|||
|
||||
# Run as a daemon
|
||||
|
||||
Remove the wmbus dongle (im871a,amb8465,rfmrx2,cul,d1tc) or the generic rtlsdr dongle (RTL2838) from your computer.
|
||||
Remove the wmbus dongle (im871a,amb8465,cul,rc1180,rfmrx2,d1tc) or the generic rtlsdr dongle (RTL2838) from your computer.
|
||||
|
||||
`./configure; make; sudo make install` will install wmbusmeters as a daemon.
|
||||
|
||||
Check the contents of your `/etc/wmbusmeters.conf` file, assuming it
|
||||
has `device=auto:c1` and you are using a im871a,amb8465,rc1180 or cul device,
|
||||
then you can now start the daemon with `sudo systemctl start wmbusmeters`
|
||||
or you can try it from the command line `wmbusmeters auto:c1`
|
||||
|
||||
Wmbusmeters will scan for wmbus devices every few seconds and detect whenever
|
||||
a device is plugged in or removed.
|
||||
|
||||
To have the wmbusmeters daemon start automatically when the computer boots do:
|
||||
`sudo systemctl enable wmbusmeters`
|
||||
|
||||
You can trigger a reload of the config files with `sudo killall -HUP wmbusmetersd`
|
||||
|
||||
`make; sudo make install` will install wmbusmeters as a daemon that starts
|
||||
automatically when an appropriate wmbus usb dongle is inserted in the computer.
|
||||
(Note! make install only works for GNU/Linux. For MacOSX try to start
|
||||
`wmbusmetersd /tmp/thepidfile` from a script instead. Here you can also override the device:
|
||||
`wmbusmetersd --device=/dev/ttyXXY --listento=t1 /tmp/thepidfile`)
|
||||
`wmbusmetersd /tmp/thepidfile` from a script instead.)
|
||||
|
||||
Check the config file /etc/wmbusmeters.conf and edit the device to
|
||||
point to your dongle.
|
||||
point to your dongle or use auto
|
||||
```
|
||||
loglevel=normal
|
||||
device=/dev/ttyUSB0:im871a
|
||||
|
@ -109,9 +121,9 @@ Or you can start wmbusmeters with your own config files:
|
|||
wmbusmeters --useconfig=/home/me/.config/wmbusmeters
|
||||
```
|
||||
|
||||
You can add --device and --listento to override the settings in the config. Like this:
|
||||
You can add --device to override the settings in the config. Like this:
|
||||
```
|
||||
wmbusmeters --useconfig=/home/me/.config/wmbusmeters --device=/dev/ttyXXY --listento=t1`
|
||||
wmbusmeters --useconfig=/home/me/.config/wmbusmeters --device=rtlwmbus
|
||||
```
|
||||
|
||||
The files/dir should then be located here:
|
||||
|
@ -124,23 +136,23 @@ depending on if you are running as a daemon or not.
|
|||
|
||||
# Running without config files, good for experimentation and test.
|
||||
```
|
||||
wmbusmeters version: 0.9.15
|
||||
Usage: wmbusmeters {options} <device>{:suffix} ( [meter_name] [meter_type]{:<modes>} [meter_id] [meter_key] )*
|
||||
wmbusmeters version: 0.9.36
|
||||
Usage: wmbusmeters {options} <device> ( [meter_name] [meter_type]{:<modes>} [meter_id] [meter_key] )*
|
||||
|
||||
As <options> you can use:
|
||||
|
||||
--addconversions=<unit>+ add conversion to these units to json and meter env variables (GJ)
|
||||
--alarmexpectedactivity=mon-fri(08-17),sat-sun(09-12) Specify when the timeout is tested, default is mon-sun(00-23)
|
||||
--alarmshell=<cmdline> invokes cmdline when an alarm triggers
|
||||
--alarmtimeout=<time> Expect a telegram to arrive within <time> seconds, eg 60s, 60m, 24h during expected activity.
|
||||
--debug for a lot of information
|
||||
--exitafter=<time> exit program after time, eg 20h, 10m 5s
|
||||
--format=<hr/json/fields> for human readable, json or semicolon separated fields
|
||||
--json_xxx=yyy always add "xxx"="yyy" to the json output and add shell env METER_xxx=yyy
|
||||
--listento=<mode> tell the wmbus dongle to listen to this single link mode where mode can be
|
||||
c1,t1,s1,s1m,n1a,n1b,n1c,n1d,n1e,n1f
|
||||
--listento=c1,t1,s1 tell the wmbus dongle to listen to these link modes
|
||||
different dongles support different combinations of modes
|
||||
--c1 --t1 --s1 --s1m ... another way to set the link mode for the dongle
|
||||
--listenvs list the env variables available for the meter
|
||||
--listfields list the fields selectable for the meter
|
||||
--listenvs=<meter_type> list the env variables available for the given meter type
|
||||
--listfields=<meter_type> list the fields selectable for the given meter type
|
||||
--listmeters list all meter types
|
||||
--listmeters=<search> list all meter types containing the text <search>
|
||||
--logfile=<file> use this file instead of stdout
|
||||
--logtelegrams log the contents of the telegrams for easy replay
|
||||
--meterfiles=<dir> store meter readings in dir
|
||||
|
@ -149,18 +161,25 @@ As <options> you can use:
|
|||
--meterfilestimestamp=(never|day|hour|minute|micros) the meter file is suffixed with a
|
||||
timestamp (localtime) with the given resolution.
|
||||
--oneshot wait for an update from each meter, then quit
|
||||
--reopenafter=<time> close/reopen dongle connection repeatedly every <time> seconds, eg 60s, 60m, 24h
|
||||
--resetafter=<time> reset the wmbus dongle regularly, default is 23h
|
||||
--selectfields=id,timestamp,total_m3 select fields to be printed
|
||||
--separator=<c> change field separator to c
|
||||
--shell=<cmdline> invokes cmdline with env variables containing the latest reading
|
||||
--useconfig=<dir> load config files from dir/etc
|
||||
--usestderr write debug/verbose and logging output to stderr
|
||||
--usestderr write notices/debug/verbose and other logging output to stderr (the default)
|
||||
--usestdoutforlogging write debug/verbose and logging output to stdout
|
||||
--verbose for more information
|
||||
|
||||
As <device> you can use:
|
||||
|
||||
auto, to have wmbusmeters look for the links /dev/im871a, /dev/amb8465, /dev/rfmrx2 and /dev/rtlsdr
|
||||
(the links are automatically generated by udev if you have run the install scripts)
|
||||
auto:c1, to have wmbusmeters look existing serial devices and probe them to detect: im871a, amb8465, cul, rc1180 or rtlsdr.
|
||||
|
||||
If you have two im871a you can supply both of them and set different listening modes:
|
||||
im871a[12345678]:c1 im871a[11223344]:t1
|
||||
|
||||
You can also specify rtlwmbus and if you set the serial in the rtlsdr
|
||||
dongle using `rtl_eeprom -s 1234` you can also refer to a specific
|
||||
rtlsdr dongle like this `rtlwmbus[1234]`.
|
||||
|
||||
/dev/ttyUSB0:amb8465, if you have an amb8465 dongle assigned to ttyUSB0. Other suffixes are im871a,rfmrx2,d1tc,cul.
|
||||
|
||||
|
@ -178,7 +197,7 @@ rtl433, to spawn the background process: "rtl_433 -F csv -f 868.95M"
|
|||
|
||||
rtl433:868.9M, to tune to this fq instead.
|
||||
|
||||
rtlwmbus:<commandline>, to specify the entire background process command line that is expected to produce rtlwbus compatible output.
|
||||
rtlwmbus:CMD(<commandline>), to specify the entire background process command line that is expected to produce rtlwbus compatible output.
|
||||
Likewise for rtl433.
|
||||
|
||||
stdin, to read raw binary telegrams from stdin.
|
||||
|
@ -206,10 +225,11 @@ As meter quadruples you specify:
|
|||
Supported wmbus dongles:
|
||||
IMST 871a (im871a)
|
||||
Amber 8465 (amb8465)
|
||||
BMeters RFM-RX2 (rfmrx2)
|
||||
CUL family (cul)
|
||||
Radiocraft (RC1180) work in progress only T1 mode
|
||||
rtl_wmbus (rtlwmbus)
|
||||
rtl_433 (rtl433)
|
||||
BMeters RFM-RX2 (rfmrx2)
|
||||
|
||||
Supported water meters:
|
||||
Apator at-wmbus-08 (apator08) (non-standard protocol)
|
||||
|
@ -275,29 +295,30 @@ and T1 telegrams at the same time.
|
|||
# Usage examples
|
||||
|
||||
```
|
||||
wmbusmeters --listento=c1 /dev/ttyUSB1:amb8465
|
||||
wmbusmeters /dev/ttyUSB1:amb8465:c1,t1
|
||||
```
|
||||
|
||||
Simply runs a scan with mode C1 to search for meters and print the IDs and any detected driver,
|
||||
Simply runs a scan with mode C1 and T1 to search for meters and print the IDs and any detected driver,
|
||||
for example:
|
||||
```
|
||||
Received telegram from: 12345678
|
||||
manufacturer: (KAM) Kamstrup Energi (0x2c2d)
|
||||
device type: Cold water meter (0x16)
|
||||
device ver: 0x1b
|
||||
device driver: multical21
|
||||
device: im871a[12345678]
|
||||
rssi: -77 dBm
|
||||
driver: multical21
|
||||
```
|
||||
|
||||
Now listen to this specific meter.
|
||||
|
||||
```
|
||||
wmbusmeters /dev/ttyUSB0:im871a MyTapWater multical21:c1 12345678 00112233445566778899AABBCCDDEEFF
|
||||
wmbusmeters /dev/ttyUSB0:im871a:c1 MyTapWater multical21:c1 12345678 00112233445566778899AABBCCDDEEFF
|
||||
```
|
||||
|
||||
(The :c1 can be left out, since multical21 only transmits c1 telegrams. The suffix
|
||||
with the expected link mode might be necessary for other meters, like apator162 for example.
|
||||
The Multical21 uses compressed telegrams, which means that you might have to wait up to 8 telegrams
|
||||
(8*16 seconds) until you receive a full length telegram which gives all the information needed
|
||||
(The Multical21 and other meters use compressed telegrams, which means
|
||||
that you might have to wait up to 8 telegrams (8*16 seconds) until you
|
||||
receive a full length telegram which gives all the information needed
|
||||
to decode the compressed telegrams.)
|
||||
|
||||
Example output:
|
||||
|
@ -310,7 +331,7 @@ Example format json output:
|
|||
|
||||
`wmbusmeters --format=json /dev/ttyUSB0:im871a MyTapWater multical21 12345678 00112233445566778899AABBCCDDEEFF MyHeater multical302 22222222 00112233445566778899AABBCCDDEEFF`
|
||||
|
||||
`{"media":"cold water","meter":"multical21","name":"MyTapWater","id":"12345678","total_m3":6.388,"target_m3":6.377,"max_flow_m3h":0.000,"flow_temperature":8,"external_temperature":23,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"2018-02-08T09:07:22Z"}`
|
||||
`{"media":"cold water","meter":"multical21","name":"MyTapWater","id":"12345678","total_m3":6.388,"target_m3":6.377,"max_flow_m3h":0.000,"flow_temperature":8,"external_temperature":23,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"2018-02-08T09:07:22Z","device":"im871a[1234567]","rssi_dbm":-40}`
|
||||
|
||||
`{"media":"heat","meter":"multical302","name":"MyHeater","id":"22222222","total_kwh":0.000,"total_volume_m3":0.000,"current_kw":"0.000","timestamp":"2018-02-08T09:07:22Z"}`
|
||||
|
||||
|
@ -373,11 +394,6 @@ file as argument. See test.sh for more info.
|
|||
|
||||
If you do not specify any meters on the command line, then wmbusmeters
|
||||
will listen and print the header information of any telegram it hears.
|
||||
You must specify the listening mode.
|
||||
|
||||
With an rtlwmbus or amb8465 dongle: `wmbusmeters --listento=c1,t1 /dev/ttyUSB0:amb8465`
|
||||
|
||||
With an imst871a dongle: `wmbusmeters --listento=c1 /dev/ttyUSB0:im871a`
|
||||
|
||||
# Builds and runs on GNU/Linux MacOSX (with recent XCode), and FreeBSD
|
||||
|
||||
|
@ -412,7 +428,6 @@ Binary generated: `./wmbusmeters_0.8_armhf.deb`
|
|||
`/usr/bin/wmbusmeters`
|
||||
`/usr/sbin/wmbusmetersd`
|
||||
`/etc/systemd/system/wmbusmeters.service`
|
||||
`/etc/udev/rules.d/99-wmbus-usb-serial.rules`
|
||||
`/etc/logrotate.d/wmbusmeters`
|
||||
|
||||
creates these directories:
|
||||
|
@ -421,34 +436,6 @@ creates these directories:
|
|||
|
||||
and adds the user `wmbusmeters` with no login account.
|
||||
|
||||
This means that when a im871a/amb8465 dongle is inserted, then the
|
||||
appropriate /dev/im871a or /dev/amb8465 link is created. Also the
|
||||
wmbusmeters daemon will be automatically started/stopped whenever the
|
||||
im871a/amb8465 dongle is inserted/removed, and the daemon starts when
|
||||
the computer boots, if the dongle is already inserted.
|
||||
|
||||
You can start/stop the daemon with `sudo systemctl stop wmbusmeters@-dev-im871a_0.service`
|
||||
or `sudo systemctl stop wmbusmeters@-dev-amb8465_1.service` etc.
|
||||
|
||||
You can trigger a reload of the config files with `sudo killall -HUP wmbusmetersd`
|
||||
|
||||
If you add more dongles, then more daemons gets started, each with a unique name/nr.
|
||||
|
||||
# Daemon without udev rules
|
||||
|
||||
To start the daemon without the udev rules. Then do:
|
||||
`make install EXTRA_INSTALL_OPTIONS='--no-udev-rules'`
|
||||
then no udev rules will be added.
|
||||
|
||||
(If you have already installed once before you might have to remove
|
||||
/dev/udev/rules.d/99-wmbus-usb-serial.rules)
|
||||
|
||||
You can now start/stop the daemon with `sudo systemctl stop wmbusmeters`
|
||||
the device must of course be correct in the /etc/wmbusmeters.conf file.
|
||||
|
||||
To have wmbusmeters start automatically when the computer boots do:
|
||||
`sudo systemctl enable wmbusmeters`
|
||||
|
||||
# Common problems
|
||||
|
||||
If the daemon has started then the wmbus device will be taken and you cannot start wmbusmeters manually.
|
||||
|
@ -491,10 +478,6 @@ If you like to send the bytes manually, the correct bytes are:
|
|||
* Factory reset of the settings: `0xFF1100EE`
|
||||
* Reset the stick to apply the factory defaults: `0xFF0500FA` this is not necessary if you unplug and reinsert the dongle.
|
||||
|
||||
## WMB13U-868, WMB14UE-868 USB sticks
|
||||
|
||||
These dongles do not seem to work with Linux, perhaps problems with the usb2serial pl2303 driver?
|
||||
|
||||
# Docker
|
||||
|
||||
Experimental docker containers are available here: https://hub.docker.com/r/weetmuts/wmbusmeters
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
# Empty so far
|
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,64 @@
|
|||
/* autoconf/config.h.in. Generated from configure.ac by autoheader. */
|
||||
|
||||
/* Stat structure has st_mtim */
|
||||
#undef HAS_ST_MTIM
|
||||
|
||||
/* Stat structure has st_mtime */
|
||||
#undef HAS_ST_MTIME
|
||||
|
||||
/* Define to 1 if you have the <inttypes.h> header file. */
|
||||
#undef HAVE_INTTYPES_H
|
||||
|
||||
/* Define to 1 if you have the <memory.h> header file. */
|
||||
#undef HAVE_MEMORY_H
|
||||
|
||||
/* Define to 1 if you have the <stdint.h> header file. */
|
||||
#undef HAVE_STDINT_H
|
||||
|
||||
/* Define to 1 if you have the <stdlib.h> header file. */
|
||||
#undef HAVE_STDLIB_H
|
||||
|
||||
/* Define to 1 if you have the <strings.h> header file. */
|
||||
#undef HAVE_STRINGS_H
|
||||
|
||||
/* Define to 1 if you have the <string.h> header file. */
|
||||
#undef HAVE_STRING_H
|
||||
|
||||
/* Define to 1 if you have the <sys/stat.h> header file. */
|
||||
#undef HAVE_SYS_STAT_H
|
||||
|
||||
/* Define to 1 if you have the <sys/types.h> header file. */
|
||||
#undef HAVE_SYS_TYPES_H
|
||||
|
||||
/* Define to 1 if you have the <unistd.h> header file. */
|
||||
#undef HAVE_UNISTD_H
|
||||
|
||||
/* Define to the address where bug reports for this package should be sent. */
|
||||
#undef PACKAGE_BUGREPORT
|
||||
|
||||
/* Define to the full name of this package. */
|
||||
#undef PACKAGE_NAME
|
||||
|
||||
/* Define to the full name and version of this package. */
|
||||
#undef PACKAGE_STRING
|
||||
|
||||
/* Define to the one symbol short name of this package. */
|
||||
#undef PACKAGE_TARNAME
|
||||
|
||||
/* Define to the home page for this package. */
|
||||
#undef PACKAGE_URL
|
||||
|
||||
/* Define to the version of this package. */
|
||||
#undef PACKAGE_VERSION
|
||||
|
||||
/* The size of `time_t', as computed by sizeof. */
|
||||
#undef SIZEOF_TIME_T
|
||||
|
||||
/* Define to 1 if the `S_IS*' macros in <sys/stat.h> do not work properly. */
|
||||
#undef STAT_MACROS_BROKEN
|
||||
|
||||
/* Define to 1 if you have the ANSI C header files. */
|
||||
#undef STDC_HEADERS
|
||||
|
||||
/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
|
||||
#undef TIME_WITH_SYS_TIME
|
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,14 @@
|
|||
X:=
|
||||
SPACE:=$(X) $(X)
|
||||
COMMA:=,
|
||||
HASH:=\#
|
||||
SQUOTE:='
|
||||
#'
|
||||
DQUOTE:="
|
||||
#"
|
||||
define NEWLINE
|
||||
|
||||
|
||||
endef
|
||||
|
||||
CONF_NAME:=@CONF_NAME@
|
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,50 @@
|
|||
# Copyright (C) 2020 Fredrik Öhrström
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
AC_PREREQ([2.69])
|
||||
AC_INIT(wmbusmeters, wmbusmeters, oehrstroem@gmail.com,,https://github.com/weetmuts/wmbusmeters)
|
||||
AC_CONFIG_AUX_DIR([autoconf])
|
||||
|
||||
AC_CANONICAL_BUILD
|
||||
AC_CANONICAL_HOST
|
||||
|
||||
SRC_ROOT="$(pwd -L)"
|
||||
|
||||
CONF_NAME="${host_cpu}-${host_vendor}-${host_os}"
|
||||
BUILD_ROOT="$SRC_ROOT/build"
|
||||
OUTPUT_ROOT="$SRC_ROOT/build/${CONF_NAME}"
|
||||
|
||||
mkdir -p "$OUTPUT_ROOT"
|
||||
|
||||
AC_LANG_PUSH([C++])
|
||||
|
||||
#C_CHECK_LIB(usb, usb_init, [],
|
||||
#
|
||||
# C_MSG_ERROR([Could not find libusb library. Try: sudo apt install libusb-dev])
|
||||
#)
|
||||
|
||||
AC_CHECK_LIB(rtlsdr, rtlsdr_get_device_count, [],
|
||||
[
|
||||
AC_MSG_ERROR([Could not find rtlsdr library. Try: sudo apt install librtlsdr-dev])
|
||||
])
|
||||
|
||||
AC_CONFIG_FILES([$OUTPUT_ROOT/spec.gmk:$SRC_ROOT/autoconf/spec.gmk.in])
|
||||
AC_CONFIG_FILES([$OUTPUT_ROOT/Makefile:$SRC_ROOT/autoconf/Makefile.in])
|
||||
|
||||
# Make sure config.status ends up in the build directory instead of the src root.
|
||||
CONFIG_STATUS="$OUTPUT_ROOT/config.status"
|
||||
# Write out spec.gmk and build/Makefile
|
||||
AC_OUTPUT
|
191
install.sh
191
install.sh
|
@ -7,33 +7,31 @@ then
|
|||
|
||||
Options:
|
||||
--no-adduser Do not add wmbusmeters user
|
||||
--no-udev-rules Do not add udev rules
|
||||
"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ ! "$(basename "$1")" = "wmbusmeters" ]
|
||||
then
|
||||
echo Oups, please only try to install wmbusmeters using this script.
|
||||
echo "Oups, please only try to install wmbusmeters using this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -x "$1" ]
|
||||
then
|
||||
echo This is not an executable.
|
||||
echo "This is not an executable."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$2" ]
|
||||
then
|
||||
echo Oups, please supply a valid root directory.
|
||||
echo "Oups, please supply a valid root directory."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SRC=$1
|
||||
ROOT="${2%/}"
|
||||
ADDUSER=true
|
||||
ADDUDEVRULES=true
|
||||
|
||||
while [ $# -ne 0 ]
|
||||
do
|
||||
|
@ -43,10 +41,6 @@ do
|
|||
--no-adduser)
|
||||
ADDUSER=false
|
||||
;;
|
||||
--no-udev-rules)
|
||||
ADDUDEVRULES=false
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
|
@ -62,7 +56,7 @@ mkdir -p "$ROOT"/usr/sbin
|
|||
cp "$SRC" "$ROOT"/usr/bin/wmbusmeters
|
||||
ln -s /usr/bin/wmbusmeters "$ROOT"/usr/sbin/wmbusmetersd
|
||||
|
||||
echo binaries: installed "$ROOT"/usr/bin/wmbusmeters and "$ROOT"/usr/sbin/wmbusmetersd
|
||||
echo "binaries: installed $ROOT/usr/bin/wmbusmeters and $ROOT/usr/sbin/wmbusmetersd"
|
||||
|
||||
####################################################################
|
||||
##
|
||||
|
@ -73,7 +67,7 @@ rm -f "$ROOT"/usr/share/man/man1/wmbusmeters.1.gz
|
|||
mkdir -p "$ROOT"/usr/share/man/man1
|
||||
gzip -c wmbusmeters.1 > "$ROOT"/usr/share/man/man1/wmbusmeters.1.gz
|
||||
|
||||
echo man page: installed "$ROOT"/usr/share/man/man1/wmbusmeters.1.gz
|
||||
echo "man page: installed $ROOT/usr/share/man/man1/wmbusmeters.1.gz"
|
||||
|
||||
####################################################################
|
||||
##
|
||||
|
@ -94,24 +88,49 @@ fi
|
|||
|
||||
if [ "$ADDUSER" = "true" ]
|
||||
then
|
||||
if [ "$ID" = "" ]
|
||||
if [ $(getent group wmbusmeters) ]
|
||||
then
|
||||
echo "group: wmbusmeters unmodified"
|
||||
else
|
||||
groupadd -f wmbusmeters
|
||||
echo "group: added wmbusmeters"
|
||||
fi
|
||||
|
||||
if [ -z "$ID" ]
|
||||
then
|
||||
# Create the wmbusmeters user
|
||||
useradd --system --shell $USERSHELL --groups dialout wmbusmeters
|
||||
echo user: added wmbusmeters
|
||||
useradd --system --shell $USERSHELL -g wmbusmeters wmbusmeters
|
||||
echo "user: added wmbusmeters"
|
||||
else
|
||||
echo user: wmbusmeters unmodified
|
||||
echo "user: wmbusmeters unmodified"
|
||||
fi
|
||||
if [ ! -z "$SUDO_USER" ]
|
||||
|
||||
if [ $(getent group dialout) ]
|
||||
then
|
||||
if [ "$(groups $SUDO_USER | grep -o wmbusmeters)" = "" ]
|
||||
then
|
||||
# Add user to the wmbusmeters group.
|
||||
usermod -a -G wmbusmeters $SUDO_USER
|
||||
echo user: added $SUDO_USER to group wmbusmeters
|
||||
else
|
||||
echo user: user $SUDO_USER already added group wmbusmeters
|
||||
fi
|
||||
if [ "$(groups wmbusmeters | grep -o dialout)" = "" ]
|
||||
then
|
||||
# Add the wmbusmeters user to dialout
|
||||
usermod -a -G dialout wmbusmeters
|
||||
echo "user: added wmbusmeters to dialout group"
|
||||
else
|
||||
echo "user: wmbusmeters already added to dialout"
|
||||
fi
|
||||
else
|
||||
echo "dialout group does not exist"
|
||||
fi
|
||||
|
||||
if [ $(getent group uucp) ]
|
||||
then
|
||||
if [ "$(groups wmbusmeters | grep -o uucp)" = "" ]
|
||||
then
|
||||
# Add the wmbusmeters user to uucp
|
||||
usermod -a -G uucp wmbusmeters
|
||||
echo "user: added wmbusmeters to uucp group"
|
||||
else
|
||||
echo "user: wmbusmeters already added to uucp"
|
||||
fi
|
||||
else
|
||||
echo "uucp group does not exist"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
@ -137,7 +156,7 @@ then
|
|||
# Create the log directories
|
||||
mkdir -p "$ROOT"/var/log/wmbusmeters/meter_readings
|
||||
chown -R wmbusmeters:wmbusmeters "$ROOT"/var/log/wmbusmeters
|
||||
echo log: created "$ROOT"/var/log/wmbusmeters/meter_readings
|
||||
echo "log: created $ROOT/var/log/wmbusmeters/meter_readings"
|
||||
fi
|
||||
|
||||
####################################################################
|
||||
|
@ -159,9 +178,9 @@ then
|
|||
/bin/kill -HUP `cat /run/wmbusmeters/wmbusmeters.pid 2> /dev/null` 2> /dev/null || true
|
||||
endscript
|
||||
EOF
|
||||
echo logrotate: created "$ROOT"/etc/logrotate.d/wmbusmeters
|
||||
echo "logrotate: created $ROOT/etc/logrotate.d/wmbusmeters"
|
||||
else
|
||||
echo conf file: "$ROOT"/etc/logrotate.d/wmbusmeters unchanged
|
||||
echo "conf file: $ROOT/etc/logrotate.d/wmbusmeters unchanged"
|
||||
fi
|
||||
|
||||
####################################################################
|
||||
|
@ -183,9 +202,9 @@ meterfilesaction=overwrite
|
|||
logfile=/var/log/wmbusmeters/wmbusmeters.log
|
||||
EOF
|
||||
chmod 644 "$ROOT"/etc/wmbusmeters.conf
|
||||
echo conf file: created "$ROOT"/etc/wmbusmeters.conf
|
||||
echo "conf file: created $ROOT/etc/wmbusmeters.conf"
|
||||
else
|
||||
echo conf file: "$ROOT"/etc/wmbusmeters.conf unchanged
|
||||
echo "conf file: $ROOT/etc/wmbusmeters.conf unchanged"
|
||||
fi
|
||||
|
||||
####################################################################
|
||||
|
@ -198,9 +217,9 @@ then
|
|||
# Create the configuration directory
|
||||
mkdir -p "$ROOT"/etc/wmbusmeters.d
|
||||
chmod -R 755 "$ROOT"/etc/wmbusmeters.d
|
||||
echo conf dir: created "$ROOT"/etc/wmbusmeters.d
|
||||
echo "conf dir: created $ROOT/etc/wmbusmeters.d"
|
||||
else
|
||||
echo conf dir: "$ROOT"/etc/wmbusmeters.d unchanged
|
||||
echo "conf dir: $ROOT/etc/wmbusmeters.d unchanged"
|
||||
fi
|
||||
|
||||
####################################################################
|
||||
|
@ -215,7 +234,7 @@ OLD_WMBS=~/old.wmbusmeters.service.backup
|
|||
CURR_WMBS="$ROOT"/lib/systemd/system/wmbusmeters.service
|
||||
if [ -f $CURR_WMBS ]
|
||||
then
|
||||
echo systemd: backing up $CURR_WMBS to here: $OLD_WMBS
|
||||
echo "systemd: backing up $CURR_WMBS to here: $OLD_WMBS"
|
||||
cp $CURR_WMBS $OLD_WMBS 2>/dev/null
|
||||
fi
|
||||
|
||||
|
@ -223,7 +242,7 @@ fi
|
|||
# This means that wmbusmeters will rely on the conf file device setting.
|
||||
cat <<'EOF' > $CURR_WMBS
|
||||
[Unit]
|
||||
Description="wmbusmeters service (no udev trigger)"
|
||||
Description="wmbusmeters service"
|
||||
Documentation=https://github.com/weetmuts/wmbusmeters
|
||||
Documentation=man:wmbusmeters(1)
|
||||
After=network.target
|
||||
|
@ -258,9 +277,9 @@ EOF
|
|||
|
||||
if diff $OLD_WMBS $CURR_WMBS 1>/dev/null
|
||||
then
|
||||
echo systemd: no changes to $CURR_WMBS
|
||||
echo "systemd: no changes to $CURR_WMBS"
|
||||
else
|
||||
echo systemd: updated $CURR_WMBS
|
||||
echo "systemd: updated $CURR_WMBS"
|
||||
SYSTEMD_NEEDS_RELOAD=true
|
||||
fi
|
||||
|
||||
|
@ -268,107 +287,41 @@ OLD_WMBAS=~/old.wmbusmeters@.service.backup
|
|||
CURR_WMBAS="$ROOT"/lib/systemd/system/wmbusmeters@.service
|
||||
if [ -f $CURR_WMBAS ]
|
||||
then
|
||||
echo systemd: backing up $CURR_WMBAS to here: $OLD_WMBAS
|
||||
echo "systemd: removing $CURR_WMBAS"
|
||||
echo "systemd: backing up $CURR_WMBAS to here: $OLD_WMBAS"
|
||||
cp $CURR_WMBAS $OLD_WMBAS 2>/dev/null
|
||||
fi
|
||||
|
||||
# Create service file that needs an argument eg.
|
||||
# sudo systemctl start wmbusmeters@/dev/im871a_1.service
|
||||
cat <<'EOF' > "$ROOT"/lib/systemd/system/wmbusmeters@.service
|
||||
[Unit]
|
||||
Description="wmbusmeters service (udev triggered by %I)"
|
||||
Documentation=https://github.com/weetmuts/wmbusmeters
|
||||
Documentation=man:wmbusmeters(1)
|
||||
After=network.target
|
||||
StopWhenUnneeded=true
|
||||
StartLimitIntervalSec=10
|
||||
StartLimitInterval=10
|
||||
StartLimitBurst=3
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
PrivateTmp=yes
|
||||
User=wmbusmeters
|
||||
Group=wmbusmeters
|
||||
Restart=always
|
||||
RestartSec=1
|
||||
|
||||
# Run ExecStartPre with root-permissions
|
||||
|
||||
PermissionsStartOnly=true
|
||||
ExecStartPre=-/bin/mkdir -p /var/log/wmbusmeters/meter_readings
|
||||
ExecStartPre=/bin/chown -R wmbusmeters:wmbusmeters /var/log/wmbusmeters
|
||||
ExecStartPre=-/bin/mkdir -p /run/wmbusmeters
|
||||
ExecStartPre=/bin/chown -R wmbusmeters:wmbusmeters /run/wmbusmeters
|
||||
|
||||
ExecStart=/usr/sbin/wmbusmetersd --device='%I' /run/wmbusmeters/wmbusmeters-%i.pid
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
PIDFile=/run/wmbusmeters/wmbusmeters-%i.pid
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
if diff $OLD_WMBAS $CURR_WMBAS 1>/dev/null
|
||||
then
|
||||
echo systemd: no changes to $CURR_WMBAS
|
||||
else
|
||||
echo systemd: updated $CURR_WMBAS
|
||||
rm $CURR_WMBAS
|
||||
SYSTEMD_NEEDS_RELOAD=true
|
||||
fi
|
||||
|
||||
####################################################################
|
||||
##
|
||||
## Create /etc/udev/rules.d/99-wmbus-usb-serial.rules
|
||||
## Remove any existing /etc/udev/rules.d/99-wmbus-usb-serial.rules
|
||||
## The new wmbuseters daemon is not going to use these.
|
||||
##
|
||||
|
||||
UDEV_NEEDS_RELOAD=false
|
||||
|
||||
|
||||
if [ "$ADDUDEVRULES" = "true" ]
|
||||
if [ -f "$ROOT"/etc/udev/rules.d/99-wmbus-usb-serial.rules ]
|
||||
then
|
||||
if [ -f "$ROOT"/etc/udev/rules.d/99-wmbus-usb-serial.rules ]
|
||||
then
|
||||
echo udev: removing "$ROOT"/etc/udev/rules.d/99-wmbus-usb-serial.rules
|
||||
echo udev: backup stored here: ~/old.wmbusmeters-wmbus-usb-serial.rules.backup
|
||||
cp "$ROOT"/etc/udev/rules.d/99-wmbus-usb-serial.rules ~/old.wmbusmeters-wmbus-usb-serial.rules.backup
|
||||
rm "$ROOT"/etc/udev/rules.d/99-wmbus-usb-serial.rules
|
||||
UDEV_NEEDS_RELOAD=true
|
||||
fi
|
||||
|
||||
if [ ! -f "$ROOT"/etc/udev/rules.d/99-wmbus-usb-serial.rules ]
|
||||
then
|
||||
mkdir -p "$ROOT"/etc/udev/rules.d
|
||||
# Create service file
|
||||
cat <<EOF > "$ROOT"/etc/udev/rules.d/99-wmbus-usb-serial.rules
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60",SYMLINK+="im871a_%n",MODE="0660", GROUP="wmbusmeters",TAG+="systemd",ENV{SYSTEMD_WANTS}="wmbusmeters@/dev/im871a_%n.service"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001",SYMLINK+="amb8465_%n",MODE="0660", GROUP="wmbusmeters",TAG+="systemd",ENV{SYSTEMD_WANTS}="wmbusmeters@/dev/amb8465_%n.service"
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="2838",SYMLINK+="rtlsdr_%n",MODE="0660", GROUP="wmbusmeters",TAG+="systemd",ENV{SYSTEMD_WANTS}="wmbusmeters@/dev/rtlsdr_%n.service"
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="2047", ATTRS{idProduct}=="0863",SYMLINK+="rfmrx2_%n",MODE="0660", GROUP="wmbusmeters",TAG+="systemd",ENV{SYSTEMD_WANTS}="wmbusmeters@/dev/rfmrx2_%n.service"
|
||||
EOF
|
||||
echo udev: installed "$ROOT"/etc/udev/rules.d/99-wmbus-usb-serial.rules
|
||||
else
|
||||
echo udev: "$ROOT"/etc/udev/rules.d/99-wmbus-usb-serial.rules unchanged
|
||||
fi
|
||||
echo "udev: removing "$ROOT"/etc/udev/rules.d/99-wmbus-usb-serial.rules"
|
||||
echo "udev: backup stored here: ~/old.wmbusmeters-wmbus-usb-serial.rules.backup"
|
||||
cp "$ROOT"/etc/udev/rules.d/99-wmbus-usb-serial.rules ~/old.wmbusmeters-wmbus-usb-serial.rules.backup
|
||||
rm "$ROOT"/etc/udev/rules.d/99-wmbus-usb-serial.rules
|
||||
UDEV_NEEDS_RELOAD=true
|
||||
fi
|
||||
|
||||
if [ "$SYSTEMD_NEEDS_RELOAD" = "true" ]
|
||||
then
|
||||
echo
|
||||
echo
|
||||
echo You need to reload systemd configuration! Please do:
|
||||
echo sudo systemctl daemon-reload
|
||||
echo "You need to reload systemd configuration! Please do:"
|
||||
echo "sudo systemctl daemon-reload"
|
||||
fi
|
||||
|
||||
if [ "$UDEV_NEEDS_RELOAD" = "true" ]
|
||||
then
|
||||
D=$(diff "$ROOT"/etc/udev/rules.d/99-wmbus-usb-serial.rules ~/old.wmbusmeters-wmbus-usb-serial.rules.backup)
|
||||
if [ "$D" != "" ]
|
||||
then
|
||||
echo
|
||||
echo
|
||||
echo You need to reload udev configuration! Please do:
|
||||
echo "sudo udevadm control --reload-rules"
|
||||
echo "sudo udevadm trigger"
|
||||
fi
|
||||
echo
|
||||
echo
|
||||
echo "You need to reload udev configuration! Please do:"
|
||||
echo "sudo udevadm control --reload-rules"
|
||||
echo "sudo udevadm trigger"
|
||||
fi
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
T1;1;1;2019-04-03 19:00:42.000;97;148;88888888;0x6e4401068888888805077a85006085bc2630713819512eb4cd87fba554fb43f67cf9654a68ee8e194088160df752e716238292e8af1ac20986202ee561d743602466915e42f1105d9c6782a54504e4f099e65a7656b930c73a30775122d2fdf074b5035cfaa7e0050bf32faae03a77
|
||||
{"media":"water","meter":"apator162","name":"ApWater","id":"88888888","total_m3":4.848,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
{"media":"water","meter":"apator162","name":"ApWater","id":"88888888","total_m3":4.848,"timestamp":"1111-11-11T11:11:11Z","device":"rtlwmbus[]","rssi_dbm":97}
|
||||
C1;1;1;2020-01-23 10:25:13.000;97;148;76348799;0x2A442D2C998734761B168D2091D37CAC21E1D68CDAFFCD3DC452BD802913FF7B1706CA9E355D6C2701CC24
|
||||
{"media":"cold water","meter":"multical21","name":"Vatten","id":"76348799","total_m3":6.408,"target_m3":6.408,"max_flow_m3h":0,"flow_temperature_c":127,"external_temperature_c":19,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"}
|
||||
{"media":"cold water","meter":"multical21","name":"Vatten","id":"76348799","total_m3":6.408,"target_m3":6.408,"max_flow_m3h":0,"flow_temperature_c":127,"external_temperature_c":19,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z","device":"rtlwmbus[]","rssi_dbm":97}
|
||||
T1;1;1;2019-04-03 19:00:42.000;97;148;77777777;0xAE44EE4D777777773C077A4400A025E78F4A01F9DCA029EDA03BA452686E8FA917507B29E5358B52D77C111EA4C41140290523F3F6B9F9261705E041C0CA41305004605F42D6C9464E5A04EEE227510BD0DC0983C665C3A5E4739C2082975476AC637BCDD39766AEF030502B6A7697BE9E1C49AF535C15470FCF8ADA36CAB9D0B2A1A8690F8DDCF70859F18B3414D8315B311A0AFA57325531587CB7E9CC110E807F24C190D7E635BEDAF4CAE8A161
|
||||
{"media":"water","meter":"supercom587","name":"Wasser","id":"77777777","total_m3":0,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
{"media":"water","meter":"supercom587","name":"Wasser","id":"77777777","total_m3":0,"timestamp":"1111-11-11T11:11:11Z","device":"rtlwmbus[]","rssi_dbm":97}
|
|
@ -1,4 +1,4 @@
|
|||
telegram=|2A442D2C998734761B168D2091D37CAC21576C78|02FF207100041308190000441308190000615B7F616713|+0
|
||||
{"media":"cold water","meter":"multical21","name":"MyTapWater","id":"76348799","total_m3":6.408,"target_m3":6.408,"max_flow_m3h":0,"flow_temperature_c":127,"external_temperature_c":19,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"}
|
||||
telegram=|2A442D2C998734761B168D2091D37CAC21576C78|02FF207100041308190000441308190000615B7F616713|+3
|
||||
telegram=|2A442D2C998734761B168D2091D37CAC21576C78|02FF207100041308190000441308190000615B7F616713|+5
|
||||
{"media":"cold water","meter":"multical21","name":"MyTapWater","id":"76348799","total_m3":6.408,"target_m3":6.408,"max_flow_m3h":0,"flow_temperature_c":127,"external_temperature_c":19,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","timestamp":"1111-11-11T11:11:11Z"}
|
||||
|
|
496
src/admin.cc
496
src/admin.cc
|
@ -15,24 +15,16 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include<curses.h>
|
||||
#include<menu.h>
|
||||
#include<string.h>
|
||||
#include<stdio.h>
|
||||
#include<stdlib.h>
|
||||
#include <syslog.h>
|
||||
#include<syslog.h>
|
||||
|
||||
#include"serial.h"
|
||||
#include"shell.h"
|
||||
#include"ui.h"
|
||||
#include"wmbus.h"
|
||||
|
||||
#define BG_PAIR 1
|
||||
#define WIN_PAIR 2
|
||||
#define TITLE_PAIR 3
|
||||
#define HILIGHT_PAIR 4
|
||||
|
||||
#include <menu.h>
|
||||
|
||||
bool running_as_root_ = false;
|
||||
bool member_of_dialout_ = false;
|
||||
|
||||
|
@ -62,7 +54,8 @@ LIST_OF_MAIN_MENU
|
|||
#define LIST_OF_WMBUS_RECEIVERS \
|
||||
X(AMB8465, "amb8465") \
|
||||
X(CUL, "cul") \
|
||||
X(IM871A, "im871a")
|
||||
X(IM871A, "im871a") \
|
||||
X(RC1180, "rc1180")
|
||||
|
||||
enum class ReceiversType {
|
||||
#define X(name,description) name,
|
||||
|
@ -78,30 +71,23 @@ LIST_OF_WMBUS_RECEIVERS
|
|||
};
|
||||
|
||||
bool detectIfRoot();
|
||||
string userName();
|
||||
bool detectIfMemberOfGroup(string group);
|
||||
void detectProcesses(string cmd, vector<int> *pids);
|
||||
void detectWMBUSReceiver();
|
||||
void resetWMBUSReceiver();
|
||||
void probeFor(string type, AccessCheck(*func)(string,SerialCommunicationManager*));
|
||||
void probeFor(string type, AccessCheck(*func)(Detected*,shared_ptr<SerialCommunicationManager>));
|
||||
|
||||
void printAt(WINDOW *win, int y, int x, const char *str, chtype color);
|
||||
void printMiddle(WINDOW *win, int y, int width, const char *str, chtype color);
|
||||
void stopDaemon();
|
||||
void startDaemon();
|
||||
|
||||
void alwaysOnScreen();
|
||||
int selectFromMenu(const char *title, const char *menu[]);
|
||||
int selectFromMenu(const char *title, vector<string> menu);
|
||||
void displayInformationAndWait(string title, vector<string> entries, int px=-1, int py=-1);
|
||||
void displayInformationNoWait(WINDOW **win, string title, vector<string> entries, int px=-1, int py=-1);
|
||||
|
||||
void notImplementedYet(string msg);
|
||||
|
||||
int screen_width, screen_height;
|
||||
unique_ptr<SerialCommunicationManager> handler;
|
||||
shared_ptr<SerialCommunicationManager> handler;
|
||||
|
||||
WINDOW *status_window;
|
||||
WINDOW *serial_ports_window;
|
||||
WINDOW *processes_window;
|
||||
|
||||
void alwaysOnScreen();
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc == 2 && (!strcmp(argv[1], "--debug") || !strcmp(argv[1], "--trace")))
|
||||
|
@ -112,27 +98,33 @@ int main(int argc, char **argv)
|
|||
enableSyslog();
|
||||
}
|
||||
|
||||
initUI();
|
||||
clear();
|
||||
|
||||
/*
|
||||
refresh();
|
||||
int x=0;
|
||||
int y=0;
|
||||
for (int i=0;i<10; ++i) {
|
||||
printAt(stdscr, y, x, "HEJSAN", COLOR_PAIR(BG_PAIR));
|
||||
y++;
|
||||
x++;
|
||||
};
|
||||
refresh();
|
||||
for(;;) {}
|
||||
endwin();
|
||||
return 0;
|
||||
*/
|
||||
running_as_root_ = detectIfRoot();
|
||||
member_of_dialout_ = detectIfMemberOfGroup("dialout");
|
||||
|
||||
handler = createSerialCommunicationManager(0, 0, false);
|
||||
handler = createSerialCommunicationManager(0, 0);
|
||||
|
||||
initscr();
|
||||
getmaxyx(stdscr, screen_height, screen_width);
|
||||
start_color();
|
||||
cbreak();
|
||||
curs_set(0);
|
||||
noecho();
|
||||
keypad(stdscr, TRUE);
|
||||
|
||||
init_pair(BG_PAIR, COLOR_WHITE, COLOR_BLUE);
|
||||
init_pair(WIN_PAIR, COLOR_BLACK, COLOR_WHITE);
|
||||
init_pair(TITLE_PAIR, COLOR_WHITE, COLOR_CYAN);
|
||||
init_pair(HILIGHT_PAIR, COLOR_WHITE, COLOR_RED);
|
||||
wbkgd(stdscr, COLOR_PAIR(BG_PAIR));
|
||||
initUI();
|
||||
|
||||
bool running = true;
|
||||
|
||||
registerUpdateCB(alwaysOnScreen);
|
||||
alwaysOnScreen();
|
||||
|
||||
do
|
||||
|
@ -157,84 +149,22 @@ int main(int argc, char **argv)
|
|||
notImplementedYet("Edit meters");
|
||||
break;
|
||||
case MainMenuType::STOP_DAEMON:
|
||||
notImplementedYet("Stop daemon");
|
||||
stopDaemon();
|
||||
break;
|
||||
case MainMenuType::START_DAEMON:
|
||||
notImplementedYet("Start daemon");
|
||||
startDaemon();
|
||||
break;
|
||||
case MainMenuType::EXIT_ADMIN:
|
||||
running = false;
|
||||
break;
|
||||
}
|
||||
} while (running);
|
||||
|
||||
|
||||
endwin();
|
||||
}
|
||||
|
||||
void printAt(WINDOW *win, int y, int x, const char *str, chtype color)
|
||||
{
|
||||
wattron(win, color);
|
||||
mvwprintw(win, y, x, "%s", str);
|
||||
wattroff(win, color);
|
||||
refresh();
|
||||
}
|
||||
|
||||
void printMiddle(WINDOW *win, int y, int width, const char *str, chtype color)
|
||||
{
|
||||
int len = strlen(str);
|
||||
int wh, ww;
|
||||
|
||||
getyx(win, wh, ww);
|
||||
((void)wh);
|
||||
|
||||
int x = (width-len)/2;
|
||||
wattron(win, color);
|
||||
mvwprintw(win, y, x, "%s", str);
|
||||
wattroff(win, color);
|
||||
refresh();
|
||||
}
|
||||
|
||||
int countEntries(const char *entries[])
|
||||
{
|
||||
int i = 0;
|
||||
for (; entries[i] != 0; ++i);
|
||||
return i+1;
|
||||
}
|
||||
|
||||
int maxWidth(const char *entries[])
|
||||
{
|
||||
int max = 0;
|
||||
for (int i=0; entries[i] != 0; ++i)
|
||||
{
|
||||
int n = strlen(entries[i]);
|
||||
if (max < n) max = n;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
int maxWidth(vector<string> entries)
|
||||
{
|
||||
int max = 0;
|
||||
for (string& s : entries)
|
||||
{
|
||||
int n = s.length();
|
||||
if (max < n) max = n;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
void alwaysOnScreen()
|
||||
{
|
||||
static uchar ticktock = 0;
|
||||
|
||||
vector<string> info;
|
||||
ticktock++;
|
||||
|
||||
if (running_as_root_ == false)
|
||||
{
|
||||
info.push_back("Not running as root!");
|
||||
}
|
||||
if (member_of_dialout_ == false)
|
||||
{
|
||||
info.push_back("Not member of dialout!");
|
||||
|
@ -268,9 +198,19 @@ void alwaysOnScreen()
|
|||
}
|
||||
}
|
||||
|
||||
displayInformationNoWait(&status_window, (ticktock%2==0)?"Status ":"Status.", info, 1, 1);
|
||||
vector<string> status;
|
||||
time_t now = time(NULL);
|
||||
struct tm nowt {};
|
||||
localtime_r(&now, &nowt);
|
||||
status.push_back("wmbusmeters-admin");
|
||||
status.push_back(strdatetimesec(&nowt));
|
||||
string name = "["+userName()+"]";
|
||||
status.push_back(name);
|
||||
displayStatusLineNoWait(&status_window, status, 0, 0);
|
||||
|
||||
vector<string> devices = handler->listSerialDevices();
|
||||
displayInformationNoWait(&status_window, "Problems", info, 2, 2);
|
||||
|
||||
vector<string> devices = handler->listSerialTTYs();
|
||||
if (devices.size() == 0)
|
||||
{
|
||||
devices.push_back("No serial ports found!");
|
||||
|
@ -280,252 +220,8 @@ void alwaysOnScreen()
|
|||
displayInformationNoWait(&serial_ports_window, "Serial ports", devices, 1, 15);
|
||||
|
||||
erase();
|
||||
redrawwin(status_window);
|
||||
redrawwin(serial_ports_window);
|
||||
}
|
||||
|
||||
int selectFromMenu(const char *title, const char *entries[])
|
||||
{
|
||||
vector<string> menu;
|
||||
int n_choices = countEntries(entries);
|
||||
|
||||
for (int i=0; i<n_choices; ++i)
|
||||
{
|
||||
if (entries[i] == NULL) break;
|
||||
menu.push_back(entries[i]);
|
||||
}
|
||||
return selectFromMenu(title, menu);
|
||||
}
|
||||
|
||||
int selectFromMenu(const char *title, vector<string> entries)
|
||||
{
|
||||
int selected = -1;
|
||||
ITEM **menu_items;
|
||||
int c;
|
||||
MENU *menu;
|
||||
WINDOW *frame_window, *menu_window;
|
||||
int n_choices, i;
|
||||
|
||||
n_choices = entries.size()+1;
|
||||
menu_items = (ITEM **)calloc(n_choices, sizeof(ITEM *));
|
||||
for(i = 0; i < n_choices-1; ++i)
|
||||
{
|
||||
menu_items[i] = new_item(entries[i].c_str(), NULL);
|
||||
}
|
||||
menu_items[n_choices-1] = NULL;
|
||||
|
||||
menu = new_menu(menu_items);
|
||||
int mw = 0;
|
||||
int mh = 0;
|
||||
scale_menu(menu, &mh, &mw);
|
||||
int w = mw+2;
|
||||
int h = mh+4;
|
||||
if (w-2 < (int)strlen(title))
|
||||
{
|
||||
w = (int)strlen(title)+2;
|
||||
}
|
||||
int x = screen_width/2-w/2;
|
||||
int y = screen_height/2-h/2;
|
||||
frame_window = newwin(h, w, y, x);
|
||||
|
||||
int mx = (w-mw)/2;
|
||||
int my = 3;
|
||||
menu_window = derwin(frame_window, mh, mw, my, mx);
|
||||
|
||||
set_menu_fore(menu, COLOR_PAIR(HILIGHT_PAIR));
|
||||
set_menu_back(menu, COLOR_PAIR(WIN_PAIR));
|
||||
set_menu_grey(menu, COLOR_PAIR(3));
|
||||
|
||||
keypad(frame_window, TRUE);
|
||||
|
||||
set_menu_win(menu, frame_window);
|
||||
set_menu_sub(menu, menu_window);
|
||||
|
||||
set_menu_mark(menu, ">");
|
||||
|
||||
box(frame_window, 0, 0);
|
||||
wbkgd(frame_window, COLOR_PAIR(WIN_PAIR));
|
||||
|
||||
printMiddle(frame_window, 1, w, title, COLOR_PAIR(WIN_PAIR));
|
||||
mvwaddch(frame_window, 2, 0, ACS_LTEE);
|
||||
mvwhline(frame_window, 2, 1, ACS_HLINE, 38);
|
||||
mvwaddch(frame_window, 2, w-1, ACS_RTEE);
|
||||
refresh();
|
||||
|
||||
post_menu(menu);
|
||||
wrefresh(frame_window);
|
||||
|
||||
alwaysOnScreen();
|
||||
|
||||
wtimeout(frame_window, 1000);
|
||||
|
||||
bool running = true;
|
||||
do
|
||||
{
|
||||
c = wgetch(frame_window);
|
||||
ITEM *cur = current_item(menu);
|
||||
selected = item_index(cur);
|
||||
switch(c)
|
||||
{
|
||||
case ERR:
|
||||
alwaysOnScreen();
|
||||
redrawwin(frame_window);
|
||||
break;
|
||||
case KEY_DOWN:
|
||||
if (selected < n_choices-2)
|
||||
{
|
||||
menu_driver(menu, REQ_DOWN_ITEM);
|
||||
}
|
||||
else
|
||||
{
|
||||
menu_driver(menu, REQ_FIRST_ITEM);
|
||||
}
|
||||
break;
|
||||
case KEY_UP:
|
||||
if (selected > 0)
|
||||
{
|
||||
menu_driver(menu, REQ_UP_ITEM);
|
||||
}
|
||||
else
|
||||
{
|
||||
menu_driver(menu, REQ_LAST_ITEM);
|
||||
}
|
||||
break;
|
||||
case '\n':
|
||||
running = false;
|
||||
break;
|
||||
}
|
||||
wrefresh(frame_window);
|
||||
} while (running);
|
||||
|
||||
unpost_menu(menu);
|
||||
free_menu(menu);
|
||||
delwin(frame_window);
|
||||
erase();
|
||||
refresh();
|
||||
for(i = 0; i < n_choices; ++i)
|
||||
{
|
||||
free_item(menu_items[i]);
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
void displayInformationAndWait(string title, vector<string> entries, int px, int py)
|
||||
{
|
||||
WINDOW *frame_window;
|
||||
|
||||
alwaysOnScreen();
|
||||
|
||||
int mw = maxWidth(entries)+1;
|
||||
int mh = entries.size();
|
||||
int w = mw+2;
|
||||
int h = mh+4;
|
||||
if (w-2 < (int)title.length())
|
||||
{
|
||||
w = (int)title.length()+2;
|
||||
}
|
||||
int x = screen_width/2-w/2;
|
||||
int y = screen_height/2-h/2;
|
||||
if (px != -1)
|
||||
{
|
||||
x = px;
|
||||
}
|
||||
if (py != -1)
|
||||
{
|
||||
y = py;
|
||||
}
|
||||
frame_window = newwin(h, w, y, x);
|
||||
|
||||
keypad(frame_window, TRUE);
|
||||
|
||||
box(frame_window, 0, 0);
|
||||
wbkgd(frame_window, COLOR_PAIR(WIN_PAIR));
|
||||
|
||||
printMiddle(frame_window, 1, w, title.c_str(), COLOR_PAIR(WIN_PAIR));
|
||||
mvwaddch(frame_window, 2, 0, ACS_LTEE);
|
||||
mvwhline(frame_window, 2, 1, ACS_HLINE, 38);
|
||||
mvwaddch(frame_window, 2, w-1, ACS_RTEE);
|
||||
//refresh();
|
||||
|
||||
int r = 3;
|
||||
for (string e : entries)
|
||||
{
|
||||
printAt(frame_window, r, 1, e.c_str(), COLOR_PAIR(WIN_PAIR));
|
||||
r++;
|
||||
}
|
||||
wrefresh(frame_window);
|
||||
wtimeout(frame_window, 1000);
|
||||
|
||||
bool running = true;
|
||||
do
|
||||
{
|
||||
int c = wgetch(frame_window);
|
||||
switch(c)
|
||||
{
|
||||
case ERR:
|
||||
alwaysOnScreen();
|
||||
redrawwin(frame_window);
|
||||
break;
|
||||
case 27:
|
||||
case '\n':
|
||||
running = false;
|
||||
break;
|
||||
}
|
||||
wrefresh(frame_window);
|
||||
} while (running);
|
||||
|
||||
delwin(frame_window);
|
||||
erase();
|
||||
refresh();
|
||||
}
|
||||
|
||||
void displayInformationNoWait(WINDOW **winp, string title, vector<string> entries, int px, int py)
|
||||
{
|
||||
WINDOW *win = *winp;
|
||||
|
||||
if (win != NULL)
|
||||
{
|
||||
delwin(win);
|
||||
*winp = NULL;
|
||||
}
|
||||
int mw = maxWidth(entries)+1;
|
||||
int mh = entries.size();
|
||||
int w = mw+2;
|
||||
int h = mh+4;
|
||||
if (w-2 < (int)title.length())
|
||||
{
|
||||
w = (int)title.length()+2;
|
||||
}
|
||||
int x = screen_width/2-w/2;
|
||||
int y = screen_height/2-h/2;
|
||||
if (px != -1)
|
||||
{
|
||||
x = px;
|
||||
}
|
||||
if (py != -1)
|
||||
{
|
||||
y = py;
|
||||
}
|
||||
win = newwin(h, w, y, x);
|
||||
*winp = win;
|
||||
|
||||
box(win, 0, 0);
|
||||
wbkgd(win, COLOR_PAIR(WIN_PAIR));
|
||||
|
||||
printMiddle(win, 1, w, title.c_str(), COLOR_PAIR(WIN_PAIR));
|
||||
mvwaddch(win, 2, 0, ACS_LTEE);
|
||||
mvwhline(win, 2, 1, ACS_HLINE, 38);
|
||||
mvwaddch(win, 2, w-1, ACS_RTEE);
|
||||
// refresh();
|
||||
|
||||
int r = 3;
|
||||
for (string e : entries)
|
||||
{
|
||||
printAt(win, r, 1, e.c_str(), COLOR_PAIR(WIN_PAIR));
|
||||
r++;
|
||||
}
|
||||
wrefresh(win);
|
||||
wrefresh(status_window);
|
||||
wrefresh(serial_ports_window);
|
||||
}
|
||||
|
||||
void detectWMBUSReceiver()
|
||||
|
@ -542,6 +238,9 @@ void detectWMBUSReceiver()
|
|||
case ReceiversType::IM871A:
|
||||
probeFor("im871a", detectIM871A);
|
||||
break;
|
||||
case ReceiversType::RC1180:
|
||||
probeFor("rc1180", detectRC1180);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -552,7 +251,7 @@ void resetWMBUSReceiver()
|
|||
{
|
||||
case ReceiversType::AMB8465:
|
||||
{
|
||||
vector<string> devices = handler->listSerialDevices();
|
||||
vector<string> devices = handler->listSerialTTYs();
|
||||
if (devices.size() == 0)
|
||||
{
|
||||
vector<string> entries;
|
||||
|
@ -562,7 +261,7 @@ void resetWMBUSReceiver()
|
|||
int c = selectFromMenu("Select device", devices);
|
||||
string device = devices[c];
|
||||
int was_baud = 0;
|
||||
AccessCheck ac = factoryResetAMB8465(device, handler.get(), &was_baud);
|
||||
AccessCheck ac = factoryResetAMB8465(device, handler, &was_baud);
|
||||
if (ac == AccessCheck::AccessOK)
|
||||
{
|
||||
vector<string> entries;
|
||||
|
@ -583,26 +282,23 @@ void resetWMBUSReceiver()
|
|||
case ReceiversType::IM871A:
|
||||
notImplementedYet("Resetting im871a");
|
||||
break;
|
||||
case ReceiversType::RC1180:
|
||||
notImplementedYet("Resetting RC1180");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void notImplementedYet(string msg)
|
||||
void probeFor(string type, AccessCheck (*check)(Detected*,shared_ptr<SerialCommunicationManager>))
|
||||
{
|
||||
vector<string> entries;
|
||||
entries.push_back(msg);
|
||||
displayInformationAndWait("Not implemented yet", entries);
|
||||
}
|
||||
|
||||
void probeFor(string type, AccessCheck (*check)(string,SerialCommunicationManager*))
|
||||
{
|
||||
vector<string> devices = handler->listSerialDevices();
|
||||
Detected detected {};
|
||||
vector<string> devices = handler->listSerialTTYs();
|
||||
vector<string> entries;
|
||||
for (string& device : devices)
|
||||
{
|
||||
string tty = "?";
|
||||
AccessCheck ac = checkAccessAndDetect(
|
||||
handler.get(),
|
||||
[=](string d, SerialCommunicationManager* m){ return check(d, m);},
|
||||
handler,
|
||||
[&](string d, shared_ptr<SerialCommunicationManager> m){ detected.specified_device.file=d; return check(&detected, m);},
|
||||
type,
|
||||
device);
|
||||
|
||||
|
@ -638,6 +334,18 @@ bool detectIfRoot()
|
|||
return out == "0\n";
|
||||
}
|
||||
|
||||
string userName()
|
||||
{
|
||||
vector<string> args;
|
||||
vector<string> envs;
|
||||
args.push_back("-u");
|
||||
args.push_back("-n");
|
||||
string out;
|
||||
invokeShellCaptureOutput("/usr/bin/id", args, envs, &out, true);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
bool detectIfMemberOfGroup(string group)
|
||||
{
|
||||
vector<string> args;
|
||||
|
@ -656,21 +364,49 @@ bool detectIfMemberOfGroup(string group)
|
|||
return false;
|
||||
}
|
||||
|
||||
void detectProcesses(string cmd, vector<int> *pids)
|
||||
{
|
||||
vector<string> args;
|
||||
vector<string> envs;
|
||||
args.push_back(cmd);
|
||||
string out;
|
||||
invokeShellCaptureOutput("/bin/pidof", args, envs, &out, true);
|
||||
|
||||
char buf[out.size()+1];
|
||||
strcpy(buf, out.c_str());
|
||||
char *pch;
|
||||
pch = strtok (buf," \n");
|
||||
while (pch != NULL)
|
||||
{
|
||||
pids->push_back(atoi(pch));
|
||||
pch = strtok (NULL, " \n");
|
||||
}
|
||||
void stopDaemon()
|
||||
{
|
||||
vector<string> info;
|
||||
info.push_back("Enter sudo password to execute:");
|
||||
info.push_back("systemctl stop wmbusmeters");
|
||||
|
||||
debug("(passowrd) calling inputfield\n");
|
||||
string pwd = inputField("Stop daemon", info, "Password");
|
||||
debug("(passowrd) GOT %s\n", pwd.c_str());
|
||||
//string pwd = displayInformationAndInput("Stop daemon", info, 1, 1);
|
||||
//vector<string> args;
|
||||
//vector<string> envs;
|
||||
//args.push_back("gurka");
|
||||
// string out;
|
||||
// invokeShellCaptureOutput("systemctl stop wmbusmeters", args, envs, &out, true);
|
||||
}
|
||||
|
||||
void startDaemon()
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
static char* trim_whitespaces(char *str)
|
||||
{
|
||||
char *end;
|
||||
|
||||
// trim leading space
|
||||
while(isspace(*str))
|
||||
str++;
|
||||
|
||||
if(*str == 0) // all spaces?
|
||||
return str;
|
||||
|
||||
// trim trailing space
|
||||
end = str + strnlen(str, 128) - 1;
|
||||
|
||||
while(end > str && isspace(*end))
|
||||
end--;
|
||||
|
||||
// write new null terminator
|
||||
*(end+1) = '\0';
|
||||
|
||||
return str;
|
||||
}
|
||||
*/
|
||||
|
|
103
src/cmdline.cc
103
src/cmdline.cc
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2017-2019 Fredrik Öhrström
|
||||
Copyright (C) 2017-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
|
||||
#include"cmdline.h"
|
||||
#include"config.h"
|
||||
#include"meters.h"
|
||||
#include"util.h"
|
||||
|
||||
|
@ -24,7 +23,7 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
unique_ptr<Configuration> parseCommandLine(int argc, char **argv) {
|
||||
shared_ptr<Configuration> parseCommandLine(int argc, char **argv) {
|
||||
|
||||
Configuration * c = new Configuration;
|
||||
|
||||
|
@ -62,16 +61,17 @@ unique_ptr<Configuration> parseCommandLine(int argc, char **argv) {
|
|||
break;
|
||||
}
|
||||
c->pid_file = argv[i];
|
||||
return unique_ptr<Configuration>(c);
|
||||
return shared_ptr<Configuration>(c);
|
||||
}
|
||||
if (argc < 2) {
|
||||
c->need_help = true;
|
||||
return unique_ptr<Configuration>(c);
|
||||
return shared_ptr<Configuration>(c);
|
||||
}
|
||||
while (argv[i] && argv[i][0] == '-') {
|
||||
while (argv[i] && argv[i][0] == '-')
|
||||
{
|
||||
if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-help") || !strcmp(argv[i], "--help")) {
|
||||
c->need_help = true;
|
||||
return unique_ptr<Configuration>(c);
|
||||
return shared_ptr<Configuration>(c);
|
||||
}
|
||||
if (!strcmp(argv[i], "--silence")) {
|
||||
c->silence = true;
|
||||
|
@ -85,17 +85,23 @@ unique_ptr<Configuration> parseCommandLine(int argc, char **argv) {
|
|||
}
|
||||
if (!strcmp(argv[i], "--version")) {
|
||||
c->version = true;
|
||||
return unique_ptr<Configuration>(c);
|
||||
return shared_ptr<Configuration>(c);
|
||||
}
|
||||
if (!strcmp(argv[i], "--license")) {
|
||||
c->license = true;
|
||||
return unique_ptr<Configuration>(c);
|
||||
return shared_ptr<Configuration>(c);
|
||||
}
|
||||
if (!strcmp(argv[i], "--debug")) {
|
||||
c->debug = true;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "--trace")) {
|
||||
c->debug = true;
|
||||
c->trace = true;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "--internaltesting")) {
|
||||
c->internaltesting = true;
|
||||
i++;
|
||||
|
@ -106,22 +112,22 @@ unique_ptr<Configuration> parseCommandLine(int argc, char **argv) {
|
|||
if (lms.bits() == 0) {
|
||||
error("Unknown link mode \"%s\"!\n", argv[i]+11);
|
||||
}
|
||||
if (c->link_mode_configured) {
|
||||
if (c->linkmodes_configured) {
|
||||
error("You have already specified a link mode!\n");
|
||||
}
|
||||
c->listen_to_link_modes = lms;
|
||||
c->link_mode_configured = true;
|
||||
c->linkmodes = lms;
|
||||
c->linkmodes_configured = true;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
LinkMode lm = isLinkModeOption(argv[i]);
|
||||
if (lm != LinkMode::UNKNOWN) {
|
||||
if (c->link_mode_configured) {
|
||||
if (c->linkmodes_configured) {
|
||||
error("You have already specified a link mode!\n");
|
||||
}
|
||||
c->listen_to_link_modes.addLinkMode(lm);
|
||||
c->link_mode_configured = true;
|
||||
c->linkmodes.addLinkMode(lm);
|
||||
c->linkmodes_configured = true;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
@ -135,7 +141,7 @@ unique_ptr<Configuration> parseCommandLine(int argc, char **argv) {
|
|||
{
|
||||
c->useconfig = true;
|
||||
c->config_root = "";
|
||||
return unique_ptr<Configuration>(c);
|
||||
return shared_ptr<Configuration>(c);
|
||||
}
|
||||
else if (strlen(argv[i]) > 12 && argv[i][11] == '=')
|
||||
{
|
||||
|
@ -173,7 +179,7 @@ unique_ptr<Configuration> parseCommandLine(int argc, char **argv) {
|
|||
if (i+1 < argc) {
|
||||
error("Usage error: --useconfig can only be followed by --device= and --listento=\n");
|
||||
}
|
||||
return unique_ptr<Configuration>(c);
|
||||
return shared_ptr<Configuration>(c);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "--reload")) {
|
||||
|
@ -181,7 +187,7 @@ unique_ptr<Configuration> parseCommandLine(int argc, char **argv) {
|
|||
if (i > 1 || argc > 2) {
|
||||
error("Usage error: --reload implies no other arguments on the command line.\n");
|
||||
}
|
||||
return unique_ptr<Configuration>(c);
|
||||
return shared_ptr<Configuration>(c);
|
||||
}
|
||||
if (!strncmp(argv[i], "--format=", 9))
|
||||
{
|
||||
|
@ -345,8 +351,13 @@ unique_ptr<Configuration> parseCommandLine(int argc, char **argv) {
|
|||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!strncmp(argv[i], "--usestderr=", 10)) {
|
||||
c->use_stderr = true;
|
||||
if (!strncmp(argv[i], "--usestderr", 11)) {
|
||||
c->use_stderr_for_log = true;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!strncmp(argv[i], "--usestdoutforlogging", 13)) {
|
||||
c->use_stderr_for_log = false;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
@ -381,13 +392,27 @@ unique_ptr<Configuration> parseCommandLine(int argc, char **argv) {
|
|||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!strncmp(argv[i], "--listenvs", 10)) {
|
||||
if (!strncmp(argv[i], "--listenvs=", 11)) {
|
||||
c->list_shell_envs = true;
|
||||
c->list_meter = string(argv[i]+11);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!strncmp(argv[i], "--listfields", 12)) {
|
||||
if (!strncmp(argv[i], "--listfields=", 13)) {
|
||||
c->list_fields = true;
|
||||
c->list_meter = string(argv[i]+13);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!strncmp(argv[i], "--listmeters=", 13)) {
|
||||
c->list_meters = true;
|
||||
c->list_meters_search = string(argv[i]+13);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
else if (!strncmp(argv[i], "--listmeters", 12)) {
|
||||
c->list_meters = true;
|
||||
c->list_meters_search = "";
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
@ -404,10 +429,10 @@ unique_ptr<Configuration> parseCommandLine(int argc, char **argv) {
|
|||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!strncmp(argv[i], "--reopenafter=", 12) && strlen(argv[i]) > 14) {
|
||||
c->reopenafter = parseTime(argv[i]+14);
|
||||
if (c->reopenafter <= 0) {
|
||||
error("Not a valid time to reopen after. \"%s\"\n", argv[i]+14);
|
||||
if (!strncmp(argv[i], "--resetafter=", 13) && strlen(argv[i]) > 13) {
|
||||
c->resetafter = parseTime(argv[i]+13);
|
||||
if (c->resetafter <= 0) {
|
||||
error("Not a valid time to regularly reset after. \"%s\"\n", argv[i]+13);
|
||||
}
|
||||
i++;
|
||||
continue;
|
||||
|
@ -438,18 +463,20 @@ unique_ptr<Configuration> parseCommandLine(int argc, char **argv) {
|
|||
error("Unknown option \"%s\"\n", argv[i]);
|
||||
}
|
||||
|
||||
char *extra = argv[i] ? strchr(argv[i], ':') : NULL ;
|
||||
if (extra) {
|
||||
*extra = 0;
|
||||
extra++;
|
||||
c->device_extra = extra;
|
||||
while (argv[i])
|
||||
{
|
||||
bool ok = handleDevice(c, argv[i]);
|
||||
if (!ok) break;
|
||||
i++;
|
||||
}
|
||||
if (argv[i]) {
|
||||
c->device = argv[i];
|
||||
}
|
||||
i++;
|
||||
if (c->device.length() == 0) {
|
||||
error("You must supply the usb device to which the wmbus dongle is connected.\n");
|
||||
|
||||
if (c->supplied_wmbus_devices.size() == 0 &&
|
||||
c->use_auto_detect == false &&
|
||||
!c->list_shell_envs &&
|
||||
!c->list_fields &&
|
||||
!c->list_meters)
|
||||
{
|
||||
error("You must supply at least one device (eg auto:c1) to receive wmbus telegrams.\n");
|
||||
}
|
||||
|
||||
if ((argc-i) % 4 != 0) {
|
||||
|
@ -499,5 +526,5 @@ unique_ptr<Configuration> parseCommandLine(int argc, char **argv) {
|
|||
c->meters.push_back(MeterInfo(name, type, id, key, modes, no_meter_shells, no_meter_jsons));
|
||||
}
|
||||
|
||||
return unique_ptr<Configuration>(c);
|
||||
return shared_ptr<Configuration>(c);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2017-2019 Fredrik Öhrström
|
||||
Copyright (C) 2017-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -26,6 +26,6 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
unique_ptr<Configuration> parseCommandLine(int argc, char **argv);
|
||||
shared_ptr<Configuration> parseCommandLine(int argc, char **argv);
|
||||
|
||||
#endif
|
||||
|
|
144
src/config.cc
144
src/config.cc
|
@ -189,34 +189,101 @@ void handleInternalTesting(Configuration *c, string value)
|
|||
}
|
||||
}
|
||||
|
||||
void handleDevice(Configuration *c, string device)
|
||||
void handleResetAfter(Configuration *c, string s)
|
||||
{
|
||||
// device can be:
|
||||
// /dev/ttyUSB00
|
||||
// auto
|
||||
// rtlwmbus:/usr/bin/rtl_sdr -f 868.9M -s 1600000 - | /usr/bin/rtl_wmbus
|
||||
// simulation....txt (read telegrams from file)
|
||||
size_t p = device.find (':');
|
||||
if (p != string::npos)
|
||||
if (s.length() >= 1)
|
||||
{
|
||||
c->device_extra = device.substr(p+1);
|
||||
c->device = device.substr(0,p);
|
||||
} else {
|
||||
c->device = device;
|
||||
c->resetafter = parseTime(s.c_str());
|
||||
if (c->resetafter <= 0)
|
||||
{
|
||||
warning("Not a valid time to reset wmbus devices after. \"%s\"\n", s.c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
warning("Reset after must be a valid number of seconds.\n");
|
||||
}
|
||||
}
|
||||
|
||||
bool handleDevice(Configuration *c, string devicefile)
|
||||
{
|
||||
SpecifiedDevice specified_device;
|
||||
bool ok = specified_device.parse(devicefile);
|
||||
if (ok)
|
||||
{
|
||||
// Number the devices
|
||||
specified_device.index = c->supplied_wmbus_devices.size();
|
||||
|
||||
if (specified_device.linkmodes != "")
|
||||
{
|
||||
// Prevent an early warning in start
|
||||
// since we at least have one configured linkmode.
|
||||
c->linkmodes_configured = true;
|
||||
c->linkmodes.unionLinkModeSet(parseLinkModes(specified_device.linkmodes));
|
||||
}
|
||||
else
|
||||
{
|
||||
// No linkmode set, but if simulation, stdin and file,
|
||||
// then assume that it will produce telegrams on all linkmodes.
|
||||
if (specified_device.is_simulation || specified_device.is_stdin || specified_device.is_file)
|
||||
{
|
||||
c->linkmodes_configured = true;
|
||||
c->linkmodes.unionLinkModeSet(LinkModeBits::Any_bit);
|
||||
}
|
||||
|
||||
if (specified_device.type == "rtlwmbus")
|
||||
{
|
||||
c->linkmodes_configured = true;
|
||||
c->linkmodes.addLinkMode(LinkMode::C1);
|
||||
c->linkmodes.addLinkMode(LinkMode::T1);
|
||||
}
|
||||
}
|
||||
|
||||
if (specified_device.is_stdin ||
|
||||
specified_device.is_file ||
|
||||
specified_device.is_simulation ||
|
||||
specified_device.command != "")
|
||||
{
|
||||
if (c->single_device_override)
|
||||
{
|
||||
error("You can only specify one stdin or one file or one command!\n");
|
||||
}
|
||||
if (c->use_auto_detect)
|
||||
{
|
||||
error("You cannot mix auto with stdin or a file.\n");
|
||||
}
|
||||
if (specified_device.is_simulation) c->simulation_found = true;
|
||||
c->single_device_override = true;
|
||||
}
|
||||
if (specified_device.type == "auto")
|
||||
{
|
||||
c->use_auto_detect = true;
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
error("You cannot use auto on macosx. You must specify the device tty or rtlwmbus.\n");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
c->supplied_wmbus_devices.push_back(specified_device);
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
void handleListenTo(Configuration *c, string mode)
|
||||
{
|
||||
LinkModeSet lms = parseLinkModes(mode.c_str());
|
||||
if (lms.bits() == 0) {
|
||||
if (lms.bits() == 0)
|
||||
{
|
||||
error("Unknown link mode \"%s\"!\n", mode.c_str());
|
||||
}
|
||||
if (c->link_mode_configured) {
|
||||
if (c->linkmodes_configured)
|
||||
{
|
||||
error("You have already specified a link mode!\n");
|
||||
}
|
||||
c->listen_to_link_modes = lms;
|
||||
c->link_mode_configured = true;
|
||||
|
||||
c->linkmodes = lms;
|
||||
c->linkmodes_configured = true;
|
||||
}
|
||||
|
||||
void handleLogtelegrams(Configuration *c, string logtelegrams)
|
||||
|
@ -331,22 +398,6 @@ void handleFormat(Configuration *c, string format)
|
|||
}
|
||||
}
|
||||
|
||||
void handleReopenAfter(Configuration *c, string s)
|
||||
{
|
||||
if (s.length() >= 1)
|
||||
{
|
||||
c->reopenafter = parseTime(s.c_str());
|
||||
if (c->reopenafter <= 0)
|
||||
{
|
||||
warning("Not a valid time to reopen after. \"%s\"\n", s.c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
warning("Reopen after must be a valid number of seconds.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void handleAlarmTimeout(Configuration *c, string s)
|
||||
{
|
||||
if (s.length() >= 1)
|
||||
|
@ -430,7 +481,7 @@ void handleJson(Configuration *c, string json)
|
|||
c->jsons.push_back(json);
|
||||
}
|
||||
|
||||
unique_ptr<Configuration> loadConfiguration(string root, string device_override, string listento_override)
|
||||
shared_ptr<Configuration> loadConfiguration(string root, string device_override, string listento_override)
|
||||
{
|
||||
Configuration *c = new Configuration;
|
||||
|
||||
|
@ -465,13 +516,13 @@ unique_ptr<Configuration> loadConfiguration(string root, string device_override,
|
|||
else if (p.first == "meterfilestimestamp") handleMeterfilesTimestamp(c, p.second);
|
||||
else if (p.first == "logfile") handleLogfile(c, p.second);
|
||||
else if (p.first == "format") handleFormat(c, p.second);
|
||||
else if (p.first == "reopenafter") handleReopenAfter(c, p.second);
|
||||
else if (p.first == "alarmtimeout") handleAlarmTimeout(c, p.second);
|
||||
else if (p.first == "alarmexpectedactivity") handleAlarmExpectedActivity(c, p.second);
|
||||
else if (p.first == "separator") handleSeparator(c, p.second);
|
||||
else if (p.first == "addconversions") handleConversions(c, p.second);
|
||||
else if (p.first == "selectfields") handleSelectedFields(c, p.second);
|
||||
else if (p.first == "shell") handleShell(c, p.second);
|
||||
else if (p.first == "resetafter") handleResetAfter(c, p.second);
|
||||
else if (p.first == "alarmshell") handleAlarmShell(c, p.second);
|
||||
else if (startsWith(p.first, "json_"))
|
||||
{
|
||||
|
@ -509,10 +560,11 @@ unique_ptr<Configuration> loadConfiguration(string root, string device_override,
|
|||
}
|
||||
if (listento_override != "")
|
||||
{
|
||||
debug("(config) overriding listento with \"%s\"\n", listento_override.c_str()); handleListenTo(c, listento_override);
|
||||
debug("(config) overriding listento with \"%s\"\n", listento_override.c_str());
|
||||
handleListenTo(c, listento_override);
|
||||
}
|
||||
|
||||
return unique_ptr<Configuration>(c);
|
||||
return shared_ptr<Configuration>(c);
|
||||
}
|
||||
|
||||
LinkModeCalculationResult calculateLinkModes(Configuration *config, WMBus *wmbus, bool link_modes_matter)
|
||||
|
@ -536,7 +588,7 @@ LinkModeCalculationResult calculateLinkModes(Configuration *config, WMBus *wmbus
|
|||
debug("(config) all possible link modes that the meters might transmit on: %s\n", metersu.c_str());
|
||||
if (meters_union.bits() == 0)
|
||||
{
|
||||
if (link_modes_matter && !config->link_mode_configured)
|
||||
if (link_modes_matter && !config->linkmodes_configured)
|
||||
{
|
||||
string msg;
|
||||
strprintf(msg,"(config) No meters supplied. You must supply which link modes to listen to. Eg. --listento=<modes>");
|
||||
|
@ -545,13 +597,13 @@ LinkModeCalculationResult calculateLinkModes(Configuration *config, WMBus *wmbus
|
|||
}
|
||||
return { LinkModeCalculationResultType::Success, "" };
|
||||
}
|
||||
if (!config->link_mode_configured)
|
||||
if (!config->linkmodes_configured)
|
||||
{
|
||||
// A listen_to link mode has not been set explicitly. Pick a listen_to link
|
||||
// mode that is supported by the wmbus dongle and works for the meters.
|
||||
config->listen_to_link_modes = wmbus->supportedLinkModes();
|
||||
config->listen_to_link_modes.disjunctionLinkModeSet(meters_union);
|
||||
if (!wmbus->canSetLinkModes(config->listen_to_link_modes))
|
||||
config->linkmodes = wmbus->supportedLinkModes();
|
||||
config->linkmodes.disjunctionLinkModeSet(meters_union);
|
||||
if (!wmbus->canSetLinkModes(config->linkmodes))
|
||||
{
|
||||
// The automatically calculated link modes cannot be set in the dongle.
|
||||
// Ie the dongle needs help....
|
||||
|
@ -565,16 +617,16 @@ LinkModeCalculationResult calculateLinkModes(Configuration *config, WMBus *wmbus
|
|||
debug("%s\n", msg.c_str());
|
||||
return { LinkModeCalculationResultType::AutomaticDeductionFailed , msg};
|
||||
}
|
||||
config->link_mode_configured = true;
|
||||
config->linkmodes_configured = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
string listen = config->listen_to_link_modes.hr();
|
||||
string listen = config->linkmodes.hr();
|
||||
debug("(config) explicitly listening to: %s\n", listen.c_str());
|
||||
}
|
||||
|
||||
string listen = config->listen_to_link_modes.hr();
|
||||
if (!wmbus->canSetLinkModes(config->listen_to_link_modes))
|
||||
string listen = config->linkmodes.hr();
|
||||
if (!wmbus->canSetLinkModes(config->linkmodes))
|
||||
{
|
||||
string msg;
|
||||
strprintf(msg, "(config) You have specified to listen to the link modes: %s but the dongle can only listen to: %s",
|
||||
|
@ -583,7 +635,7 @@ LinkModeCalculationResult calculateLinkModes(Configuration *config, WMBus *wmbus
|
|||
return { LinkModeCalculationResultType::DongleCannotListenTo , msg};
|
||||
}
|
||||
|
||||
if (!config->listen_to_link_modes.hasAll(meters_union))
|
||||
if (!config->linkmodes.hasAll(meters_union))
|
||||
{
|
||||
string msg;
|
||||
strprintf(msg, "(config) You have specified to listen to the link modes: %s but the meters might transmit on: %s\n"
|
||||
|
|
24
src/config.h
24
src/config.h
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2019 Fredrik Öhrström
|
||||
Copyright (C) 2019-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -65,7 +65,7 @@ struct Configuration
|
|||
MeterFileNaming meterfiles_naming {};
|
||||
MeterFileTimestamp meterfiles_timestamp {}; // Default is never.
|
||||
bool use_logfile {};
|
||||
bool use_stderr {};
|
||||
bool use_stderr_for_log = true; // Default is to use stderr for logging.
|
||||
std::string logfile;
|
||||
bool json {};
|
||||
bool fields {};
|
||||
|
@ -74,17 +74,24 @@ struct Configuration
|
|||
std::vector<std::string> alarm_shells;
|
||||
int alarm_timeout {}; // Maximum number of seconds between dongle receiving two telegrams.
|
||||
std::string alarm_expected_activity; // Only warn when within these time periods.
|
||||
bool exit_instead_of_alarm_ {};
|
||||
bool list_shell_envs {};
|
||||
bool list_fields {};
|
||||
bool list_meters {};
|
||||
std::string list_meters_search;
|
||||
// When asking for envs or fields, this is the meter type to list for.
|
||||
std::string list_meter;
|
||||
bool oneshot {};
|
||||
int exitafter {}; // Seconds to exit.
|
||||
int reopenafter {}; // Re-open the serial device repeatedly. Silly dongle.
|
||||
string device; // auto, /dev/ttyUSB0, simulation.txt, rtlwmbus
|
||||
string device_extra; // The frequency or the command line that will start rtlwmbus
|
||||
int resetafter {}; // Reset the wmbus devices regularly.
|
||||
std::vector<SpecifiedDevice> supplied_wmbus_devices; // /dev/ttyUSB0, simulation.txt, rtlwmbus, /dev/ttyUSB1:9600
|
||||
bool use_auto_detect {}; // Set to true if auto was supplied as device.
|
||||
bool single_device_override {}; // Set to true if there is a stdin/file or simulation device.
|
||||
bool simulation_found {};
|
||||
LinkModeSet linkmodes; // If --c1 or auto:c1 then store c1 here.
|
||||
bool linkmodes_configured {}; // Either auto:c1 or --c1 is specified.
|
||||
string telegram_reader;
|
||||
// A set of all link modes (union) that the user requests the wmbus dongle to listen to.
|
||||
LinkModeSet listen_to_link_modes;
|
||||
bool link_mode_configured {};
|
||||
bool no_init {};
|
||||
std::vector<Unit> conversions;
|
||||
std::vector<std::string> selected_fields;
|
||||
|
@ -94,10 +101,11 @@ struct Configuration
|
|||
~Configuration() = default;
|
||||
};
|
||||
|
||||
unique_ptr<Configuration> loadConfiguration(string root, string device_override, string listento_override);
|
||||
shared_ptr<Configuration> loadConfiguration(string root, string device_override, string listento_override);
|
||||
|
||||
void handleConversions(Configuration *c, string s);
|
||||
void handleSelectedFields(Configuration *c, string s);
|
||||
bool handleDevice(Configuration *c, string devicefile);
|
||||
|
||||
enum class LinkModeCalculationResultType
|
||||
{
|
||||
|
|
1375
src/main.cc
1375
src/main.cc
Plik diff jest za duży
Load Diff
|
@ -23,7 +23,7 @@
|
|||
#include"util.h"
|
||||
|
||||
struct MeterAmiplus : public virtual ElectricityMeter, public virtual MeterCommonImplementation {
|
||||
MeterAmiplus(WMBus *bus, MeterInfo &mi);
|
||||
MeterAmiplus(MeterInfo &mi);
|
||||
|
||||
double totalEnergyConsumption(Unit u);
|
||||
double currentPowerConsumption(Unit u);
|
||||
|
@ -41,8 +41,8 @@ private:
|
|||
string device_date_time_;
|
||||
};
|
||||
|
||||
MeterAmiplus::MeterAmiplus(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::AMIPLUS)
|
||||
MeterAmiplus::MeterAmiplus(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::AMIPLUS)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
|
@ -74,9 +74,9 @@ MeterAmiplus::MeterAmiplus(WMBus *bus, MeterInfo &mi) :
|
|||
false, true);
|
||||
}
|
||||
|
||||
unique_ptr<ElectricityMeter> createAmiplus(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<ElectricityMeter> createAmiplus(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<ElectricityMeter>(new MeterAmiplus(bus, mi));
|
||||
return shared_ptr<ElectricityMeter>(new MeterAmiplus(mi));
|
||||
}
|
||||
|
||||
double MeterAmiplus::totalEnergyConsumption(Unit u)
|
||||
|
|
|
@ -25,7 +25,7 @@ using namespace std;
|
|||
|
||||
struct MeterApator08 : public virtual WaterMeter, public virtual MeterCommonImplementation
|
||||
{
|
||||
MeterApator08(WMBus *bus, MeterInfo &mi);
|
||||
MeterApator08(MeterInfo &mi);
|
||||
|
||||
// Total water counted through the meter
|
||||
double totalWaterConsumption(Unit u);
|
||||
|
@ -38,13 +38,13 @@ private:
|
|||
double total_water_consumption_m3_ {};
|
||||
};
|
||||
|
||||
unique_ptr<WaterMeter> createApator08(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<WaterMeter> createApator08(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<WaterMeter>(new MeterApator08(bus, mi));
|
||||
return shared_ptr<WaterMeter>(new MeterApator08(mi));
|
||||
}
|
||||
|
||||
MeterApator08::MeterApator08(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::APATOR08)
|
||||
MeterApator08::MeterApator08(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::APATOR08)
|
||||
{
|
||||
// manufacturer 0x8614 is not compliant with flags encoding.
|
||||
// forced decode will decode to APT.
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
using namespace std;
|
||||
|
||||
struct MeterApator162 : public virtual WaterMeter, public virtual MeterCommonImplementation {
|
||||
MeterApator162(WMBus *bus, MeterInfo &mi);
|
||||
MeterApator162(MeterInfo &mi);
|
||||
|
||||
// Total water counted through the meter
|
||||
double totalWaterConsumption(Unit u);
|
||||
|
@ -37,13 +37,13 @@ private:
|
|||
double total_water_consumption_m3_ {};
|
||||
};
|
||||
|
||||
unique_ptr<WaterMeter> createApator162(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<WaterMeter> createApator162(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<WaterMeter>(new MeterApator162(bus, mi));
|
||||
return shared_ptr<WaterMeter>(new MeterApator162(mi));
|
||||
}
|
||||
|
||||
MeterApator162::MeterApator162(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::APATOR162)
|
||||
MeterApator162::MeterApator162(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::APATOR162)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
#include"wmbus_utils.h"
|
||||
|
||||
struct MeterCMa12w : public virtual TempHygroMeter, public virtual MeterCommonImplementation {
|
||||
MeterCMa12w(WMBus *bus, MeterInfo &mi);
|
||||
MeterCMa12w(MeterInfo &mi);
|
||||
|
||||
double currentTemperature(Unit u);
|
||||
double currentRelativeHumidity();
|
||||
|
@ -35,8 +35,8 @@ private:
|
|||
double average_temperature_1h_c_;
|
||||
};
|
||||
|
||||
MeterCMa12w::MeterCMa12w(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::CMA12W)
|
||||
MeterCMa12w::MeterCMa12w(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::CMA12W)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
|
@ -53,9 +53,9 @@ MeterCMa12w::MeterCMa12w(WMBus *bus, MeterInfo &mi) :
|
|||
false, true);
|
||||
}
|
||||
|
||||
unique_ptr<TempHygroMeter> createCMa12w(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<TempHygroMeter> createCMa12w(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<TempHygroMeter>(new MeterCMa12w(bus, mi));
|
||||
return shared_ptr<TempHygroMeter>(new MeterCMa12w(mi));
|
||||
}
|
||||
|
||||
double MeterCMa12w::currentTemperature(Unit u)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2019 Fredrik Öhrström
|
||||
Copyright (C) 2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -25,7 +25,7 @@
|
|||
|
||||
struct MeterCompact5 : public virtual HeatMeter, public virtual MeterCommonImplementation
|
||||
{
|
||||
MeterCompact5(WMBus *bus, MeterInfo &mi);
|
||||
MeterCompact5(MeterInfo &mi);
|
||||
|
||||
double totalEnergyConsumption(Unit u);
|
||||
double currentPeriodEnergyConsumption(Unit u);
|
||||
|
@ -40,13 +40,13 @@ struct MeterCompact5 : public virtual HeatMeter, public virtual MeterCommonImple
|
|||
double prev_energy_kwh_ {};
|
||||
};
|
||||
|
||||
unique_ptr<HeatMeter> createCompact5(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<HeatMeter> createCompact5(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<HeatMeter>(new MeterCompact5(bus, mi));
|
||||
return shared_ptr<HeatMeter>(new MeterCompact5(mi));
|
||||
}
|
||||
|
||||
MeterCompact5::MeterCompact5(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::COMPACT5)
|
||||
MeterCompact5::MeterCompact5(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::COMPACT5)
|
||||
{
|
||||
// media 0x04 is used for C telegrams
|
||||
// media 0xC3 is used for T telegrams
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
struct MeterEBZWMBE : public virtual ElectricityMeter, public virtual MeterCommonImplementation
|
||||
{
|
||||
MeterEBZWMBE(WMBus *bus, MeterInfo &mi);
|
||||
MeterEBZWMBE(MeterInfo &mi);
|
||||
|
||||
double totalEnergyConsumption(Unit u);
|
||||
double currentPowerConsumption(Unit u);
|
||||
|
@ -44,8 +44,8 @@ private:
|
|||
string customer_;
|
||||
};
|
||||
|
||||
MeterEBZWMBE::MeterEBZWMBE(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::EBZWMBE)
|
||||
MeterEBZWMBE::MeterEBZWMBE(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::EBZWMBE)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_NO_IV);
|
||||
|
||||
|
@ -85,9 +85,9 @@ MeterEBZWMBE::MeterEBZWMBE(WMBus *bus, MeterInfo &mi) :
|
|||
false, true);
|
||||
}
|
||||
|
||||
unique_ptr<ElectricityMeter> createEBZWMBE(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<ElectricityMeter> createEBZWMBE(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<ElectricityMeter>(new MeterEBZWMBE(bus, mi));
|
||||
return shared_ptr<ElectricityMeter>(new MeterEBZWMBE(mi));
|
||||
}
|
||||
|
||||
double MeterEBZWMBE::totalEnergyConsumption(Unit u)
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
struct MeterEHZP : public virtual ElectricityMeter, public virtual MeterCommonImplementation
|
||||
{
|
||||
MeterEHZP(WMBus *bus, MeterInfo &mi);
|
||||
MeterEHZP(MeterInfo &mi);
|
||||
|
||||
double totalEnergyConsumption(Unit u);
|
||||
double currentPowerConsumption(Unit u);
|
||||
|
@ -42,8 +42,8 @@ private:
|
|||
double on_time_h_ {};
|
||||
};
|
||||
|
||||
MeterEHZP::MeterEHZP(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::EHZP)
|
||||
MeterEHZP::MeterEHZP(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::EHZP)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_NO_IV);
|
||||
|
||||
|
@ -71,9 +71,9 @@ MeterEHZP::MeterEHZP(WMBus *bus, MeterInfo &mi) :
|
|||
false, true);
|
||||
}
|
||||
|
||||
unique_ptr<ElectricityMeter> createEHZP(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<ElectricityMeter> createEHZP(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<ElectricityMeter>(new MeterEHZP(bus, mi));
|
||||
return shared_ptr<ElectricityMeter>(new MeterEHZP(mi));
|
||||
}
|
||||
|
||||
double MeterEHZP::totalEnergyConsumption(Unit u)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2017-2019 Fredrik Öhrström
|
||||
Copyright (C) 2017-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -39,7 +39,7 @@ constexpr uint8_t ERROR_CODE_FREQUENCY_OUT_OF_RANGE=0x40;
|
|||
|
||||
|
||||
struct MeterEM24 : public virtual ElectricityMeter, public virtual MeterCommonImplementation {
|
||||
MeterEM24(WMBus *bus, MeterInfo &mi);
|
||||
MeterEM24(MeterInfo &mi);
|
||||
|
||||
double totalEnergyConsumption(Unit u);
|
||||
double totalEnergyProduction(Unit u);
|
||||
|
@ -64,13 +64,13 @@ private:
|
|||
uint8_t error_codes_ {};
|
||||
};
|
||||
|
||||
unique_ptr<ElectricityMeter> createEM24(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<ElectricityMeter> createEM24(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<ElectricityMeter>(new MeterEM24(bus, mi));
|
||||
return shared_ptr<ElectricityMeter>(new MeterEM24(mi));
|
||||
}
|
||||
|
||||
MeterEM24::MeterEM24(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::EM24)
|
||||
MeterEM24::MeterEM24(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::EM24)
|
||||
{
|
||||
setExpectedELLSecurityMode(ELLSecurityMode::AES_CTR);
|
||||
|
||||
|
@ -173,7 +173,7 @@ void MeterEM24::processContent(Telegram *t)
|
|||
// 75 vife (Cold / Warm Temperature Limit 10^-2 Celsius)
|
||||
extractDVdouble(&t->values, "04FB8275", &offset, &total_reactive_energy_consumption_kvarh_);
|
||||
t->addMoreExplanation(offset, " total reactive power (%f kvarh)", total_reactive_energy_consumption_kvarh_);
|
||||
|
||||
|
||||
// 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
// 85 vif (Energy 10² Wh)
|
||||
// 3C vife (backward flow)
|
||||
|
@ -187,7 +187,7 @@ void MeterEM24::processContent(Telegram *t)
|
|||
// 3C vife (Reserved)
|
||||
extractDVdouble(&t->values, "04FB82F53C", &offset, &total_reactive_energy_production_kvarh_);
|
||||
t->addMoreExplanation(offset, " total reactive power (%f kvarh)", total_reactive_energy_production_kvarh_);
|
||||
|
||||
|
||||
// 01 dif (8 Bit Integer/Binary Instantaneous value)
|
||||
// FD vif (Second extension of VIF-codes)
|
||||
// 17 vife (Error flags (binary))
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
struct MeterESYSWM : public virtual ElectricityMeter, public virtual MeterCommonImplementation
|
||||
{
|
||||
MeterESYSWM(WMBus *bus, MeterInfo &mi);
|
||||
MeterESYSWM(MeterInfo &mi);
|
||||
|
||||
double totalEnergyConsumption(Unit u);
|
||||
double totalEnergyConsumptionTariff1(Unit u);
|
||||
|
@ -61,8 +61,8 @@ private:
|
|||
string fabrication_no_;
|
||||
};
|
||||
|
||||
MeterESYSWM::MeterESYSWM(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::ESYSWM)
|
||||
MeterESYSWM::MeterESYSWM(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::ESYSWM)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_NO_IV);
|
||||
|
||||
|
@ -132,9 +132,9 @@ MeterESYSWM::MeterESYSWM(WMBus *bus, MeterInfo &mi) :
|
|||
false, true);
|
||||
}
|
||||
|
||||
unique_ptr<ElectricityMeter> createESYSWM(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<ElectricityMeter> createESYSWM(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<ElectricityMeter>(new MeterESYSWM(bus, mi));
|
||||
return shared_ptr<ElectricityMeter>(new MeterESYSWM(mi));
|
||||
}
|
||||
|
||||
double MeterESYSWM::totalEnergyConsumption(Unit u)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2019 Fredrik Öhrström
|
||||
Copyright (C) 2019-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -22,7 +22,7 @@
|
|||
#include"wmbus_utils.h"
|
||||
|
||||
struct MeterEurisII : public virtual HeatCostMeter, public virtual MeterCommonImplementation {
|
||||
MeterEurisII(WMBus *bus, MeterInfo &mi);
|
||||
MeterEurisII(MeterInfo &mi);
|
||||
|
||||
double currentConsumption(Unit u);
|
||||
string setDate();
|
||||
|
@ -41,8 +41,8 @@ private:
|
|||
uint16_t error_flags_;
|
||||
};
|
||||
|
||||
MeterEurisII::MeterEurisII(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::EURISII)
|
||||
MeterEurisII::MeterEurisII(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::EURISII)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
|
@ -80,9 +80,9 @@ MeterEurisII::MeterEurisII(WMBus *bus, MeterInfo &mi) :
|
|||
true, true);
|
||||
}
|
||||
|
||||
unique_ptr<HeatCostMeter> createEurisII(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<HeatCostMeter> createEurisII(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<HeatCostMeter>(new MeterEurisII(bus, mi));
|
||||
return shared_ptr<HeatCostMeter>(new MeterEurisII(mi));
|
||||
}
|
||||
|
||||
double MeterEurisII::currentConsumption(Unit u)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2019 Fredrik Öhrström
|
||||
Copyright (C) 2019-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -26,7 +26,7 @@
|
|||
|
||||
struct MeterFHKVDataIII : public virtual HeatCostMeter, public virtual MeterCommonImplementation
|
||||
{
|
||||
MeterFHKVDataIII(WMBus *bus, MeterInfo &mi);
|
||||
MeterFHKVDataIII(MeterInfo &mi);
|
||||
|
||||
double currentPeriodEnergyConsumption(Unit u);
|
||||
string currentPeriodDate();
|
||||
|
@ -49,14 +49,14 @@ struct MeterFHKVDataIII : public virtual HeatCostMeter, public virtual MeterComm
|
|||
double temp_radiator_ {};
|
||||
};
|
||||
|
||||
unique_ptr<HeatCostMeter> createFHKVDataIII(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<HeatCostMeter> createFHKVDataIII(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<HeatCostMeter>(new MeterFHKVDataIII(bus, mi));
|
||||
return shared_ptr<HeatCostMeter>(new MeterFHKVDataIII(mi));
|
||||
}
|
||||
|
||||
|
||||
MeterFHKVDataIII::MeterFHKVDataIII(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::FHKVDATAIII)
|
||||
MeterFHKVDataIII::MeterFHKVDataIII(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::FHKVDATAIII)
|
||||
{
|
||||
// media 0x80 T telegrams
|
||||
addLinkMode(LinkMode::T1);
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
using namespace std;
|
||||
|
||||
struct MeterHydrodigit : public virtual WaterMeter, public virtual MeterCommonImplementation {
|
||||
MeterHydrodigit(WMBus *bus, MeterInfo &mi);
|
||||
MeterHydrodigit(MeterInfo &mi);
|
||||
|
||||
// Total water counted through the meter
|
||||
double totalWaterConsumption(Unit u);
|
||||
|
@ -38,13 +38,13 @@ private:
|
|||
string meter_datetime_;
|
||||
};
|
||||
|
||||
unique_ptr<WaterMeter> createHydrodigit(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<WaterMeter> createHydrodigit(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<WaterMeter>(new MeterHydrodigit(bus, mi));
|
||||
return shared_ptr<WaterMeter>(new MeterHydrodigit(mi));
|
||||
}
|
||||
|
||||
MeterHydrodigit::MeterHydrodigit(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::HYDRODIGIT)
|
||||
MeterHydrodigit::MeterHydrodigit(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::HYDRODIGIT)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2019 Fredrik Öhrström
|
||||
Copyright (C) 2019-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -24,7 +24,7 @@
|
|||
using namespace std;
|
||||
|
||||
struct MeterHydrus : public virtual WaterMeter, public virtual MeterCommonImplementation {
|
||||
MeterHydrus(WMBus *bus, MeterInfo &mi);
|
||||
MeterHydrus(MeterInfo &mi);
|
||||
|
||||
// Total water counted through the meter
|
||||
double totalWaterConsumption(Unit u);
|
||||
|
@ -48,8 +48,8 @@ private:
|
|||
map<int,string> error_codes_;
|
||||
};
|
||||
|
||||
MeterHydrus::MeterHydrus(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::HYDRUS)
|
||||
MeterHydrus::MeterHydrus(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::HYDRUS)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
|
@ -100,9 +100,9 @@ MeterHydrus::MeterHydrus(WMBus *bus, MeterInfo &mi) :
|
|||
|
||||
}
|
||||
|
||||
unique_ptr<WaterMeter> createHydrus(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<WaterMeter> createHydrus(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<WaterMeter>(new MeterHydrus(bus, mi));
|
||||
return shared_ptr<WaterMeter>(new MeterHydrus(mi));
|
||||
}
|
||||
|
||||
void MeterHydrus::processContent(Telegram *t)
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
using namespace std;
|
||||
|
||||
struct MeterIperl : public virtual WaterMeter, public virtual MeterCommonImplementation {
|
||||
MeterIperl(WMBus *bus, MeterInfo &mi);
|
||||
MeterIperl(MeterInfo &mi);
|
||||
|
||||
// Total water counted through the meter
|
||||
double totalWaterConsumption(Unit u);
|
||||
|
@ -40,8 +40,8 @@ private:
|
|||
double max_flow_m3h_ {};
|
||||
};
|
||||
|
||||
MeterIperl::MeterIperl(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::IPERL)
|
||||
MeterIperl::MeterIperl(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::IPERL)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
|
@ -61,9 +61,9 @@ MeterIperl::MeterIperl(WMBus *bus, MeterInfo &mi) :
|
|||
true, true);
|
||||
}
|
||||
|
||||
unique_ptr<WaterMeter> createIperl(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<WaterMeter> createIperl(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<WaterMeter>(new MeterIperl(bus, mi));
|
||||
return shared_ptr<WaterMeter>(new MeterIperl(mi));
|
||||
}
|
||||
|
||||
void MeterIperl::processContent(Telegram *t)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright (C) 2019 Jacek Tomasiak
|
||||
2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -45,7 +46,7 @@ typedef struct _izar_alarms {
|
|||
} izar_alarms;
|
||||
|
||||
struct MeterIzar : public virtual WaterMeter, public virtual MeterCommonImplementation {
|
||||
MeterIzar(WMBus *bus, MeterInfo &mi);
|
||||
MeterIzar(MeterInfo &mi);
|
||||
|
||||
// Total water counted through the meter
|
||||
double totalWaterConsumption(Unit u);
|
||||
|
@ -75,13 +76,13 @@ private:
|
|||
vector<uint32_t> keys;
|
||||
};
|
||||
|
||||
unique_ptr<WaterMeter> createIzar(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<WaterMeter> createIzar(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<WaterMeter>(new MeterIzar(bus, mi));
|
||||
return shared_ptr<WaterMeter>(new MeterIzar(mi));
|
||||
}
|
||||
|
||||
MeterIzar::MeterIzar(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::IZAR)
|
||||
MeterIzar::MeterIzar(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::IZAR)
|
||||
{
|
||||
MeterKeys *mk = meterKeys();
|
||||
if (!mk->confidentiality_key.empty())
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
using namespace std;
|
||||
|
||||
struct MeterIzar3 : public virtual WaterMeter, public virtual MeterCommonImplementation {
|
||||
MeterIzar3(WMBus *bus, MeterInfo &mi);
|
||||
MeterIzar3(MeterInfo &mi);
|
||||
|
||||
double totalWaterConsumption(Unit u);
|
||||
bool hasTotalWaterConsumption();
|
||||
|
@ -37,13 +37,13 @@ private:
|
|||
double total_water_consumption_l_ {};
|
||||
};
|
||||
|
||||
unique_ptr<WaterMeter> createIzar3(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<WaterMeter> createIzar3(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<WaterMeter>(new MeterIzar3(bus, mi));
|
||||
return shared_ptr<WaterMeter>(new MeterIzar3(mi));
|
||||
}
|
||||
|
||||
MeterIzar3::MeterIzar3(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::IZAR3)
|
||||
MeterIzar3::MeterIzar3(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::IZAR3)
|
||||
{
|
||||
// We do not know how to decode the IZAR r3 aka Diehl AQUARIUS!
|
||||
addLinkMode(LinkMode::T1);
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
#define INFO_CODE_OPEN 0x0055
|
||||
|
||||
struct MeterLansenDW : public virtual DoorWindowDetector, public virtual MeterCommonImplementation {
|
||||
MeterLansenDW(WMBus *bus, MeterInfo &mi);
|
||||
MeterLansenDW(MeterInfo &mi);
|
||||
|
||||
string status();
|
||||
bool open();
|
||||
|
@ -40,8 +40,8 @@ private:
|
|||
|
||||
};
|
||||
|
||||
MeterLansenDW::MeterLansenDW(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::LANSENDW)
|
||||
MeterLansenDW::MeterLansenDW(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::LANSENDW)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
|
@ -53,9 +53,9 @@ MeterLansenDW::MeterLansenDW(WMBus *bus, MeterInfo &mi) :
|
|||
true, true);
|
||||
}
|
||||
|
||||
unique_ptr<DoorWindowDetector> createLansenDW(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<DoorWindowDetector> createLansenDW(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<DoorWindowDetector>(new MeterLansenDW(bus, mi));
|
||||
return shared_ptr<DoorWindowDetector>(new MeterLansenDW(mi));
|
||||
}
|
||||
|
||||
bool MeterLansenDW::open()
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
#include"wmbus_utils.h"
|
||||
|
||||
struct MeterLansenPU : public virtual PulseCounter, public virtual MeterCommonImplementation {
|
||||
MeterLansenPU(WMBus *bus, MeterInfo &mi);
|
||||
MeterLansenPU(MeterInfo &mi);
|
||||
|
||||
double counterA();
|
||||
double counterB();
|
||||
|
@ -38,8 +38,8 @@ private:
|
|||
|
||||
};
|
||||
|
||||
MeterLansenPU::MeterLansenPU(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::LANSENPU)
|
||||
MeterLansenPU::MeterLansenPU(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::LANSENPU)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
|
@ -58,9 +58,9 @@ MeterLansenPU::MeterLansenPU(WMBus *bus, MeterInfo &mi) :
|
|||
true, true);
|
||||
}
|
||||
|
||||
unique_ptr<PulseCounter> createLansenPU(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<PulseCounter> createLansenPU(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<PulseCounter>(new MeterLansenPU(bus, mi));
|
||||
return shared_ptr<PulseCounter>(new MeterLansenPU(mi));
|
||||
}
|
||||
|
||||
double MeterLansenPU::counterA()
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
#define INFO_CODE_TEST 0x0008
|
||||
|
||||
struct MeterLansenSM : public virtual SmokeDetector, public virtual MeterCommonImplementation {
|
||||
MeterLansenSM(WMBus *bus, MeterInfo &mi);
|
||||
MeterLansenSM(MeterInfo &mi);
|
||||
|
||||
string status();
|
||||
bool smokeDetected();
|
||||
|
@ -40,8 +40,8 @@ private:
|
|||
|
||||
};
|
||||
|
||||
MeterLansenSM::MeterLansenSM(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::LANSENSM)
|
||||
MeterLansenSM::MeterLansenSM(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::LANSENSM)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
|
@ -53,9 +53,9 @@ MeterLansenSM::MeterLansenSM(WMBus *bus, MeterInfo &mi) :
|
|||
true, true);
|
||||
}
|
||||
|
||||
unique_ptr<SmokeDetector> createLansenSM(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<SmokeDetector> createLansenSM(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<SmokeDetector>(new MeterLansenSM(bus, mi));
|
||||
return shared_ptr<SmokeDetector>(new MeterLansenSM(mi));
|
||||
}
|
||||
|
||||
bool MeterLansenSM::smokeDetected()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2019 Fredrik Öhrström
|
||||
Copyright (C) 2019-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -22,7 +22,7 @@
|
|||
#include"wmbus_utils.h"
|
||||
|
||||
struct MeterLansenTH : public virtual TempHygroMeter, public virtual MeterCommonImplementation {
|
||||
MeterLansenTH(WMBus *bus, MeterInfo &mi);
|
||||
MeterLansenTH(MeterInfo &mi);
|
||||
|
||||
double currentTemperature(Unit u);
|
||||
double currentRelativeHumidity();
|
||||
|
@ -39,8 +39,8 @@ private:
|
|||
double average_relative_humidity_24h_rh_ {};
|
||||
};
|
||||
|
||||
MeterLansenTH::MeterLansenTH(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::LANSENTH)
|
||||
MeterLansenTH::MeterLansenTH(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::LANSENTH)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
|
@ -77,9 +77,9 @@ MeterLansenTH::MeterLansenTH(WMBus *bus, MeterInfo &mi) :
|
|||
false, true);
|
||||
}
|
||||
|
||||
unique_ptr<TempHygroMeter> createLansenTH(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<TempHygroMeter> createLansenTH(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<TempHygroMeter>(new MeterLansenTH(bus, mi));
|
||||
return shared_ptr<TempHygroMeter>(new MeterLansenTH(mi));
|
||||
}
|
||||
|
||||
double MeterLansenTH::currentTemperature(Unit u)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2019 Fredrik Öhrström
|
||||
Copyright (C) 2019-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -26,7 +26,7 @@ using namespace std;
|
|||
|
||||
struct MKRadio3 : public virtual WaterMeter, public virtual MeterCommonImplementation
|
||||
{
|
||||
MKRadio3(WMBus *bus, MeterInfo &mi);
|
||||
MKRadio3(MeterInfo &mi);
|
||||
|
||||
double totalWaterConsumption(Unit u);
|
||||
bool hasTotalWaterConsumption();
|
||||
|
@ -40,8 +40,8 @@ private:
|
|||
double target_water_consumption_m3_ {};
|
||||
};
|
||||
|
||||
MKRadio3::MKRadio3(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::MKRADIO3)
|
||||
MKRadio3::MKRadio3(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::MKRADIO3)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
|
@ -58,9 +58,9 @@ MKRadio3::MKRadio3(WMBus *bus, MeterInfo &mi) :
|
|||
true, true);
|
||||
}
|
||||
|
||||
unique_ptr<WaterMeter> createMKRadio3(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<WaterMeter> createMKRadio3(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<WaterMeter>(new MKRadio3(bus, mi));
|
||||
return shared_ptr<WaterMeter>(new MKRadio3(mi));
|
||||
}
|
||||
|
||||
void MKRadio3::processContent(Telegram *t)
|
||||
|
|
|
@ -39,7 +39,7 @@ using namespace std;
|
|||
#define INFO_CODE_BURST_SHIFT (4+9)
|
||||
|
||||
struct MeterMultical21 : public virtual WaterMeter, public virtual MeterCommonImplementation {
|
||||
MeterMultical21(WMBus *bus, MeterInfo &mi, MeterType mt);
|
||||
MeterMultical21(MeterInfo &mi, MeterType mt);
|
||||
|
||||
// Total water counted through the meter
|
||||
double totalWaterConsumption(Unit u);
|
||||
|
@ -87,12 +87,10 @@ private:
|
|||
bool has_flow_temperature_ {};
|
||||
double external_temperature_c_ { 127 };
|
||||
bool has_external_temperature_ {};
|
||||
|
||||
int expected_version_ {}; // 0x1b for Multical21 and 0x1d for FlowIQ3100
|
||||
};
|
||||
|
||||
MeterMultical21::MeterMultical21(WMBus *bus, MeterInfo &mi, MeterType mt) :
|
||||
MeterCommonImplementation(bus, mi, mt)
|
||||
MeterMultical21::MeterMultical21(MeterInfo &mi, MeterType mt) :
|
||||
MeterCommonImplementation(mi, mt)
|
||||
{
|
||||
setExpectedELLSecurityMode(ELLSecurityMode::AES_CTR);
|
||||
|
||||
|
@ -209,22 +207,22 @@ bool MeterMultical21::hasExternalTemperature()
|
|||
return has_external_temperature_;
|
||||
}
|
||||
|
||||
unique_ptr<WaterMeter> createMulticalWaterMeter(WMBus *bus, MeterInfo &mi, MeterType mt)
|
||||
shared_ptr<WaterMeter> createMulticalWaterMeter(MeterInfo &mi, MeterType mt)
|
||||
{
|
||||
if (mt != MeterType::MULTICAL21 && mt != MeterType::FLOWIQ3100) {
|
||||
error("Internal error! Not a proper meter type when creating a multical21 style meter.\n");
|
||||
}
|
||||
return unique_ptr<WaterMeter>(new MeterMultical21(bus,mi,mt));
|
||||
return shared_ptr<WaterMeter>(new MeterMultical21(mi,mt));
|
||||
}
|
||||
|
||||
unique_ptr<WaterMeter> createMultical21(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<WaterMeter> createMultical21(MeterInfo &mi)
|
||||
{
|
||||
return createMulticalWaterMeter(bus, mi, MeterType::MULTICAL21);
|
||||
return createMulticalWaterMeter(mi, MeterType::MULTICAL21);
|
||||
}
|
||||
|
||||
unique_ptr<WaterMeter> createFlowIQ3100(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<WaterMeter> createFlowIQ3100(MeterInfo &mi)
|
||||
{
|
||||
return createMulticalWaterMeter(bus, mi, MeterType::FLOWIQ3100);
|
||||
return createMulticalWaterMeter(mi, MeterType::FLOWIQ3100);
|
||||
}
|
||||
|
||||
void MeterMultical21::processContent(Telegram *t)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2018-2019 Fredrik Öhrström
|
||||
Copyright (C) 2018-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -31,7 +31,7 @@
|
|||
#define INFO_CODE_VOLTAGE_TOO_LOW 128
|
||||
|
||||
struct MeterMultical302 : public virtual HeatMeter, public virtual MeterCommonImplementation {
|
||||
MeterMultical302(WMBus *bus, MeterInfo &mi);
|
||||
MeterMultical302(MeterInfo &mi);
|
||||
|
||||
double totalEnergyConsumption(Unit u);
|
||||
double targetEnergyConsumption(Unit u);
|
||||
|
@ -51,8 +51,8 @@ private:
|
|||
string target_date_ {};
|
||||
};
|
||||
|
||||
MeterMultical302::MeterMultical302(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::MULTICAL302)
|
||||
MeterMultical302::MeterMultical302(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::MULTICAL302)
|
||||
{
|
||||
setExpectedELLSecurityMode(ELLSecurityMode::AES_CTR);
|
||||
|
||||
|
@ -89,8 +89,8 @@ MeterMultical302::MeterMultical302(WMBus *bus, MeterInfo &mi) :
|
|||
true, true);
|
||||
}
|
||||
|
||||
unique_ptr<HeatMeter> createMultical302(WMBus *bus, MeterInfo &mi) {
|
||||
return unique_ptr<HeatMeter>(new MeterMultical302(bus, mi));
|
||||
shared_ptr<HeatMeter> createMultical302(MeterInfo &mi) {
|
||||
return shared_ptr<HeatMeter>(new MeterMultical302(mi));
|
||||
}
|
||||
|
||||
double MeterMultical302::totalEnergyConsumption(Unit u)
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
#define INFO_CODE_TEMP_DIFF_WRONG_POLARITY 128
|
||||
|
||||
struct MeterMultical403 : public virtual HeatMeter, public virtual MeterCommonImplementation {
|
||||
MeterMultical403(WMBus *bus, MeterInfo &mi);
|
||||
MeterMultical403(MeterInfo &mi);
|
||||
|
||||
double totalEnergyConsumption(Unit u);
|
||||
string status();
|
||||
|
@ -60,8 +60,8 @@ private:
|
|||
string target_date_ {};
|
||||
};
|
||||
|
||||
MeterMultical403::MeterMultical403(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::MULTICAL403)
|
||||
MeterMultical403::MeterMultical403(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::MULTICAL403)
|
||||
{
|
||||
setExpectedELLSecurityMode(ELLSecurityMode::AES_CTR);
|
||||
|
||||
|
@ -103,8 +103,8 @@ MeterMultical403::MeterMultical403(WMBus *bus, MeterInfo &mi) :
|
|||
true, true);
|
||||
}
|
||||
|
||||
unique_ptr<HeatMeter> createMultical403(WMBus *bus, MeterInfo &mi) {
|
||||
return unique_ptr<HeatMeter>(new MeterMultical403(bus, mi));
|
||||
shared_ptr<HeatMeter> createMultical403(MeterInfo &mi) {
|
||||
return shared_ptr<HeatMeter>(new MeterMultical403(mi));
|
||||
}
|
||||
|
||||
double MeterMultical403::totalEnergyConsumption(Unit u)
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
#define INFO_CODE_TEMP_DIFF_WRONG_POLARITY 128
|
||||
|
||||
struct MeterMultical603 : public virtual HeatMeter, public virtual MeterCommonImplementation {
|
||||
MeterMultical603(WMBus *bus, MeterInfo &mi);
|
||||
MeterMultical603(MeterInfo &mi);
|
||||
|
||||
double totalEnergyConsumption(Unit u);
|
||||
string status();
|
||||
|
@ -50,7 +50,7 @@ private:
|
|||
void processContent(Telegram *t);
|
||||
|
||||
uchar info_codes_ {};
|
||||
double total_energy_mj_ {};
|
||||
double total_energy_kwh_ {};
|
||||
double total_volume_m3_ {};
|
||||
double volume_flow_m3h_ {};
|
||||
double t1_temperature_c_ { 127 };
|
||||
|
@ -58,10 +58,13 @@ private:
|
|||
double t2_temperature_c_ { 127 };
|
||||
bool has_t2_temperature_ {};
|
||||
string target_date_ {};
|
||||
|
||||
uint32_t something_a_ {};
|
||||
uint32_t something_b_ {};
|
||||
};
|
||||
|
||||
MeterMultical603::MeterMultical603(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::MULTICAL603)
|
||||
MeterMultical603::MeterMultical603(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::MULTICAL603)
|
||||
{
|
||||
setExpectedELLSecurityMode(ELLSecurityMode::AES_CTR);
|
||||
|
||||
|
@ -101,16 +104,27 @@ MeterMultical603::MeterMultical603(WMBus *bus, MeterInfo &mi) :
|
|||
[&](){ return status(); },
|
||||
"Status of meter.",
|
||||
true, true);
|
||||
|
||||
addPrint("something_a", Quantity::Text,
|
||||
[&](){ return to_string(something_a_); },
|
||||
"Something A.",
|
||||
true, true);
|
||||
|
||||
addPrint("something_b", Quantity::Text,
|
||||
[&](){ return to_string(something_b_); },
|
||||
"Something B.",
|
||||
true, true);
|
||||
|
||||
}
|
||||
|
||||
unique_ptr<HeatMeter> createMultical603(WMBus *bus, MeterInfo &mi) {
|
||||
return unique_ptr<HeatMeter>(new MeterMultical603(bus, mi));
|
||||
shared_ptr<HeatMeter> createMultical603(MeterInfo &mi) {
|
||||
return shared_ptr<HeatMeter>(new MeterMultical603(mi));
|
||||
}
|
||||
|
||||
double MeterMultical603::totalEnergyConsumption(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Energy);
|
||||
return convert(total_energy_mj_, Unit::MJ, u);
|
||||
return convert(total_energy_kwh_, Unit::KWH, u);
|
||||
}
|
||||
|
||||
double MeterMultical603::totalVolume(Unit u)
|
||||
|
@ -149,15 +163,51 @@ double MeterMultical603::volumeFlow(Unit u)
|
|||
|
||||
void MeterMultical603::processContent(Telegram *t)
|
||||
{
|
||||
/*
|
||||
(multical603) 13: 78 tpl-ci-field (EN 13757-3 Application Layer (no tplh))
|
||||
(multical603) 14: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
(multical603) 15: 06 vif (Energy kWh)
|
||||
(multical603) 16: * A5000000 total energy consumption (165.000000 kWh)
|
||||
(multical603) 1a: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
(multical603) 1b: FF vif (Vendor extension)
|
||||
(multical603) 1c: 07 vife (?)
|
||||
(multical603) 1d: 2B010000
|
||||
(multical603) 21: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
(multical603) 22: FF vif (Vendor extension)
|
||||
(multical603) 23: 08 vife (?)
|
||||
(multical603) 24: 9C000000
|
||||
(multical603) 28: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
(multical603) 29: 14 vif (Volume 10⁻² m³)
|
||||
(multical603) 2a: * 21020000 total volume (5.450000 m3)
|
||||
(multical603) 2e: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
(multical603) 2f: 3B vif (Volume flow l/h)
|
||||
(multical603) 30: * 12000000 volume flow (0.018000 m3/h)
|
||||
(multical603) 34: 02 dif (16 Bit Integer/Binary Instantaneous value)
|
||||
(multical603) 35: 59 vif (Flow temperature 10⁻² °C)
|
||||
(multical603) 36: * D014 T1 flow temperature (53.280000 °C)
|
||||
(multical603) 38: 02 dif (16 Bit Integer/Binary Instantaneous value)
|
||||
(multical603) 39: 5D vif (Return temperature 10⁻² °C)
|
||||
(multical603) 3a: * 0009 T2 flow temperature (23.040000 °C)
|
||||
(multical603) 3c: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
(multical603) 3d: FF vif (Vendor extension)
|
||||
(multical603) 3e: 22 vife (per hour)
|
||||
(multical603) 3f: * 00000000 info codes ()
|
||||
*/
|
||||
int offset;
|
||||
string key;
|
||||
|
||||
extractDVuint8(&t->values, "04FF22", &offset, &info_codes_);
|
||||
t->addMoreExplanation(offset, " info codes (%s)", status().c_str());
|
||||
|
||||
if(findKey(MeasurementType::Instantaneous, ValueInformation::EnergyMJ, 0, 0, &key, &t->values)) {
|
||||
extractDVdouble(&t->values, key, &offset, &total_energy_mj_);
|
||||
t->addMoreExplanation(offset, " total energy consumption (%f MJ)", total_energy_mj_);
|
||||
extractDVuint32(&t->values, "04FF07", &offset, &something_a_);
|
||||
t->addMoreExplanation(offset, " something A (%zu)", something_a_);
|
||||
|
||||
extractDVuint32(&t->values, "04FF08", &offset, &something_b_);
|
||||
t->addMoreExplanation(offset, " something B (%zu)", something_b_);
|
||||
|
||||
if(findKey(MeasurementType::Instantaneous, ValueInformation::EnergyWh, 0, 0, &key, &t->values)) {
|
||||
extractDVdouble(&t->values, key, &offset, &total_energy_kwh_);
|
||||
t->addMoreExplanation(offset, " total energy consumption (%f kWh)", total_energy_kwh_);
|
||||
}
|
||||
|
||||
if(findKey(MeasurementType::Instantaneous, ValueInformation::Volume, 0, 0, &key, &t->values)) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2018-2019 Fredrik Öhrström
|
||||
Copyright (C) 2018-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -23,7 +23,7 @@
|
|||
#include"util.h"
|
||||
|
||||
struct MeterOmnipower : public virtual ElectricityMeter, public virtual MeterCommonImplementation {
|
||||
MeterOmnipower(WMBus *bus, MeterInfo &mi);
|
||||
MeterOmnipower(MeterInfo &mi);
|
||||
|
||||
double totalEnergyConsumption(Unit u);
|
||||
|
||||
|
@ -34,13 +34,13 @@ private:
|
|||
double total_energy_kwh_ {};
|
||||
};
|
||||
|
||||
unique_ptr<ElectricityMeter> createOmnipower(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<ElectricityMeter> createOmnipower(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<ElectricityMeter>(new MeterOmnipower(bus, mi));
|
||||
return shared_ptr<ElectricityMeter>(new MeterOmnipower(mi));
|
||||
}
|
||||
|
||||
MeterOmnipower::MeterOmnipower(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::OMNIPOWER)
|
||||
MeterOmnipower::MeterOmnipower(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::OMNIPOWER)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
using namespace std;
|
||||
|
||||
struct MeterQ400 : public virtual WaterMeter, public virtual MeterCommonImplementation {
|
||||
MeterQ400(WMBus *bus, MeterInfo &mi);
|
||||
MeterQ400(MeterInfo &mi);
|
||||
|
||||
// Total water counted through the meter
|
||||
double totalWaterConsumption(Unit u);
|
||||
|
@ -42,13 +42,13 @@ private:
|
|||
double consumption_at_set_date_m3_ {};
|
||||
};
|
||||
|
||||
unique_ptr<WaterMeter> createQ400(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<WaterMeter> createQ400(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<WaterMeter>(new MeterQ400(bus, mi));
|
||||
return shared_ptr<WaterMeter>(new MeterQ400(mi));
|
||||
}
|
||||
|
||||
MeterQ400::MeterQ400(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::Q400)
|
||||
MeterQ400::MeterQ400(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::Q400)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2019 Fredrik Öhrström
|
||||
Copyright (C) 2019-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -22,7 +22,7 @@
|
|||
#include"wmbus_utils.h"
|
||||
|
||||
struct MeterQCaloric : public virtual HeatCostMeter, public virtual MeterCommonImplementation {
|
||||
MeterQCaloric(WMBus *bus, MeterInfo &mi);
|
||||
MeterQCaloric(MeterInfo &mi);
|
||||
|
||||
double currentConsumption(Unit u);
|
||||
string setDate();
|
||||
|
@ -45,8 +45,8 @@ private:
|
|||
string device_date_time_;
|
||||
};
|
||||
|
||||
MeterQCaloric::MeterQCaloric(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::QCALORIC)
|
||||
MeterQCaloric::MeterQCaloric(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::QCALORIC)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
|
@ -98,9 +98,9 @@ MeterQCaloric::MeterQCaloric(WMBus *bus, MeterInfo &mi) :
|
|||
false, true);
|
||||
}
|
||||
|
||||
unique_ptr<HeatCostMeter> createQCaloric(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<HeatCostMeter> createQCaloric(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<HeatCostMeter>(new MeterQCaloric(bus, mi));
|
||||
return shared_ptr<HeatCostMeter>(new MeterQCaloric(mi));
|
||||
}
|
||||
|
||||
double MeterQCaloric::currentConsumption(Unit u)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2019 Fredrik Öhrström
|
||||
Copyright (C) 2019-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -22,7 +22,7 @@
|
|||
#include"wmbus_utils.h"
|
||||
|
||||
struct MeterRfmAmb : public virtual TempHygroMeter, public virtual MeterCommonImplementation {
|
||||
MeterRfmAmb(WMBus *bus, MeterInfo &mi);
|
||||
MeterRfmAmb(MeterInfo &mi);
|
||||
|
||||
double currentTemperature(Unit u);
|
||||
double maximumTemperature(Unit u);
|
||||
|
@ -58,8 +58,8 @@ private:
|
|||
string device_date_time_;
|
||||
};
|
||||
|
||||
MeterRfmAmb::MeterRfmAmb(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::RFMAMB)
|
||||
MeterRfmAmb::MeterRfmAmb(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::RFMAMB)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
|
@ -141,9 +141,9 @@ MeterRfmAmb::MeterRfmAmb(WMBus *bus, MeterInfo &mi) :
|
|||
false, true);
|
||||
}
|
||||
|
||||
unique_ptr<TempHygroMeter> createRfmAmb(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<TempHygroMeter> createRfmAmb(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<TempHygroMeter>(new MeterRfmAmb(bus, mi));
|
||||
return shared_ptr<TempHygroMeter>(new MeterRfmAmb(mi));
|
||||
}
|
||||
|
||||
double MeterRfmAmb::currentTemperature(Unit u)
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
using namespace std;
|
||||
|
||||
struct MeterRfmTX1 : public virtual WaterMeter, public virtual MeterCommonImplementation {
|
||||
MeterRfmTX1(WMBus *bus, MeterInfo &mi);
|
||||
MeterRfmTX1(MeterInfo &mi);
|
||||
|
||||
// Total water counted through the meter
|
||||
double totalWaterConsumption(Unit u);
|
||||
|
@ -38,13 +38,13 @@ private:
|
|||
string meter_datetime_;
|
||||
};
|
||||
|
||||
unique_ptr<WaterMeter> createRfmTX1(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<WaterMeter> createRfmTX1(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<WaterMeter>(new MeterRfmTX1(bus, mi));
|
||||
return shared_ptr<WaterMeter>(new MeterRfmTX1(mi));
|
||||
}
|
||||
|
||||
MeterRfmTX1::MeterRfmTX1(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::RFMTX1)
|
||||
MeterRfmTX1::MeterRfmTX1(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::RFMTX1)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2017-2019 Fredrik Öhrström
|
||||
Copyright (C) 2017-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -25,7 +25,7 @@
|
|||
using namespace std;
|
||||
|
||||
struct MeterSupercom587 : public virtual WaterMeter, public virtual MeterCommonImplementation {
|
||||
MeterSupercom587(WMBus *bus, MeterInfo &mi);
|
||||
MeterSupercom587(MeterInfo &mi);
|
||||
|
||||
// Total water counted through the meter
|
||||
double totalWaterConsumption(Unit u);
|
||||
|
@ -37,13 +37,13 @@ private:
|
|||
double total_water_consumption_m3_ {};
|
||||
};
|
||||
|
||||
unique_ptr<WaterMeter> createSupercom587(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<WaterMeter> createSupercom587(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<WaterMeter>(new MeterSupercom587(bus, mi));
|
||||
return shared_ptr<WaterMeter>(new MeterSupercom587(mi));
|
||||
}
|
||||
|
||||
MeterSupercom587::MeterSupercom587(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::SUPERCOM587)
|
||||
MeterSupercom587::MeterSupercom587(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::SUPERCOM587)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ using namespace std;
|
|||
telegram=|4E44B40512345678F1077A310040052F2F|01FD08040C13991848004C1359423500CC101300000000CC201359423500426C7F2C0B3B00000002FD74DA10025AD300C4016D3B179F27CC011387124600|+2
|
||||
*/
|
||||
struct MeterTopasEsKr : public virtual WaterMeter, public virtual MeterCommonImplementation {
|
||||
MeterTopasEsKr(WMBus *bus, MeterInfo &mi);
|
||||
MeterTopasEsKr(MeterInfo &mi);
|
||||
|
||||
double totalWaterConsumption(Unit u);
|
||||
bool hasTotalWaterConsumption();
|
||||
|
@ -66,13 +66,13 @@ private:
|
|||
string meter_month_period_datetime_;
|
||||
};
|
||||
|
||||
unique_ptr<WaterMeter> createTopasEsKr(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<WaterMeter> createTopasEsKr(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<WaterMeter>(new MeterTopasEsKr(bus, mi));
|
||||
return shared_ptr<WaterMeter>(new MeterTopasEsKr(mi));
|
||||
}
|
||||
|
||||
MeterTopasEsKr::MeterTopasEsKr(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::TOPASESKR)
|
||||
MeterTopasEsKr::MeterTopasEsKr(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::TOPASESKR)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2019 Fredrik Öhrström
|
||||
Copyright (C) 2019-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -25,7 +25,7 @@
|
|||
|
||||
struct MeterVario451 : public virtual HeatMeter, public virtual MeterCommonImplementation
|
||||
{
|
||||
MeterVario451(WMBus *bus, MeterInfo &mi);
|
||||
MeterVario451(MeterInfo &mi);
|
||||
|
||||
double totalEnergyConsumption(Unit u);
|
||||
double currentPeriodEnergyConsumption(Unit u);
|
||||
|
@ -40,13 +40,13 @@ struct MeterVario451 : public virtual HeatMeter, public virtual MeterCommonImple
|
|||
double prev_energy_gj_ {};
|
||||
};
|
||||
|
||||
unique_ptr<HeatMeter> createVario451(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<HeatMeter> createVario451(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<HeatMeter>(new MeterVario451(bus, mi));
|
||||
return shared_ptr<HeatMeter>(new MeterVario451(mi));
|
||||
}
|
||||
|
||||
MeterVario451::MeterVario451(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::VARIO451)
|
||||
MeterVario451::MeterVario451(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::VARIO451)
|
||||
{
|
||||
// media 0x04 C telegrams
|
||||
// media 0xC3 T telegrams
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
using namespace std;
|
||||
|
||||
struct MeterWaterstarM : public virtual WaterMeter, public virtual MeterCommonImplementation {
|
||||
MeterWaterstarM(WMBus *bus, MeterInfo &mi);
|
||||
MeterWaterstarM(MeterInfo &mi);
|
||||
|
||||
// Total water counted through the meter
|
||||
double totalWaterConsumption(Unit u);
|
||||
|
@ -44,13 +44,13 @@ private:
|
|||
string parameter_set_ {};
|
||||
};
|
||||
|
||||
unique_ptr<WaterMeter> createWaterstarM(WMBus *bus, MeterInfo &mi)
|
||||
shared_ptr<WaterMeter> createWaterstarM(MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<WaterMeter>(new MeterWaterstarM(bus, mi));
|
||||
return shared_ptr<WaterMeter>(new MeterWaterstarM(mi));
|
||||
}
|
||||
|
||||
MeterWaterstarM::MeterWaterstarM(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::WATERSTARM)
|
||||
MeterWaterstarM::MeterWaterstarM(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, MeterType::WATERSTARM)
|
||||
{
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
|
|
135
src/meters.cc
135
src/meters.cc
|
@ -26,26 +26,108 @@
|
|||
#include<time.h>
|
||||
#include<cmath>
|
||||
|
||||
MeterCommonImplementation::MeterCommonImplementation(WMBus *bus, MeterInfo &mi,
|
||||
struct MeterManagerImplementation : public virtual MeterManager
|
||||
{
|
||||
void addMeter(shared_ptr<Meter> meter)
|
||||
{
|
||||
meters_.push_back(meter);
|
||||
}
|
||||
|
||||
Meter *lastAddedMeter()
|
||||
{
|
||||
return meters_.back().get();
|
||||
}
|
||||
|
||||
void removeAllMeters()
|
||||
{
|
||||
meters_.clear();
|
||||
}
|
||||
|
||||
void forEachMeter(std::function<void(Meter*)> cb)
|
||||
{
|
||||
for (auto &meter : meters_)
|
||||
{
|
||||
cb(meter.get());
|
||||
}
|
||||
}
|
||||
|
||||
bool hasAllMetersReceivedATelegram()
|
||||
{
|
||||
for (auto &meter : meters_)
|
||||
{
|
||||
if (meter->numUpdates() == 0) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool hasMeters()
|
||||
{
|
||||
return meters_.size() != 0;
|
||||
}
|
||||
|
||||
bool handleTelegram(AboutTelegram &about, vector<uchar> data, bool simulated)
|
||||
{
|
||||
if (!hasMeters())
|
||||
{
|
||||
if (on_telegram_)
|
||||
{
|
||||
on_telegram_(about, data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool handled = false;
|
||||
|
||||
string id;
|
||||
for (auto &m : meters_)
|
||||
{
|
||||
bool h = m->handleTelegram(about, data, simulated, &id);
|
||||
if (h) handled = true;
|
||||
}
|
||||
if (isVerboseEnabled() && !handled)
|
||||
{
|
||||
verbose("(wmbus) telegram from %s ignored by all configured meters!\n", id.c_str());
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
void onTelegram(function<void(AboutTelegram &about, vector<uchar>)> cb)
|
||||
{
|
||||
on_telegram_ = cb;
|
||||
}
|
||||
~MeterManagerImplementation() {}
|
||||
|
||||
private:
|
||||
|
||||
vector<shared_ptr<Meter>> meters_;
|
||||
function<void(AboutTelegram&,vector<uchar>)> on_telegram_;
|
||||
};
|
||||
|
||||
shared_ptr<MeterManager> createMeterManager()
|
||||
{
|
||||
return shared_ptr<MeterManager>(new MeterManagerImplementation);
|
||||
}
|
||||
|
||||
MeterCommonImplementation::MeterCommonImplementation(MeterInfo &mi,
|
||||
MeterType type) :
|
||||
type_(type), name_(mi.name), bus_(bus)
|
||||
type_(type), name_(mi.name)
|
||||
{
|
||||
ids_ = splitMatchExpressions(mi.id);
|
||||
if (mi.key.length() > 0)
|
||||
{
|
||||
hex2bin(mi.key, &meter_keys_.confidentiality_key);
|
||||
}
|
||||
if (bus->type() == DEVICE_SIMULATOR)
|
||||
/*if (bus->type() == DEVICE_SIMULATION)
|
||||
{
|
||||
meter_keys_.simulation = true;
|
||||
}
|
||||
}*/
|
||||
for (auto s : mi.shells) {
|
||||
addShell(s);
|
||||
}
|
||||
for (auto j : mi.jsons) {
|
||||
addJson(j);
|
||||
}
|
||||
MeterCommonImplementation::bus()->onTelegram([this](vector<uchar>input_frame){return this->handleTelegram(input_frame);});
|
||||
}
|
||||
|
||||
void MeterCommonImplementation::addConversions(std::vector<Unit> cs)
|
||||
|
@ -90,21 +172,25 @@ void MeterCommonImplementation::addPrint(string vname, Quantity vquantity,
|
|||
function<double(Unit)> getValueFunc, string help, bool field, bool json)
|
||||
{
|
||||
string default_unit = unitToStringLowerCase(defaultUnitForQuantity(vquantity));
|
||||
fields_.push_back(vname+"_"+default_unit);
|
||||
prints_.push_back( { vname, vquantity, defaultUnitForQuantity(vquantity), getValueFunc, NULL, help, field, json });
|
||||
string field_name = vname+"_"+default_unit;
|
||||
fields_.push_back(field_name);
|
||||
prints_.push_back( { vname, vquantity, defaultUnitForQuantity(vquantity), getValueFunc, NULL, help, field, json, field_name });
|
||||
}
|
||||
|
||||
void MeterCommonImplementation::addPrint(string vname, Quantity vquantity, Unit unit,
|
||||
function<double(Unit)> getValueFunc, string help, bool field, bool json)
|
||||
{
|
||||
prints_.push_back( { vname, vquantity, unit, getValueFunc, NULL, help, field, json });
|
||||
string default_unit = unitToStringLowerCase(defaultUnitForQuantity(vquantity));
|
||||
string field_name = vname+"_"+default_unit;
|
||||
fields_.push_back(field_name);
|
||||
prints_.push_back( { vname, vquantity, unit, getValueFunc, NULL, help, field, json, field_name });
|
||||
}
|
||||
|
||||
void MeterCommonImplementation::addPrint(string vname, Quantity vquantity,
|
||||
function<string()> getValueFunc,
|
||||
string help, bool field, bool json)
|
||||
{
|
||||
prints_.push_back( { vname, vquantity, defaultUnitForQuantity(vquantity), NULL, getValueFunc, help, field, json } );
|
||||
prints_.push_back( { vname, vquantity, defaultUnitForQuantity(vquantity), NULL, getValueFunc, help, field, json, vname } );
|
||||
}
|
||||
|
||||
vector<string> MeterCommonImplementation::ids()
|
||||
|
@ -117,16 +203,16 @@ vector<string> MeterCommonImplementation::fields()
|
|||
return fields_;
|
||||
}
|
||||
|
||||
vector<Print> MeterCommonImplementation::prints()
|
||||
{
|
||||
return prints_;
|
||||
}
|
||||
|
||||
string MeterCommonImplementation::name()
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
WMBus *MeterCommonImplementation::bus()
|
||||
{
|
||||
return bus_;
|
||||
}
|
||||
|
||||
void MeterCommonImplementation::onUpdate(function<void(Telegram*,Meter*)> cb)
|
||||
{
|
||||
on_update_.push_back(cb);
|
||||
|
@ -200,6 +286,12 @@ bool MeterCommonImplementation::isTelegramForMe(Telegram *t)
|
|||
name_.c_str(),
|
||||
toMeterName(type()).c_str(),
|
||||
possible_drivers.c_str());
|
||||
|
||||
if (possible_drivers == "unknown!")
|
||||
{
|
||||
warning("(meter) please consider opening an issue at https://github.com/weetmuts/wmbusmeters/\n");
|
||||
warning("(meter) to add support for this unknown mfct,media,version combination\n");
|
||||
}
|
||||
}
|
||||
|
||||
debug("(meter) %s: yes for me\n", name_.c_str());
|
||||
|
@ -336,17 +428,24 @@ string concatFields(Meter *m, Telegram *t, char c, vector<Print> &prints, vector
|
|||
return s;
|
||||
}
|
||||
|
||||
bool MeterCommonImplementation::handleTelegram(vector<uchar> input_frame)
|
||||
bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<uchar> input_frame, bool simulated, string *id)
|
||||
{
|
||||
Telegram t;
|
||||
t.about = about;
|
||||
bool ok = t.parseHeader(input_frame);
|
||||
|
||||
if (simulated) t.markAsSimulated();
|
||||
|
||||
*id = t.id;
|
||||
|
||||
if (!ok || !isTelegramForMe(&t))
|
||||
{
|
||||
// This telegram is not intended for this meter.
|
||||
return false;
|
||||
}
|
||||
|
||||
verbose("(meter) %s %s handling telegram from %s\n", name().c_str(), meterName().c_str(), t.id.c_str());
|
||||
|
||||
if (isDebugEnabled())
|
||||
{
|
||||
string msg = bin2hex(input_frame);
|
||||
|
@ -417,6 +516,12 @@ void MeterCommonImplementation::printMeter(Telegram *t,
|
|||
}
|
||||
}
|
||||
s += "\"timestamp\":\""+datetimeOfUpdateRobot()+"\"";
|
||||
if (t->about.device != "")
|
||||
{
|
||||
s += ",";
|
||||
s += "\"device\":\""+t->about.device+"\",";
|
||||
s += "\"rssi_dbm\":"+to_string(t->about.rssi_dbm);
|
||||
}
|
||||
for (string add_json : additionalJsons())
|
||||
{
|
||||
s += ",";
|
||||
|
|
112
src/meters.h
112
src/meters.h
|
@ -23,6 +23,7 @@
|
|||
#include"wmbus.h"
|
||||
|
||||
#include<string>
|
||||
#include<functional>
|
||||
#include<vector>
|
||||
|
||||
#define LIST_OF_METERS \
|
||||
|
@ -168,6 +169,10 @@ struct MeterInfo
|
|||
vector<string> shells;
|
||||
vector<string> jsons; // Additional static jsons that are added to each message.
|
||||
|
||||
MeterInfo()
|
||||
{
|
||||
}
|
||||
|
||||
MeterInfo(string n, string t, string i, string k, LinkModeSet lms, vector<string> &s, vector<string> &j)
|
||||
{
|
||||
name = n;
|
||||
|
@ -180,21 +185,34 @@ struct MeterInfo
|
|||
}
|
||||
};
|
||||
|
||||
struct Print
|
||||
{
|
||||
string vname; // Value name, like: total current previous target
|
||||
Quantity quantity; // Quantity: Energy, Volume
|
||||
Unit default_unit; // Default unit for above quantity: KWH, M3
|
||||
function<double(Unit)> getValueDouble; // Callback to fetch the value from the meter.
|
||||
function<string()> getValueString; // Callback to fetch the value from the meter.
|
||||
string help; // Helpful information on this meters use of this value.
|
||||
bool field; // If true, print in hr/fields output.
|
||||
bool json; // If true, print in json and shell env variables.
|
||||
string field_name; // Field name for default unit.
|
||||
};
|
||||
|
||||
struct Meter
|
||||
{
|
||||
// This meter listens to these ids.
|
||||
virtual vector<string> ids() = 0;
|
||||
// This meter can report these fields, like total_m3, temp_c.
|
||||
virtual vector<string> fields() = 0;
|
||||
virtual vector<Print> prints() = 0;
|
||||
virtual string meterName() = 0;
|
||||
virtual string name() = 0;
|
||||
virtual MeterType type() = 0;
|
||||
virtual WMBus *bus() = 0;
|
||||
|
||||
virtual string datetimeOfUpdateHumanReadable() = 0;
|
||||
virtual string datetimeOfUpdateRobot() = 0;
|
||||
|
||||
virtual void onUpdate(function<void(Telegram*t,Meter*)> cb) = 0;
|
||||
virtual void onUpdate(std::function<void(Telegram*t,Meter*)> cb) = 0;
|
||||
virtual int numUpdates() = 0;
|
||||
|
||||
virtual void printMeter(Telegram *t,
|
||||
|
@ -206,7 +224,8 @@ struct Meter
|
|||
vector<string> *selected_fields) = 0;
|
||||
|
||||
// The handleTelegram expects an input_frame where the DLL crcs have been removed.
|
||||
bool handleTelegram(vector<uchar> input_frame);
|
||||
// Returns true of this meter handled this telegram!
|
||||
virtual bool handleTelegram(AboutTelegram &about, vector<uchar> input_frame, bool simulated, string *id) = 0;
|
||||
virtual bool isTelegramForMe(Telegram *t) = 0;
|
||||
virtual MeterKeys *meterKeys() = 0;
|
||||
|
||||
|
@ -222,6 +241,21 @@ struct Meter
|
|||
virtual ~Meter() = default;
|
||||
};
|
||||
|
||||
struct MeterManager
|
||||
{
|
||||
virtual void addMeter(shared_ptr<Meter> meter) = 0;
|
||||
virtual Meter*lastAddedMeter() = 0;
|
||||
virtual void removeAllMeters() = 0;
|
||||
virtual void forEachMeter(std::function<void(Meter*)> cb) = 0;
|
||||
virtual bool handleTelegram(AboutTelegram &about, vector<uchar> data, bool simulated) = 0;
|
||||
virtual bool hasAllMetersReceivedATelegram() = 0;
|
||||
virtual bool hasMeters() = 0;
|
||||
virtual void onTelegram(function<void(AboutTelegram&,vector<uchar>)> cb) = 0;
|
||||
virtual ~MeterManager() = default;
|
||||
};
|
||||
|
||||
shared_ptr<MeterManager> createMeterManager();
|
||||
|
||||
struct WaterMeter : public virtual Meter
|
||||
{
|
||||
virtual double totalWaterConsumption(Unit u); // m3
|
||||
|
@ -316,41 +350,43 @@ struct GenericMeter : public virtual Meter {
|
|||
string toMeterName(MeterType mt);
|
||||
MeterType toMeterType(string& type);
|
||||
LinkModeSet toMeterLinkModeSet(string& type);
|
||||
unique_ptr<WaterMeter> createMultical21(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<WaterMeter> createFlowIQ3100(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<HeatMeter> createMultical302(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<HeatMeter> createMultical403(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<HeatMeter> createMultical603(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<HeatMeter> createVario451(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<HeatMeter> createCompact5(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<WaterMeter> createWaterstarM(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<ElectricityMeter> createOmnipower(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<ElectricityMeter> createAmiplus(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<ElectricityMeter> createEM24(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<WaterMeter> createSupercom587(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<WaterMeter> createMKRadio3(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<WaterMeter> createApator08(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<WaterMeter> createApator162(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<WaterMeter> createIperl(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<WaterMeter> createHydrus(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<WaterMeter> createHydrodigit(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<WaterMeter> createIzar(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<WaterMeter> createIzar3(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<WaterMeter> createQ400(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<HeatCostMeter> createQCaloric(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<HeatCostMeter> createEurisII(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<HeatCostMeter> createFHKVDataIII(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<TempHygroMeter> createLansenTH(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<SmokeDetector> createLansenSM(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<PulseCounter> createLansenPU(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<DoorWindowDetector> createLansenDW(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<TempHygroMeter> createCMa12w(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<TempHygroMeter> createRfmAmb(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<WaterMeter> createRfmTX1(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<ElectricityMeter> createEHZP(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<ElectricityMeter> createESYSWM(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<ElectricityMeter> createEBZWMBE(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<WaterMeter> createTopasEsKr(WMBus *bus, MeterInfo &m);
|
||||
|
||||
shared_ptr<WaterMeter> createMultical21(MeterInfo &m);
|
||||
shared_ptr<WaterMeter> createFlowIQ3100(MeterInfo &m);
|
||||
shared_ptr<HeatMeter> createMultical302(MeterInfo &m);
|
||||
shared_ptr<HeatMeter> createMultical403(MeterInfo &m);
|
||||
shared_ptr<HeatMeter> createMultical603(MeterInfo &m);
|
||||
shared_ptr<HeatMeter> createVario451(MeterInfo &m);
|
||||
shared_ptr<WaterMeter> createWaterstarM(MeterInfo &m);
|
||||
shared_ptr<HeatMeter> createCompact5(MeterInfo &m);
|
||||
shared_ptr<ElectricityMeter> createOmnipower(MeterInfo &m);
|
||||
shared_ptr<ElectricityMeter> createAmiplus(MeterInfo &m);
|
||||
shared_ptr<ElectricityMeter> createEM24(MeterInfo &m);
|
||||
shared_ptr<WaterMeter> createSupercom587(MeterInfo &m);
|
||||
shared_ptr<WaterMeter> createMKRadio3(MeterInfo &m);
|
||||
shared_ptr<WaterMeter> createApator08(MeterInfo &m);
|
||||
shared_ptr<WaterMeter> createApator162(MeterInfo &m);
|
||||
shared_ptr<WaterMeter> createIperl(MeterInfo &m);
|
||||
shared_ptr<WaterMeter> createHydrus(MeterInfo &m);
|
||||
shared_ptr<WaterMeter> createHydrodigit(MeterInfo &m);
|
||||
shared_ptr<WaterMeter> createIzar(MeterInfo &m);
|
||||
shared_ptr<WaterMeter> createIzar3(MeterInfo &m);
|
||||
shared_ptr<WaterMeter> createQ400(MeterInfo &m);
|
||||
shared_ptr<HeatCostMeter> createQCaloric(MeterInfo &m);
|
||||
shared_ptr<HeatCostMeter> createEurisII(MeterInfo &m);
|
||||
shared_ptr<HeatCostMeter> createFHKVDataIII(MeterInfo &m);
|
||||
shared_ptr<TempHygroMeter> createLansenTH(MeterInfo &m);
|
||||
shared_ptr<SmokeDetector> createLansenSM(MeterInfo &m);
|
||||
shared_ptr<PulseCounter> createLansenPU(MeterInfo &m);
|
||||
shared_ptr<DoorWindowDetector> createLansenDW(MeterInfo &m);
|
||||
shared_ptr<TempHygroMeter> createCMa12w(MeterInfo &m);
|
||||
shared_ptr<TempHygroMeter> createRfmAmb(MeterInfo &m);
|
||||
shared_ptr<WaterMeter> createRfmTX1(MeterInfo &m);
|
||||
shared_ptr<ElectricityMeter> createEHZP(MeterInfo &m);
|
||||
shared_ptr<ElectricityMeter> createESYSWM(MeterInfo &m);
|
||||
shared_ptr<ElectricityMeter> createEBZWMBE(MeterInfo &m);
|
||||
shared_ptr<WaterMeter> createTopasEsKr(MeterInfo &m);
|
||||
|
||||
GenericMeter *createGeneric(WMBus *bus, MeterInfo &m);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -24,25 +24,13 @@
|
|||
#include<map>
|
||||
#include<set>
|
||||
|
||||
struct Print
|
||||
{
|
||||
string vname; // Value name, like: total current previous target
|
||||
Quantity quantity; // Quantity: Energy, Volume
|
||||
Unit default_unit; // Default unit for above quantity: KWH, M3
|
||||
function<double(Unit)> getValueDouble; // Callback to fetch the value from the meter.
|
||||
function<string()> getValueString; // Callback to fetch the value from the meter.
|
||||
string help; // Helpful information on this meters use of this value.
|
||||
bool field; // If true, print in hr/fields output.
|
||||
bool json; // If true, print in json and shell env variables.
|
||||
};
|
||||
|
||||
struct MeterCommonImplementation : public virtual Meter
|
||||
{
|
||||
vector<string> ids();
|
||||
vector<string> fields();
|
||||
vector<Print> prints();
|
||||
string name();
|
||||
MeterType type();
|
||||
WMBus *bus();
|
||||
|
||||
ELLSecurityMode expectedELLSecurityMode();
|
||||
TPLSecurityMode expectedTPLSecurityMode();
|
||||
|
@ -60,8 +48,7 @@ struct MeterCommonImplementation : public virtual Meter
|
|||
double getRecordAsDouble(std::string record);
|
||||
uint16_t getRecordAsUInt16(std::string record);
|
||||
|
||||
MeterCommonImplementation(WMBus *bus, MeterInfo &mi,
|
||||
MeterType type);
|
||||
MeterCommonImplementation(MeterInfo &mi, MeterType type);
|
||||
|
||||
~MeterCommonImplementation() = default;
|
||||
|
||||
|
@ -87,7 +74,7 @@ protected:
|
|||
// Print the dimensionless Text quantity, no unit is needed.
|
||||
void addPrint(string vname, Quantity vquantity,
|
||||
function<std::string()> getValueFunc, string help, bool field, bool json);
|
||||
bool handleTelegram(vector<uchar> frame);
|
||||
bool handleTelegram(AboutTelegram &about, vector<uchar> frame, bool simulated, string *id);
|
||||
void printMeter(Telegram *t,
|
||||
string *human_readable,
|
||||
string *fields, char separator,
|
||||
|
@ -106,7 +93,6 @@ private:
|
|||
TPLSecurityMode expected_tpl_sec_mode_ {};
|
||||
string name_;
|
||||
vector<string> ids_;
|
||||
WMBus *bus_ {};
|
||||
vector<function<void(Telegram*,Meter*)>> on_update_;
|
||||
int num_updates_ {};
|
||||
time_t datetime_of_update_ {};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2017-2019 Fredrik Öhrström
|
||||
Copyright (C) 2017-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2017-2019 Fredrik Öhrström
|
||||
Copyright (C) 2017-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
Copyright (C) 2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include"rtlsdr.h"
|
||||
#include"util.h"
|
||||
|
||||
// Include rtl-sdr which is licensed under GPL v2 or later
|
||||
// so it happily relicenses with wmbusmeters under GPL v3 or later.
|
||||
|
||||
#include<rtl-sdr.h>
|
||||
|
||||
// Include libusb, we do not currently make use of this,
|
||||
// but the intention is to have better detection of usb
|
||||
// changes in the future. Perhaps even callbacks?
|
||||
|
||||
//#include<libusb-1.0/libusb.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
/*
|
||||
struct StaticLibUSB
|
||||
{
|
||||
libusb_context *ctx_;
|
||||
|
||||
StaticLibUSB()
|
||||
{
|
||||
libusb_init(&ctx_);
|
||||
libusb_device **list;
|
||||
ssize_t count = libusb_get_device_list(ctx_, &list);
|
||||
|
||||
for (ssize_t idx = 0; idx < count; ++idx)
|
||||
{
|
||||
libusb_device *device = list[idx];
|
||||
libusb_device_descriptor desc = {0};
|
||||
|
||||
libusb_get_device_descriptor(device, &desc);
|
||||
|
||||
printf("Vendor:Device = %04x:%04x\n", desc.idVendor, desc.idProduct);
|
||||
}
|
||||
|
||||
libusb_free_device_list(list, true);
|
||||
}
|
||||
~StaticLibUSB()
|
||||
{
|
||||
libusb_exit(ctx_);
|
||||
}
|
||||
};
|
||||
*/
|
||||
//StaticLibUSB static_;
|
||||
|
||||
vector<string> listRtlSdrDevices()
|
||||
{
|
||||
vector<string> devices;
|
||||
|
||||
uint32_t n = rtlsdr_get_device_count();
|
||||
|
||||
char mfct[256];
|
||||
char product[256];
|
||||
char serial[256];
|
||||
|
||||
for (uint32_t i=0; i<n; ++i)
|
||||
{
|
||||
rtlsdr_get_device_usb_strings(i, mfct, product, serial);
|
||||
devices.push_back(serial);
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
|
||||
int indexFromRtlSdrSerial(std::string serialnr)
|
||||
{
|
||||
uint32_t n = rtlsdr_get_device_count();
|
||||
|
||||
char mfct[256];
|
||||
char product[256];
|
||||
char serial[256];
|
||||
|
||||
for (uint32_t i=0; i<n; ++i)
|
||||
{
|
||||
rtlsdr_get_device_usb_strings(i, mfct, product, serial);
|
||||
if (serialnr == serial)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// Something is wrong.
|
||||
return -1;
|
||||
}
|
||||
|
||||
AccessCheck detectRTLSDR(string serialnr, Detected *detected)
|
||||
{
|
||||
uint32_t n = rtlsdr_get_device_count();
|
||||
|
||||
char mfct[256];
|
||||
char product[256];
|
||||
char serial[256];
|
||||
|
||||
for (uint32_t i=0; i<n; ++i)
|
||||
{
|
||||
rtlsdr_get_device_usb_strings(i, mfct, product, serial);
|
||||
if (serialnr == serial)
|
||||
{
|
||||
detected->setAsFound(serialnr, WMBusDeviceType::DEVICE_RTLWMBUS, 0, false, false);
|
||||
return AccessCheck::AccessOK;
|
||||
}
|
||||
}
|
||||
|
||||
// Something is wrong.
|
||||
return AccessCheck::NotThere;
|
||||
}
|
||||
|
||||
/*
|
||||
Find /dev/swradio0 1 2 3 etc
|
||||
Works in Ubuntu where the dev device is automatically created.
|
||||
|
||||
struct dirent **entries;
|
||||
vector<string> found_swradios;
|
||||
string devdir = "/dev/";
|
||||
|
||||
int n = scandir(devdir.c_str(), &entries, NULL, sorty);
|
||||
if (n < 0)
|
||||
{
|
||||
perror("scandir");
|
||||
return found_swradios;
|
||||
}
|
||||
|
||||
for (int i=0; i<n; ++i)
|
||||
{
|
||||
string name = entries[i]->d_name;
|
||||
|
||||
if (name == ".." || name == ".")
|
||||
{
|
||||
free(entries[i]);
|
||||
continue;
|
||||
}
|
||||
// swradio0 swradio1 swradio2 ...
|
||||
if (name.length() > 7 &&
|
||||
!strncmp(name.c_str(), "swradio", 7) &&
|
||||
name[7] >= '0' &&
|
||||
name[7] <= '9')
|
||||
{
|
||||
string rtlsdr = devdir+name;
|
||||
found_swradios.push_back(rtlsdr);
|
||||
}
|
||||
free(entries[i]);
|
||||
}
|
||||
free(entries);
|
||||
|
||||
return found_swradios;
|
||||
*/
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
Copyright (C) 2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef RTLSDR_H
|
||||
#define RTLSDR_H
|
||||
|
||||
#include "wmbus.h"
|
||||
|
||||
#include<string>
|
||||
#include<vector>
|
||||
|
||||
std::vector<std::string> listRtlSdrDevices();
|
||||
AccessCheck detectRTLSDR(std::string serialnr, Detected *detected);
|
||||
int indexFromRtlSdrSerial(std::string serialnr);
|
||||
|
||||
#endif
|
749
src/serial.cc
749
src/serial.cc
Plik diff jest za duży
Load Diff
57
src/serial.h
57
src/serial.h
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2017-2019 Fredrik Öhrström
|
||||
Copyright (C) 2017-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -36,61 +36,84 @@ struct SerialCommunicationManager;
|
|||
*/
|
||||
struct SerialDevice
|
||||
{
|
||||
// If fail_if_not_ok then forcefully exit the program if cannot be opened.
|
||||
virtual AccessCheck open(bool fail_if_not_ok) = 0;
|
||||
virtual void close() = 0;
|
||||
// Explicitly closed fd == -1
|
||||
virtual bool isClosed() = 0;
|
||||
// Send will return true only if sending on a tty.
|
||||
virtual bool send(std::vector<uchar> &data) = 0;
|
||||
// Receive returns the number of bytes received.
|
||||
virtual int receive(std::vector<uchar> *data) = 0;
|
||||
// Read and skip until the desired character is found
|
||||
// and no further bytes can be read.
|
||||
virtual bool waitFor(uchar c) = 0;
|
||||
virtual int fd() = 0;
|
||||
virtual bool opened() = 0;
|
||||
virtual bool working() = 0;
|
||||
// Used when connecting stdin to a tty driver for testing.
|
||||
virtual bool readonly() = 0;
|
||||
// Mark this device so that it is ignored by the select/callback event loop.
|
||||
virtual void disableCallbacks() = 0;
|
||||
// Enable this device to trigger callbacks from the event loop.
|
||||
virtual void enableCallbacks() = 0;
|
||||
virtual bool skippingCallbacks() = 0;
|
||||
|
||||
// Return underlying device as string.
|
||||
virtual std::string device() = 0;
|
||||
|
||||
virtual void checkIfShouldReopen() = 0;
|
||||
virtual bool checkIfDataIsPending() = 0;
|
||||
virtual void fill(std::vector<uchar> &data) = 0; // Fill buffer with raw data.
|
||||
virtual SerialCommunicationManager *manager() = 0;
|
||||
virtual void resetInitiated() = 0;
|
||||
virtual void resetCompleted() = 0;
|
||||
|
||||
virtual ~SerialDevice() = default;
|
||||
};
|
||||
|
||||
struct SerialCommunicationManager
|
||||
{
|
||||
// Read from a /dev/ttyUSB0 or /dev/ttyACM0 device with baud settings.
|
||||
virtual unique_ptr<SerialDevice> createSerialDeviceTTY(string dev, int baud_rate) = 0;
|
||||
virtual shared_ptr<SerialDevice> createSerialDeviceTTY(string dev, int baud_rate, string purpose) = 0;
|
||||
// Read from a sub shell.
|
||||
virtual unique_ptr<SerialDevice> createSerialDeviceCommand(string command,
|
||||
virtual shared_ptr<SerialDevice> createSerialDeviceCommand(string identifier,
|
||||
string command,
|
||||
vector<string> args,
|
||||
vector<string> envs,
|
||||
function<void()> on_exit) = 0;
|
||||
function<void()> on_exit,
|
||||
string purpose) = 0;
|
||||
// Read from stdin (file="stdin") or a specific file.
|
||||
virtual unique_ptr<SerialDevice> createSerialDeviceFile(string file) = 0;
|
||||
virtual shared_ptr<SerialDevice> createSerialDeviceFile(string file, string purpose) = 0;
|
||||
// A serial device simulator used for internal testing.
|
||||
virtual unique_ptr<SerialDevice> createSerialDeviceSimulator() = 0;
|
||||
virtual shared_ptr<SerialDevice> createSerialDeviceSimulator() = 0;
|
||||
|
||||
// Invoke cb callback when data arrives on the serial device.
|
||||
virtual void listenTo(SerialDevice *sd, function<void()> cb) = 0;
|
||||
// Invoke cb callback when the serial device has disappeared!
|
||||
virtual void onDisappear(SerialDevice *sd, function<void()> cb) = 0;
|
||||
// Normally the communication mananager runs for ever.
|
||||
// But if you expect configured devices to work, then
|
||||
// the manager will exit when there are no working devices.
|
||||
virtual void expectDevicesToWork() = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual void startEventLoop() = 0;
|
||||
virtual void waitForStop() = 0;
|
||||
virtual bool isRunning() = 0;
|
||||
virtual void setReopenAfter(int seconds) = 0;
|
||||
// Register a new timer that regularly, every seconds, invokes the callback.
|
||||
// Returns an id for the timer.
|
||||
virtual int startRegularCallback(int seconds, function<void()> callback, std::string name) = 0;
|
||||
virtual int startRegularCallback(std::string name, int seconds, function<void()> callback) = 0;
|
||||
virtual void stopRegularCallback(int id) = 0;
|
||||
|
||||
virtual void resetInitiated() = 0;
|
||||
virtual void resetCompleted() = 0;
|
||||
|
||||
// List all real serial devices.
|
||||
virtual std::vector<std::string> listSerialDevices() = 0;
|
||||
// List all real serial devices (avoid pseudo ttys)
|
||||
virtual std::vector<std::string> listSerialTTYs() = 0;
|
||||
// Return a serial device for the given device, if it exists! Otherwise NULL.
|
||||
virtual std::shared_ptr<SerialDevice> lookup(std::string device) = 0;
|
||||
// Remove a closed device, returns false and do not remove, if the device is still in use.
|
||||
virtual bool removeNonWorking(std::string device) = 0;
|
||||
virtual ~SerialCommunicationManager();
|
||||
};
|
||||
|
||||
unique_ptr<SerialCommunicationManager> createSerialCommunicationManager(time_t exit_after_seconds = 0,
|
||||
time_t reopen_after_seconds = 0,
|
||||
bool start_event_loop = true);
|
||||
shared_ptr<SerialCommunicationManager> createSerialCommunicationManager(time_t exit_after_seconds,
|
||||
bool start_event_loop);
|
||||
|
||||
#endif
|
||||
|
|
21
src/shell.cc
21
src/shell.cc
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2017-2019 Fredrik Öhrström
|
||||
Copyright (C) 2017-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -325,3 +325,22 @@ bool invokeShellCaptureOutput(string program, vector<string> args, vector<string
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
void detectProcesses(string cmd, vector<int> *pids)
|
||||
{
|
||||
vector<string> args;
|
||||
vector<string> envs;
|
||||
args.push_back(cmd);
|
||||
string out;
|
||||
invokeShellCaptureOutput("/bin/pidof", args, envs, &out, true);
|
||||
|
||||
char buf[out.size()+1];
|
||||
strcpy(buf, out.c_str());
|
||||
char *pch;
|
||||
pch = strtok (buf," \n");
|
||||
while (pch != NULL)
|
||||
{
|
||||
pids->push_back(atoi(pch));
|
||||
pch = strtok (NULL, " \n");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2017-2019 Fredrik Öhrström
|
||||
Copyright (C) 2017-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -25,3 +25,4 @@ bool invokeShellCaptureOutput(string program, vector<string> args, vector<string
|
|||
bool invokeBackgroundShell(string program, vector<string> args, vector<string> envs, int *out, int *pid);
|
||||
bool stillRunning(int pid);
|
||||
void stopBackgroundShell(int pid);
|
||||
void detectProcesses(string cmd, vector<int> *pids);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2018-2019 Fredrik Öhrström
|
||||
Copyright (C) 2018-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -31,22 +31,34 @@ using namespace std;
|
|||
|
||||
int test_crc();
|
||||
int test_dvparser();
|
||||
int test_test();
|
||||
int test_linkmodes();
|
||||
void test_ids();
|
||||
void test_kdf();
|
||||
void test_periods();
|
||||
void test_devices();
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc > 1) {
|
||||
// Supply --debug (oh well, anything works) to enable debug mode.
|
||||
debugEnabled(true);
|
||||
if (!strcmp(argv[1], "--debug"))
|
||||
{
|
||||
debugEnabled(true);
|
||||
}
|
||||
if (!strcmp(argv[1], "--trace"))
|
||||
{
|
||||
debugEnabled(true);
|
||||
traceEnabled(true);
|
||||
}
|
||||
}
|
||||
onExit([](){});
|
||||
|
||||
test_crc();
|
||||
test_dvparser();
|
||||
test_linkmodes();
|
||||
test_test();
|
||||
test_devices();
|
||||
/*
|
||||
test_linkmodes();*/
|
||||
test_ids();
|
||||
test_kdf();
|
||||
test_periods();
|
||||
|
@ -201,11 +213,22 @@ int test_dvparser()
|
|||
return 0;
|
||||
}
|
||||
|
||||
int test_test()
|
||||
{
|
||||
shared_ptr<SerialCommunicationManager> manager = createSerialCommunicationManager(0, false);
|
||||
|
||||
shared_ptr<SerialDevice> serial1 = manager->createSerialDeviceSimulator();
|
||||
|
||||
/*
|
||||
shared_ptr<WMBus> wmbus_im871a = openIM871A("", manager, serial1);
|
||||
manager->stop();*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_linkmodes()
|
||||
{
|
||||
LinkModeCalculationResult lmcr;
|
||||
auto manager = createSerialCommunicationManager(0, 0, false);
|
||||
auto manager = createSerialCommunicationManager(0, false);
|
||||
|
||||
auto serial1 = manager->createSerialDeviceSimulator();
|
||||
auto serial2 = manager->createSerialDeviceSimulator();
|
||||
|
@ -214,14 +237,14 @@ int test_linkmodes()
|
|||
|
||||
vector<string> no_meter_shells, no_meter_jsons;
|
||||
|
||||
unique_ptr<WMBus> wmbus_im871a = openIM871A("", manager.get(), std::move(serial1));
|
||||
unique_ptr<WMBus> wmbus_amb8465 = openAMB8465("", manager.get(), std::move(serial2));
|
||||
unique_ptr<WMBus> wmbus_rtlwmbus = openRTLWMBUS("", manager.get(), [](){}, std::move(serial3));
|
||||
unique_ptr<WMBus> wmbus_rawtty = openRawTTY("", 0, manager.get(), std::move(serial4));
|
||||
shared_ptr<WMBus> wmbus_im871a = openIM871A("", manager, serial1);
|
||||
shared_ptr<WMBus> wmbus_amb8465 = openAMB8465("", manager, serial2);
|
||||
shared_ptr<WMBus> wmbus_rtlwmbus = openRTLWMBUS("", "", manager, [](){}, serial3);
|
||||
shared_ptr<WMBus> wmbus_rawtty = openRawTTY("", 0, manager, serial4);
|
||||
|
||||
Configuration nometers_config;
|
||||
// Check that if no meters are supplied then you must set a link mode.
|
||||
nometers_config.link_mode_configured = false;
|
||||
nometers_config.linkmodes_configured = false;
|
||||
lmcr = calculateLinkModes(&nometers_config, wmbus_im871a.get());
|
||||
if (lmcr.type != LinkModeCalculationResultType::NoMetersMustSupplyModes)
|
||||
{
|
||||
|
@ -229,8 +252,8 @@ int test_linkmodes()
|
|||
}
|
||||
debug("test0 OK\n\n");
|
||||
|
||||
nometers_config.link_mode_configured = true;
|
||||
nometers_config.listen_to_link_modes.addLinkMode(LinkMode::T1);
|
||||
nometers_config.linkmodes_configured = true;
|
||||
nometers_config.linkmodes.addLinkMode(LinkMode::T1);
|
||||
lmcr = calculateLinkModes(&nometers_config, wmbus_im871a.get());
|
||||
if (lmcr.type != LinkModeCalculationResultType::Success)
|
||||
{
|
||||
|
@ -248,7 +271,7 @@ int test_linkmodes()
|
|||
// Check that if no explicit link modes are provided to apator162, then
|
||||
// automatic deduction will fail, since apator162 can be configured to transmit
|
||||
// either C1 or T1 telegrams.
|
||||
apator_config.link_mode_configured = false;
|
||||
apator_config.linkmodes_configured = false;
|
||||
lmcr = calculateLinkModes(&apator_config, wmbus_im871a.get());
|
||||
if (lmcr.type != LinkModeCalculationResultType::AutomaticDeductionFailed)
|
||||
{
|
||||
|
@ -258,10 +281,10 @@ int test_linkmodes()
|
|||
|
||||
// Check that if we supply the link mode T1 when using an apator162, then
|
||||
// automatic deduction will succeeed.
|
||||
apator_config.link_mode_configured = true;
|
||||
apator_config.listen_to_link_modes = LinkModeSet();
|
||||
apator_config.listen_to_link_modes.addLinkMode(LinkMode::T1);
|
||||
apator_config.listen_to_link_modes.addLinkMode(LinkMode::C1);
|
||||
apator_config.linkmodes_configured = true;
|
||||
apator_config.linkmodes = LinkModeSet();
|
||||
apator_config.linkmodes.addLinkMode(LinkMode::T1);
|
||||
apator_config.linkmodes.addLinkMode(LinkMode::C1);
|
||||
lmcr = calculateLinkModes(&apator_config, wmbus_im871a.get());
|
||||
if (lmcr.type != LinkModeCalculationResultType::DongleCannotListenTo)
|
||||
{
|
||||
|
@ -290,7 +313,7 @@ int test_linkmodes()
|
|||
|
||||
// Check that meters that transmit on two different link modes cannot be listened to
|
||||
// at the same time using im871a.
|
||||
multical21_and_supercom587_config.link_mode_configured = false;
|
||||
multical21_and_supercom587_config.linkmodes_configured = false;
|
||||
lmcr = calculateLinkModes(&multical21_and_supercom587_config, wmbus_im871a.get());
|
||||
if (lmcr.type != LinkModeCalculationResultType::AutomaticDeductionFailed)
|
||||
{
|
||||
|
@ -299,9 +322,9 @@ int test_linkmodes()
|
|||
debug("test4 OK\n\n");
|
||||
|
||||
// Explicitly set T1
|
||||
multical21_and_supercom587_config.link_mode_configured = true;
|
||||
multical21_and_supercom587_config.listen_to_link_modes = LinkModeSet();
|
||||
multical21_and_supercom587_config.listen_to_link_modes.addLinkMode(LinkMode::T1);
|
||||
multical21_and_supercom587_config.linkmodes_configured = true;
|
||||
multical21_and_supercom587_config.linkmodes = LinkModeSet();
|
||||
multical21_and_supercom587_config.linkmodes.addLinkMode(LinkMode::T1);
|
||||
lmcr = calculateLinkModes(&multical21_and_supercom587_config, wmbus_im871a.get());
|
||||
if (lmcr.type != LinkModeCalculationResultType::MightMissTelegrams)
|
||||
{
|
||||
|
@ -310,9 +333,9 @@ int test_linkmodes()
|
|||
debug("test5 OK\n\n");
|
||||
|
||||
// Explicitly set N1a, but the meters transmit on C1 and T1.
|
||||
multical21_and_supercom587_config.link_mode_configured = true;
|
||||
multical21_and_supercom587_config.listen_to_link_modes = LinkModeSet();
|
||||
multical21_and_supercom587_config.listen_to_link_modes.addLinkMode(LinkMode::N1a);
|
||||
multical21_and_supercom587_config.linkmodes_configured = true;
|
||||
multical21_and_supercom587_config.linkmodes = LinkModeSet();
|
||||
multical21_and_supercom587_config.linkmodes.addLinkMode(LinkMode::N1a);
|
||||
lmcr = calculateLinkModes(&multical21_and_supercom587_config, wmbus_im871a.get());
|
||||
if (lmcr.type != LinkModeCalculationResultType::MightMissTelegrams)
|
||||
{
|
||||
|
@ -321,9 +344,9 @@ int test_linkmodes()
|
|||
debug("test6 OK\n\n");
|
||||
|
||||
// Explicitly set N1a, but it is an amber dongle.
|
||||
multical21_and_supercom587_config.link_mode_configured = true;
|
||||
multical21_and_supercom587_config.listen_to_link_modes = LinkModeSet();
|
||||
multical21_and_supercom587_config.listen_to_link_modes.addLinkMode(LinkMode::N1a);
|
||||
multical21_and_supercom587_config.linkmodes_configured = true;
|
||||
multical21_and_supercom587_config.linkmodes = LinkModeSet();
|
||||
multical21_and_supercom587_config.linkmodes.addLinkMode(LinkMode::N1a);
|
||||
lmcr = calculateLinkModes(&multical21_and_supercom587_config, wmbus_amb8465.get());
|
||||
if (lmcr.type != LinkModeCalculationResultType::DongleCannotListenTo)
|
||||
{
|
||||
|
@ -331,6 +354,7 @@ int test_linkmodes()
|
|||
}
|
||||
debug("test7 OK\n\n");
|
||||
|
||||
manager->stop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -491,3 +515,113 @@ void test_periods()
|
|||
testp(t, "thu(00-00)", false);
|
||||
testp(t, "thu(01-01)", true);
|
||||
}
|
||||
|
||||
void testd(string arg, bool xok, string xfile, string xtype, string xid, string xfq, string xbps, string xlm, string xcmd)
|
||||
{
|
||||
SpecifiedDevice d;
|
||||
bool ok = d.parse(arg);
|
||||
if (ok != xok)
|
||||
{
|
||||
printf("ERROR in device parse test \"%s\" expected %s but got %s\n", arg.c_str(), xok?"OK":"FALSE", ok?"OK":"FALSE");
|
||||
return;
|
||||
}
|
||||
if (ok == false) return;
|
||||
if (d.file != xfile ||
|
||||
d.type != xtype ||
|
||||
d.id != xid ||
|
||||
d.fq != xfq ||
|
||||
d.bps != xbps ||
|
||||
d.linkmodes != xlm ||
|
||||
d.command != xcmd)
|
||||
{
|
||||
printf("ERROR in device parsing parts \"%s\"\n", arg.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void test_devices()
|
||||
{
|
||||
testd("/dev/ttyUSB0:im871a[12345678]:9600:868.95M:c1,t1", true,
|
||||
"/dev/ttyUSB0", // file
|
||||
"im871a", // type
|
||||
"12345678", // id
|
||||
"868.95M", // fq
|
||||
"9600", // bps
|
||||
"c1,t1", // linkmodes
|
||||
""); // command
|
||||
|
||||
testd("im871a[12345678]:c1", true,
|
||||
"", // file
|
||||
"im871a", // type
|
||||
"12345678", // id
|
||||
"", // fq
|
||||
"", // bps
|
||||
"c1", // linkmodes
|
||||
""); // command
|
||||
|
||||
testd("rtlwmbus:c1,t1:CMD(gurka)", true,
|
||||
"", // file
|
||||
"rtlwmbus", // type
|
||||
"", // id
|
||||
"", // fq
|
||||
"", // bps
|
||||
"c1,t1", // linkmodes
|
||||
"gurka"); // command
|
||||
|
||||
testd("stdin:rtlwmbus", true,
|
||||
"stdin", // file
|
||||
"rtlwmbus", // type
|
||||
"", // id
|
||||
"", // fq
|
||||
"", // bps
|
||||
"", // linkmodes
|
||||
""); // command
|
||||
|
||||
testd("/dev/ttyUSB0:rawtty:9600", true,
|
||||
"/dev/ttyUSB0", // file
|
||||
"rawtty", // type
|
||||
"", // id
|
||||
"", // fq
|
||||
"9600", // bps
|
||||
"", // linkmodes
|
||||
""); // command
|
||||
|
||||
// testinternals must be run from a location where
|
||||
// there is a Makefile.
|
||||
testd("Makefile:simulation", true,
|
||||
"Makefile", // file
|
||||
"simulation", // type
|
||||
"", // id
|
||||
"", // fq
|
||||
"", // bps
|
||||
"", // linkmodes
|
||||
""); // command
|
||||
|
||||
testd("auto:c1,t1", true,
|
||||
"", // file
|
||||
"auto", // type
|
||||
"", // id
|
||||
"", // fq
|
||||
"", // bps
|
||||
"c1,t1", // linkmodes
|
||||
""); // command
|
||||
|
||||
testd("auto:Makefile:c1,t1", false,
|
||||
"", // file
|
||||
"", // type
|
||||
"", // id
|
||||
"", // fq
|
||||
"", // bps
|
||||
"", // linkmodes
|
||||
""); // command
|
||||
|
||||
testd("Vatten", false,
|
||||
"", // file
|
||||
"", // type
|
||||
"", // id
|
||||
"", // fq
|
||||
"", // bps
|
||||
"", // linkmodes
|
||||
""); // command
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
Copyright (C) 2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "threads.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/resource.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
#include <mach/mach.h>
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
|
||||
pthread_t main_thread_ {};
|
||||
|
||||
pthread_t event_loop_thread_ {};
|
||||
function<void()> event_loop_entry_point_;
|
||||
|
||||
pthread_t timer_loop_thread_ {};
|
||||
function<void()> timer_loop_entry_point_;
|
||||
|
||||
pthread_t getMainThread()
|
||||
{
|
||||
return main_thread_;
|
||||
}
|
||||
|
||||
void recordMyselfAsMainThread()
|
||||
{
|
||||
main_thread_ = pthread_self();
|
||||
}
|
||||
|
||||
pthread_t getEventLoopThread()
|
||||
{
|
||||
return event_loop_thread_;
|
||||
}
|
||||
|
||||
void *dispatch(void *ptr)
|
||||
{
|
||||
function<void()> *cb = static_cast<function<void()>*>(ptr);
|
||||
(*cb)();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void startEventLoopThread(function<void()> cb)
|
||||
{
|
||||
event_loop_entry_point_ = cb;
|
||||
pthread_create(&event_loop_thread_, NULL, dispatch, &event_loop_entry_point_);
|
||||
}
|
||||
|
||||
pthread_t getTimerLoopThread()
|
||||
{
|
||||
return timer_loop_thread_;
|
||||
}
|
||||
|
||||
void startTimerLoopThread(function<void()> cb)
|
||||
{
|
||||
timer_loop_entry_point_ = cb;
|
||||
pthread_create(&timer_loop_thread_, NULL, dispatch, &timer_loop_entry_point_);
|
||||
}
|
||||
|
||||
pthread_mutex_t wmbus_devices_lock_ = PTHREAD_MUTEX_INITIALIZER;
|
||||
const char *wmbus_devices_lock_func_ = "";
|
||||
pid_t wmbus_devices_lock_pid_;
|
||||
|
||||
pthread_mutex_t serial_devices_lock_ = PTHREAD_MUTEX_INITIALIZER;
|
||||
const char *serial_devices_lock_func_ = "";
|
||||
pid_t serial_devices_lock_pid_;
|
||||
|
||||
pthread_mutex_t event_loop_lock_ = PTHREAD_MUTEX_INITIALIZER;
|
||||
const char *event_loop_lock_func_ = "";
|
||||
pid_t event_loop_lock_pid_;
|
||||
|
||||
pthread_mutex_t timers_lock_ = PTHREAD_MUTEX_INITIALIZER;
|
||||
const char *timers_lock_func_ = "";
|
||||
pid_t timers_lock_pid_;
|
||||
|
||||
RecursiveMutex serial_devices_mutex_("serial_devices_mutex");
|
||||
|
||||
RecursiveMutex::RecursiveMutex(const char *name)
|
||||
: name_(name), locked_in_func_(""), locked_by_pid_(0)
|
||||
{
|
||||
pthread_mutexattr_init(&attr_);
|
||||
pthread_mutexattr_settype(&attr_, PTHREAD_MUTEX_RECURSIVE);
|
||||
pthread_mutex_init(&mutex_, &attr_);
|
||||
}
|
||||
|
||||
RecursiveMutex::~RecursiveMutex()
|
||||
{
|
||||
pthread_mutex_destroy(&mutex_);
|
||||
pthread_mutexattr_destroy(&attr_);
|
||||
}
|
||||
|
||||
void RecursiveMutex::lock()
|
||||
{
|
||||
pthread_mutex_lock(&mutex_);
|
||||
}
|
||||
|
||||
void RecursiveMutex::unlock()
|
||||
{
|
||||
pthread_mutex_unlock(&mutex_);
|
||||
}
|
||||
|
||||
Lock::Lock(RecursiveMutex *rmutex, const char *func_name)
|
||||
{
|
||||
rmutex_ = rmutex;
|
||||
func_name_ = func_name;
|
||||
trace("[LOCKING] %s %s (%s %d)\n", rmutex_->name_, func_name_, rmutex_->locked_in_func_, rmutex->locked_by_pid_);
|
||||
pthread_mutex_lock(&rmutex_->mutex_);
|
||||
rmutex->locked_in_func_ = func_name;
|
||||
rmutex->locked_by_pid_ = getpid();
|
||||
trace("[LOCKED] %s %s (%s %d)\n", rmutex_->name_, func_name_, rmutex_->locked_in_func_, rmutex->locked_by_pid_);
|
||||
}
|
||||
|
||||
Lock::~Lock()
|
||||
{
|
||||
trace("[UNLOCKING] %s %s (%s %d)\n", rmutex_->name_, func_name_, rmutex_->locked_in_func_, rmutex_->locked_by_pid_);
|
||||
pthread_mutex_unlock(&rmutex_->mutex_);
|
||||
rmutex_->locked_in_func_ = "";
|
||||
rmutex_->locked_by_pid_ = 0;
|
||||
trace("[UNLOCKED] %s %s (%s %d)\n", rmutex_->name_, func_name_, rmutex_->locked_in_func_, rmutex_->locked_by_pid_);
|
||||
}
|
||||
|
||||
|
||||
Semaphore::Semaphore(const char *name)
|
||||
: name_(name)
|
||||
{
|
||||
pthread_cond_init(&condition_, NULL);
|
||||
pthread_mutex_init(&mutex_, NULL);
|
||||
}
|
||||
|
||||
Semaphore::~Semaphore()
|
||||
{
|
||||
pthread_mutex_destroy(&mutex_);
|
||||
pthread_cond_destroy(&condition_);
|
||||
}
|
||||
|
||||
bool Semaphore::wait()
|
||||
{
|
||||
trace("[WAITING] %s\n", name_);
|
||||
|
||||
pthread_mutex_lock(&mutex_);
|
||||
struct timespec wait_until;
|
||||
clock_gettime(CLOCK_REALTIME, &wait_until);
|
||||
wait_until.tv_sec += 5;
|
||||
|
||||
int rc = 0;
|
||||
for (;;)
|
||||
{
|
||||
rc = pthread_cond_timedwait(&condition_, &mutex_, &wait_until);
|
||||
if (!rc) break;
|
||||
if (rc == EINTR) continue;
|
||||
if (rc == ETIMEDOUT) break;
|
||||
error("(thread) pthread cond timedwait ERROR %d\n", rc);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&mutex_);
|
||||
|
||||
trace("[WAITED] %s %s\n", name_, (rc==ETIMEDOUT)?"TIMEOUT":"OK");
|
||||
|
||||
// Return true if proper wait.
|
||||
// Return false if timeout!!!!
|
||||
return rc != ETIMEDOUT;
|
||||
}
|
||||
|
||||
void Semaphore::notify()
|
||||
{
|
||||
trace("[NOTIFY] %s\n", name_);
|
||||
int rc = pthread_cond_signal(&condition_);
|
||||
if (rc)
|
||||
{
|
||||
error("(thread) pthread cond signal ERROR\n");
|
||||
}
|
||||
}
|
||||
|
||||
size_t getPeakRSS()
|
||||
{
|
||||
struct rusage rusage;
|
||||
|
||||
getrusage( RUSAGE_SELF, &rusage );
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
return (size_t)rusage.ru_maxrss;
|
||||
#else
|
||||
return (size_t)(rusage.ru_maxrss * 1024L);
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t getCurrentRSS()
|
||||
{
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
|
||||
struct mach_task_basic_info info;
|
||||
|
||||
mach_msg_type_number_t info_count = MACH_TASK_BASIC_INFO_COUNT;
|
||||
|
||||
int rc = task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &info_count);
|
||||
if (rc != KERN_SUCCESS)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return (size_t)info.resident_size;
|
||||
|
||||
#elif defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__)
|
||||
|
||||
long rss = 0;
|
||||
FILE *fp = fopen("/proc/self/statm", "r");
|
||||
if (!fp)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int rc = fscanf(fp, "%*s%ld", &rss);
|
||||
if (rc != 1)
|
||||
{
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
fclose(fp);
|
||||
return (size_t)rss * (size_t)sysconf( _SC_PAGESIZE);
|
||||
|
||||
#endif
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
Copyright (C) 2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef THREADS_H
|
||||
#define THREADS_H
|
||||
|
||||
#include "util.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <functional>
|
||||
#include <pthread.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// Declare all threads and locks used in wmbusmeters!
|
||||
|
||||
// When the main thread enters serial_manager->waitForStop()
|
||||
// this thread is recorded as the main thread. It will
|
||||
// now just sleep until its time for wmbusmeters to exit.
|
||||
pthread_t getMainThread();
|
||||
void recordMyselfAsMainThread();
|
||||
|
||||
// The event loop thread runs the event loop and executes callbacks to file descriptor
|
||||
// listeners. This thread is used for all the important work:
|
||||
// Wmbus-dongle protocol decoding, followed by parsing of telegrams and eventually
|
||||
// updating and printing meter values and executing a subshell for mqtt.
|
||||
//
|
||||
// This thread is not allowed to send commands to the dongles or update
|
||||
// wmbus-devices or serial-devices, if it does, then wmbusmeters will deadlock,
|
||||
// since the callbacks are needed to execute the commands.
|
||||
pthread_t getEventLoopThread();
|
||||
void startEventLoopThread(std::function<void()> cb);
|
||||
|
||||
// The timer callback thread runs whenever a timer timeout has happened.
|
||||
// This thread is used to probe for lost/found dongles, send commands to dongles,
|
||||
// reset dongles due to alarms, and generally monitor the system.
|
||||
pthread_t getTimerLoopThread();
|
||||
void startTimerLoopThread(std::function<void()> cb);
|
||||
|
||||
|
||||
size_t getPeakRSS();
|
||||
size_t getCurrentRSS();
|
||||
|
||||
|
||||
#define LOCK(module,func,x) { trace("[LOCKING] " #x " " func " (%s %d)\n", x ## func_, x ## pid_); \
|
||||
pthread_mutex_lock(&x); \
|
||||
x ## func_ = func; \
|
||||
x ## pid_ = getpid(); \
|
||||
trace("[LOCKED] " #x " " func "\n"); }
|
||||
#define UNLOCK(module,func,x) { trace("[UNLOCKING] " #x " " func " (%s %d) \n", x ## func_, x ## pid_); \
|
||||
pthread_mutex_unlock(&x); \
|
||||
x ## func_ = ""; \
|
||||
x ## pid_ = 0; \
|
||||
trace("[UNLOCKED] " #x " " func "\n"); }
|
||||
|
||||
#define WITH(mutex,func) Lock local_ ## mutex (&mutex, #func)
|
||||
|
||||
struct Lock;
|
||||
|
||||
struct RecursiveMutex
|
||||
{
|
||||
RecursiveMutex(const char *name);
|
||||
~RecursiveMutex();
|
||||
void lock();
|
||||
void unlock();
|
||||
|
||||
private:
|
||||
|
||||
const char *name_;
|
||||
pthread_mutex_t mutex_;
|
||||
pthread_mutexattr_t attr_;
|
||||
const char *locked_in_func_;
|
||||
pid_t locked_by_pid_;
|
||||
|
||||
friend Lock;
|
||||
};
|
||||
|
||||
struct Lock
|
||||
{
|
||||
Lock(RecursiveMutex *rmutex, const char *func_name);
|
||||
~Lock();
|
||||
|
||||
private:
|
||||
|
||||
RecursiveMutex *rmutex_ {};
|
||||
const char *func_name_;
|
||||
};
|
||||
|
||||
struct Semaphore
|
||||
{
|
||||
Semaphore(const char *name);
|
||||
~Semaphore();
|
||||
bool wait();
|
||||
void notify();
|
||||
|
||||
private:
|
||||
|
||||
const char *name_;
|
||||
pthread_mutex_t mutex_;
|
||||
pthread_cond_t condition_;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -18,14 +18,10 @@
|
|||
#ifndef TIMINGS_H
|
||||
#define TIMINGS_H
|
||||
|
||||
// Default select timeout, every 10 seconds if no other timer/timeout cuts it short.
|
||||
#define SELECT_TIMEOUT 10
|
||||
// When running internal tests on timeouts use 5 seconds instead.
|
||||
#define SELECT_TIMEOUT_INTERNAL_TESTING 5
|
||||
// Default select timeout one second.
|
||||
#define SELECT_TIMEOUT 1
|
||||
|
||||
// Default checkStatus callback frequency every 60 seconds, when an alarmtimeout has been set.
|
||||
#define CHECKSTATUS_TIMER 60
|
||||
// When running internal tests on timeouts use 2 seconds instead.
|
||||
#define CHECKSTATUS_TIMER_INTERNAL_TESTING 2
|
||||
// Default checkStatus callback frequency every 2 seconds, when an alarmtimeout has been set.
|
||||
#define CHECKSTATUS_TIMER 2
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,618 @@
|
|||
/*
|
||||
Copyright (C) 2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include"ui.h"
|
||||
#include"util.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
int screen_width_;
|
||||
int screen_height_;
|
||||
|
||||
function<void()> update_cb_;
|
||||
|
||||
void initUI()
|
||||
{
|
||||
initscr();
|
||||
getmaxyx(stdscr, screen_height_, screen_width_);
|
||||
start_color();
|
||||
cbreak();
|
||||
curs_set(1);
|
||||
noecho();
|
||||
keypad(stdscr, TRUE);
|
||||
|
||||
init_pair(BG_PAIR, COLOR_WHITE, COLOR_BLUE);
|
||||
init_pair(WIN_PAIR, COLOR_BLACK, COLOR_WHITE);
|
||||
init_pair(TITLE_PAIR, COLOR_WHITE, COLOR_CYAN);
|
||||
init_pair(HILIGHT_PAIR, COLOR_WHITE, COLOR_RED);
|
||||
wbkgd(stdscr, COLOR_PAIR(BG_PAIR));
|
||||
}
|
||||
|
||||
void registerUpdateCB(std::function<void()> cb)
|
||||
{
|
||||
update_cb_ = cb;
|
||||
}
|
||||
|
||||
void printAt(WINDOW *win, int y, int x, const char *str, chtype color)
|
||||
{
|
||||
wattron(win, color);
|
||||
mvwprintw(win, y, x, "%s", str);
|
||||
wattroff(win, color);
|
||||
refresh();
|
||||
}
|
||||
|
||||
void printMiddle(WINDOW *win, int y, int width, const char *str, chtype color)
|
||||
{
|
||||
int len = strlen(str);
|
||||
int wh, ww;
|
||||
|
||||
getyx(win, wh, ww);
|
||||
((void)wh);
|
||||
|
||||
int x = (width-len)/2;
|
||||
wattron(win, color);
|
||||
mvwprintw(win, y, x, "%s", str);
|
||||
wattroff(win, color);
|
||||
refresh();
|
||||
}
|
||||
|
||||
int countEntries(const char *entries[])
|
||||
{
|
||||
int i = 0;
|
||||
for (; entries[i] != 0; ++i);
|
||||
return i+1;
|
||||
}
|
||||
|
||||
int maxWidth(const char *entries[])
|
||||
{
|
||||
int max = 0;
|
||||
for (int i=0; entries[i] != 0; ++i)
|
||||
{
|
||||
int n = strlen(entries[i]);
|
||||
if (max < n) max = n;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
int maxWidth(vector<string> entries)
|
||||
{
|
||||
int max = 0;
|
||||
for (string& s : entries)
|
||||
{
|
||||
int n = s.length();
|
||||
if (max < n) max = n;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
int selectFromMenu(const char *title, const char *entries[])
|
||||
{
|
||||
vector<string> menu;
|
||||
int n_choices = countEntries(entries);
|
||||
|
||||
for (int i=0; i<n_choices; ++i)
|
||||
{
|
||||
if (entries[i] == NULL) break;
|
||||
menu.push_back(entries[i]);
|
||||
}
|
||||
return selectFromMenu(title, menu);
|
||||
}
|
||||
|
||||
int selectFromMenu(const char *title, vector<string> entries)
|
||||
{
|
||||
int selected = -1;
|
||||
ITEM **menu_items;
|
||||
int c;
|
||||
MENU *menu;
|
||||
WINDOW *frame_window, *menu_window;
|
||||
int n_choices, i;
|
||||
|
||||
n_choices = entries.size()+1;
|
||||
menu_items = (ITEM **)calloc(n_choices, sizeof(ITEM *));
|
||||
for(i = 0; i < n_choices-1; ++i)
|
||||
{
|
||||
menu_items[i] = new_item(entries[i].c_str(), NULL);
|
||||
}
|
||||
menu_items[n_choices-1] = NULL;
|
||||
|
||||
menu = new_menu(menu_items);
|
||||
int mw = 0;
|
||||
int mh = 0;
|
||||
scale_menu(menu, &mh, &mw);
|
||||
int w = mw+2;
|
||||
int h = mh+4;
|
||||
if (w-2 < (int)strlen(title))
|
||||
{
|
||||
w = (int)strlen(title)+2;
|
||||
}
|
||||
int x = screen_width_/2-w/2;
|
||||
int y = screen_height_/2-h/2;
|
||||
frame_window = newwin(h, w, y, x);
|
||||
|
||||
int mx = (w-mw)/2;
|
||||
int my = 3;
|
||||
menu_window = derwin(frame_window, mh, mw, my, mx);
|
||||
|
||||
set_menu_fore(menu, COLOR_PAIR(HILIGHT_PAIR));
|
||||
set_menu_back(menu, COLOR_PAIR(WIN_PAIR));
|
||||
set_menu_grey(menu, COLOR_PAIR(3));
|
||||
|
||||
keypad(frame_window, TRUE);
|
||||
|
||||
set_menu_win(menu, frame_window);
|
||||
set_menu_sub(menu, menu_window);
|
||||
|
||||
set_menu_mark(menu, ">");
|
||||
|
||||
box(frame_window, 0, 0);
|
||||
wbkgd(frame_window, COLOR_PAIR(WIN_PAIR));
|
||||
|
||||
printMiddle(frame_window, 1, w, title, COLOR_PAIR(WIN_PAIR));
|
||||
mvwaddch(frame_window, 2, 0, ACS_LTEE);
|
||||
mvwhline(frame_window, 2, 1, ACS_HLINE, 38);
|
||||
mvwaddch(frame_window, 2, w-1, ACS_RTEE);
|
||||
refresh();
|
||||
|
||||
post_menu(menu);
|
||||
wrefresh(frame_window);
|
||||
|
||||
update_cb_();
|
||||
|
||||
wtimeout(frame_window, 1000);
|
||||
|
||||
bool running = true;
|
||||
do
|
||||
{
|
||||
c = wgetch(frame_window);
|
||||
ITEM *cur = current_item(menu);
|
||||
selected = item_index(cur);
|
||||
switch(c)
|
||||
{
|
||||
case ERR:
|
||||
update_cb_();
|
||||
redrawwin(frame_window);
|
||||
break;
|
||||
case KEY_DOWN:
|
||||
if (selected < n_choices-2)
|
||||
{
|
||||
menu_driver(menu, REQ_DOWN_ITEM);
|
||||
}
|
||||
else
|
||||
{
|
||||
menu_driver(menu, REQ_FIRST_ITEM);
|
||||
}
|
||||
break;
|
||||
case KEY_UP:
|
||||
if (selected > 0)
|
||||
{
|
||||
menu_driver(menu, REQ_UP_ITEM);
|
||||
}
|
||||
else
|
||||
{
|
||||
menu_driver(menu, REQ_LAST_ITEM);
|
||||
}
|
||||
break;
|
||||
case '\n':
|
||||
running = false;
|
||||
break;
|
||||
}
|
||||
wrefresh(frame_window);
|
||||
} while (running);
|
||||
|
||||
unpost_menu(menu);
|
||||
free_menu(menu);
|
||||
delwin(frame_window);
|
||||
erase();
|
||||
refresh();
|
||||
for(i = 0; i < n_choices; ++i)
|
||||
{
|
||||
free_item(menu_items[i]);
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
void displayInformationAndWait(string title, vector<string> entries, int px, int py)
|
||||
{
|
||||
WINDOW *frame_window;
|
||||
|
||||
update_cb_();
|
||||
|
||||
int mw = maxWidth(entries)+1;
|
||||
int mh = entries.size();
|
||||
int w = mw+2;
|
||||
int h = mh+4;
|
||||
if (w-2 < (int)title.length())
|
||||
{
|
||||
w = (int)title.length()+2;
|
||||
}
|
||||
int x = screen_width_/2-w/2;
|
||||
int y = screen_height_/2-h/2;
|
||||
if (px != -1)
|
||||
{
|
||||
x = px;
|
||||
}
|
||||
if (py != -1)
|
||||
{
|
||||
y = py;
|
||||
}
|
||||
frame_window = newwin(h, w, y, x);
|
||||
|
||||
keypad(frame_window, TRUE);
|
||||
|
||||
box(frame_window, 0, 0);
|
||||
wbkgd(frame_window, COLOR_PAIR(WIN_PAIR));
|
||||
|
||||
printMiddle(frame_window, 1, w, title.c_str(), COLOR_PAIR(WIN_PAIR));
|
||||
mvwaddch(frame_window, 2, 0, ACS_LTEE);
|
||||
mvwhline(frame_window, 2, 1, ACS_HLINE, 38);
|
||||
mvwaddch(frame_window, 2, w-1, ACS_RTEE);
|
||||
//refresh();
|
||||
|
||||
int r = 3;
|
||||
for (string e : entries)
|
||||
{
|
||||
printAt(frame_window, r, 1, e.c_str(), COLOR_PAIR(WIN_PAIR));
|
||||
r++;
|
||||
}
|
||||
wrefresh(frame_window);
|
||||
wtimeout(frame_window, 1000);
|
||||
|
||||
bool running = true;
|
||||
do
|
||||
{
|
||||
int c = wgetch(frame_window);
|
||||
switch(c)
|
||||
{
|
||||
case ERR:
|
||||
update_cb_();
|
||||
redrawwin(frame_window);
|
||||
break;
|
||||
case 27:
|
||||
case '\n':
|
||||
running = false;
|
||||
break;
|
||||
}
|
||||
wrefresh(frame_window);
|
||||
} while (running);
|
||||
|
||||
delwin(frame_window);
|
||||
erase();
|
||||
refresh();
|
||||
}
|
||||
|
||||
void displayInformationNoWait(WINDOW **winp, string title, vector<string> entries, int px, int py)
|
||||
{
|
||||
WINDOW *win = *winp;
|
||||
|
||||
if (win != NULL)
|
||||
{
|
||||
delwin(win);
|
||||
*winp = NULL;
|
||||
}
|
||||
int mw = maxWidth(entries)+1;
|
||||
int mh = entries.size();
|
||||
int w = mw+2;
|
||||
int h = mh+4;
|
||||
if (w-2 < (int)title.length())
|
||||
{
|
||||
w = (int)title.length()+2;
|
||||
}
|
||||
int x = screen_width_/2-w/2;
|
||||
int y = screen_height_/2-h/2;
|
||||
if (px != -1)
|
||||
{
|
||||
x = px;
|
||||
}
|
||||
if (py != -1)
|
||||
{
|
||||
y = py;
|
||||
}
|
||||
win = newwin(h, w, y, x);
|
||||
*winp = win;
|
||||
|
||||
box(win, 0, 0);
|
||||
wbkgd(win, COLOR_PAIR(WIN_PAIR));
|
||||
|
||||
printMiddle(win, 1, w, title.c_str(), COLOR_PAIR(WIN_PAIR));
|
||||
mvwaddch(win, 2, 0, ACS_LTEE);
|
||||
mvwhline(win, 2, 1, ACS_HLINE, 38);
|
||||
mvwaddch(win, 2, w-1, ACS_RTEE);
|
||||
// refresh();
|
||||
|
||||
int r = 3;
|
||||
for (string e : entries)
|
||||
{
|
||||
printAt(win, r, 1, e.c_str(), COLOR_PAIR(WIN_PAIR));
|
||||
r++;
|
||||
}
|
||||
wrefresh(win);
|
||||
}
|
||||
|
||||
void displayStatusLineNoWait(WINDOW **winp, vector<string> entries, int px, int py)
|
||||
{
|
||||
WINDOW *win = *winp;
|
||||
|
||||
if (win != NULL)
|
||||
{
|
||||
delwin(win);
|
||||
*winp = NULL;
|
||||
}
|
||||
int w = screen_width_;
|
||||
int h = 1;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
if (px != -1)
|
||||
{
|
||||
x = px;
|
||||
}
|
||||
if (py != -1)
|
||||
{
|
||||
y = py;
|
||||
}
|
||||
win = newwin(h, w, y, x);
|
||||
*winp = win;
|
||||
|
||||
wbkgd(win, COLOR_PAIR(WIN_PAIR));
|
||||
|
||||
int sum = 0;
|
||||
for (string & e : entries) sum += e.length();
|
||||
int num = entries.size()-1;
|
||||
if (num == 0) num = 1;
|
||||
int spacing = (w-sum) / num;
|
||||
|
||||
// printMiddle(win, 0, w, title.c_str(), COLOR_PAIR(WIN_PAIR));
|
||||
|
||||
int xx = 0;
|
||||
for (string & e : entries)
|
||||
{
|
||||
printAt(win, 0, xx, e.c_str(), COLOR_PAIR(WIN_PAIR));
|
||||
xx += e.length()+spacing;
|
||||
}
|
||||
|
||||
wrefresh(win);
|
||||
}
|
||||
|
||||
string displayInformationAndInput(string title, vector<string> entries, int px, int py)
|
||||
{
|
||||
WINDOW *frame_window;
|
||||
|
||||
int mw = maxWidth(entries)+1;
|
||||
int mh = entries.size();
|
||||
int w = mw+2;
|
||||
int h = mh+4;
|
||||
if (w-2 < (int)title.length())
|
||||
{
|
||||
w = (int)title.length()+2;
|
||||
}
|
||||
int x = screen_width_/2-w/2;
|
||||
int y = screen_height_/2-h/2;
|
||||
if (px != -1)
|
||||
{
|
||||
x = px;
|
||||
}
|
||||
if (py != -1)
|
||||
{
|
||||
y = py;
|
||||
}
|
||||
frame_window = newwin(h, w, y, x);
|
||||
|
||||
keypad(frame_window, TRUE);
|
||||
|
||||
box(frame_window, 0, 0);
|
||||
wbkgd(frame_window, COLOR_PAIR(WIN_PAIR));
|
||||
|
||||
printMiddle(frame_window, 1, w, title.c_str(), COLOR_PAIR(WIN_PAIR));
|
||||
mvwaddch(frame_window, 2, 0, ACS_LTEE);
|
||||
mvwhline(frame_window, 2, 1, ACS_HLINE, 38);
|
||||
mvwaddch(frame_window, 2, w-1, ACS_RTEE);
|
||||
//refresh();
|
||||
|
||||
int r = 3;
|
||||
for (string e : entries)
|
||||
{
|
||||
printAt(frame_window, r, 1, e.c_str(), COLOR_PAIR(WIN_PAIR));
|
||||
r++;
|
||||
}
|
||||
wrefresh(frame_window);
|
||||
|
||||
char buf[128];
|
||||
mvwgetnstr(frame_window, 1, 1, buf, 127);
|
||||
|
||||
debug("(sudo) %s\n", buf);
|
||||
delwin(frame_window);
|
||||
erase();
|
||||
refresh();
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
void notImplementedYet(string msg)
|
||||
{
|
||||
vector<string> entries;
|
||||
entries.push_back(msg);
|
||||
displayInformationAndWait("Not implemented yet", entries);
|
||||
}
|
||||
|
||||
static string driver(FORM *form, FIELD **fields, int ch, WINDOW *win)
|
||||
{
|
||||
//int i;
|
||||
string val = "";
|
||||
switch (ch) {
|
||||
case 10:
|
||||
// Or the current field buffer won't be sync with what is displayed
|
||||
form_driver(form, REQ_NEXT_FIELD);
|
||||
form_driver(form, REQ_PREV_FIELD);
|
||||
val = string(field_buffer(fields[1], 0));
|
||||
/*
|
||||
move(LINES-3, 2);
|
||||
|
||||
for (i = 0; fields[i]; i++)
|
||||
{
|
||||
printw("%s", trim_whitespaces(field_buffer(fields[i], 0)));
|
||||
|
||||
if (field_opts(fields[i]) & O_ACTIVE)
|
||||
{
|
||||
printw("\"\t");
|
||||
val = field_buffer(fields[i], 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
printw(": \"");
|
||||
}
|
||||
}
|
||||
*/
|
||||
//refresh();
|
||||
//pos_form_cursor(form);
|
||||
return val;
|
||||
|
||||
case KEY_DOWN:
|
||||
form_driver(form, REQ_NEXT_FIELD);
|
||||
form_driver(form, REQ_END_LINE);
|
||||
break;
|
||||
|
||||
case KEY_UP:
|
||||
form_driver(form, REQ_PREV_FIELD);
|
||||
form_driver(form, REQ_END_LINE);
|
||||
break;
|
||||
|
||||
case KEY_LEFT:
|
||||
form_driver(form, REQ_PREV_CHAR);
|
||||
break;
|
||||
|
||||
case KEY_RIGHT:
|
||||
form_driver(form, REQ_NEXT_CHAR);
|
||||
break;
|
||||
|
||||
// Delete the char before cursor
|
||||
case KEY_BACKSPACE:
|
||||
case 127:
|
||||
form_driver(form, REQ_DEL_PREV);
|
||||
break;
|
||||
|
||||
// Delete the char under the cursor
|
||||
case KEY_DC:
|
||||
form_driver(form, REQ_DEL_CHAR);
|
||||
break;
|
||||
|
||||
default:
|
||||
debug("CHAR %d\n", ch);
|
||||
form_driver(form, ch);
|
||||
break;
|
||||
}
|
||||
|
||||
wrefresh(win);
|
||||
return "";
|
||||
}
|
||||
|
||||
string inputField(string title, vector<string> entries, string label)
|
||||
{
|
||||
FORM *form;
|
||||
FIELD *fields[3];
|
||||
WINDOW *frame_window;
|
||||
|
||||
update_cb_();
|
||||
|
||||
entries.push_back(""); // Add empty line on which the input field will be displayed.
|
||||
|
||||
int mw = maxWidth(entries)+1;
|
||||
int mh = entries.size();
|
||||
int w = mw+2;
|
||||
int h = mh+4;
|
||||
if (w-2 < (int)title.length())
|
||||
{
|
||||
w = (int)title.length()+2;
|
||||
}
|
||||
w=50;
|
||||
h=20;
|
||||
int x = screen_width_/2-w/2;
|
||||
int y = screen_height_/2-h/2;
|
||||
frame_window = newwin(h, w, y, x);
|
||||
|
||||
keypad(frame_window, TRUE);
|
||||
|
||||
box(frame_window, 0, 0);
|
||||
wbkgd(frame_window, COLOR_PAIR(WIN_PAIR));
|
||||
|
||||
printMiddle(frame_window, 1, w, title.c_str(), COLOR_PAIR(WIN_PAIR));
|
||||
mvwaddch(frame_window, 2, 0, ACS_LTEE);
|
||||
mvwhline(frame_window, 2, 1, ACS_HLINE, w-2);
|
||||
mvwaddch(frame_window, 2, w-1, ACS_RTEE);
|
||||
wrefresh(frame_window);
|
||||
|
||||
int r = 3;
|
||||
for (string e : entries)
|
||||
{
|
||||
printAt(frame_window, r, 1, e.c_str(), COLOR_PAIR(WIN_PAIR));
|
||||
r++;
|
||||
}
|
||||
wrefresh(frame_window);
|
||||
// wtimeout(frame_window, 1000);
|
||||
|
||||
fields[0] = new_field(1, 10, 1, 1, 0, 0);
|
||||
fields[1] = new_field(1, 20, 1, 15, 0, 0);
|
||||
fields[2] = NULL;
|
||||
set_field_fore(fields[0], COLOR_PAIR(WIN_PAIR));
|
||||
set_field_fore(fields[1], COLOR_PAIR(BG_PAIR));
|
||||
assert(fields[0] != NULL && fields[1] != NULL);
|
||||
|
||||
set_field_buffer(fields[0], 0, "Password");
|
||||
set_field_buffer(fields[1], 0, "IFFOBIFFO");
|
||||
|
||||
set_field_opts(fields[0], O_VISIBLE | O_PUBLIC | O_AUTOSKIP);
|
||||
set_field_opts(fields[1], O_VISIBLE | O_PUBLIC | O_EDIT | O_ACTIVE); // O_PUBLIC removed to hide contents.
|
||||
|
||||
set_field_back(fields[1], A_UNDERLINE);
|
||||
|
||||
form = new_form(fields);
|
||||
assert(form != NULL);
|
||||
set_form_win(form, frame_window);
|
||||
WINDOW *subwin = derwin(frame_window, 5, w-2, 5, 1);
|
||||
set_form_sub(form, subwin);
|
||||
wbkgd(subwin, COLOR_PAIR(WIN_PAIR));
|
||||
post_form(form);
|
||||
|
||||
wtimeout(frame_window, 1000);
|
||||
wtimeout(subwin, 1000);
|
||||
|
||||
wrefresh(frame_window);
|
||||
wrefresh(subwin);
|
||||
refresh();
|
||||
|
||||
int ch;
|
||||
string pwd = "";
|
||||
pos_form_cursor(form);
|
||||
form_driver(form, REQ_NEXT_FIELD);
|
||||
for (;;)
|
||||
{
|
||||
ch = getch();
|
||||
update_cb_();
|
||||
if (ch == ERR) continue;
|
||||
pwd = driver(form, fields, ch, subwin);
|
||||
if (pwd != "") break;
|
||||
}
|
||||
|
||||
unpost_form(form);
|
||||
free_form(form);
|
||||
free_field(fields[0]);
|
||||
free_field(fields[1]);
|
||||
delwin(frame_window);
|
||||
delwin(subwin);
|
||||
refresh();
|
||||
|
||||
return pwd;
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
Copyright (C) 2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef UI_H
|
||||
#define UI_H
|
||||
|
||||
#include<assert.h>
|
||||
#include<curses.h>
|
||||
#include<form.h>
|
||||
#include<menu.h>
|
||||
#include<string.h>
|
||||
#include<stdio.h>
|
||||
#include<stdlib.h>
|
||||
|
||||
#include<functional>
|
||||
#include<string>
|
||||
#include<vector>
|
||||
|
||||
#define BG_PAIR 1
|
||||
#define WIN_PAIR 2
|
||||
#define TITLE_PAIR 3
|
||||
#define HILIGHT_PAIR 4
|
||||
|
||||
void initUI();
|
||||
|
||||
void registerUpdateCB(std::function<void()> cb);
|
||||
|
||||
void printAt(WINDOW *win, int y, int x, const char *str, chtype color);
|
||||
void printMiddle(WINDOW *win, int y, int width, const char *str, chtype color);
|
||||
|
||||
int selectFromMenu(const char *title, const char *menu[]);
|
||||
int selectFromMenu(const char *title, std::vector<std::string> menu);
|
||||
void displayInformationAndWait(std::string title, std::vector<std::string> entries, int px=-1, int py=-1);
|
||||
void displayInformationNoWait(WINDOW **win, std::string title, std::vector<std::string> entries, int px=-1, int py=-1);
|
||||
void displayStatusLineNoWait(WINDOW **win, std::vector<std::string> entries, int px=-1, int py=-1);
|
||||
std::string displayInformationAndInput(std::string title, std::vector<std::string> entries, int px=-1, int py=-1);
|
||||
std::string inputField(std::string title, std::vector<std::string> entries, std::string label);
|
||||
void notImplementedYet(std::string msg);
|
||||
|
||||
#endif
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2019 Fredrik Öhrström
|
||||
Copyright (C) 2019-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
|
207
src/util.cc
207
src/util.cc
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2017-2019 Fredrik Öhrström
|
||||
Copyright (C) 2017-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -19,6 +19,7 @@
|
|||
#include"meters.h"
|
||||
#include"shell.h"
|
||||
|
||||
#include<algorithm>
|
||||
#include<assert.h>
|
||||
#include<dirent.h>
|
||||
#include<functional>
|
||||
|
@ -228,6 +229,18 @@ std::string safeString(vector<uchar> &target) {
|
|||
return str;
|
||||
}
|
||||
|
||||
string tostrprintf(const char* fmt, ...)
|
||||
{
|
||||
string s;
|
||||
char buf[4096];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buf, 4095, fmt, args);
|
||||
va_end(args);
|
||||
s = buf;
|
||||
return s;
|
||||
}
|
||||
|
||||
void strprintf(std::string &s, const char* fmt, ...)
|
||||
{
|
||||
char buf[4096];
|
||||
|
@ -537,6 +550,22 @@ bool isValidMatchExpressions(string mes, bool non_compliant)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool isValidId(string id, bool accept_non_compliant)
|
||||
{
|
||||
|
||||
for (size_t i=0; i<id.length(); ++i)
|
||||
{
|
||||
if (id[i] >= '0' && id[i] <= '9') continue;
|
||||
if (accept_non_compliant)
|
||||
{
|
||||
if (id[i] >= 'a' && id[i] <= 'f') continue;
|
||||
if (id[i] >= 'A' && id[i] <= 'F') continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool doesIdMatchExpression(string id, string match)
|
||||
{
|
||||
if (id.length() == 0) return false;
|
||||
|
@ -640,7 +669,8 @@ bool isFrequency(std::string& fq)
|
|||
{
|
||||
int len = fq.length();
|
||||
if (len == 0) return false;
|
||||
if (fq[len-1] == 'M') len--;
|
||||
if (fq[len-1] != 'M') return false;
|
||||
len--;
|
||||
for (int i=0; i<len; ++i) {
|
||||
if (!isdigit(fq[i]) && fq[i] != '.') return false;
|
||||
}
|
||||
|
@ -844,6 +874,15 @@ void padWithZeroesTo(vector<uchar> *content, size_t len, vector<uchar> *full_con
|
|||
}
|
||||
}
|
||||
|
||||
static string space = " ";
|
||||
string padLeft(string input, int width)
|
||||
{
|
||||
int w = width-input.size();
|
||||
if (w < 0) return input;
|
||||
assert(w < (int)space.length());
|
||||
return space.substr(0, w)+input;
|
||||
}
|
||||
|
||||
int parseTime(string time) {
|
||||
int mul = 1;
|
||||
if (time.back() == 'h') {
|
||||
|
@ -952,6 +991,48 @@ bool listFiles(string dir, vector<string> *files)
|
|||
return true;
|
||||
}
|
||||
|
||||
int loadFile(string file, vector<string> *lines)
|
||||
{
|
||||
char block[32768+1];
|
||||
vector<uchar> buf;
|
||||
|
||||
int fd = open(file.c_str(), O_RDONLY);
|
||||
if (fd == -1) {
|
||||
return -1;
|
||||
}
|
||||
while (true) {
|
||||
ssize_t n = read(fd, block, sizeof(block));
|
||||
if (n == -1) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
error("Could not read file %s errno=%d\n", file.c_str(), errno);
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
buf.insert(buf.end(), block, block+n);
|
||||
if (n < (ssize_t)sizeof(block)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
|
||||
bool eof, err;
|
||||
auto i = buf.begin();
|
||||
for (;;) {
|
||||
string line = eatTo(buf, i, '\n', 32768, &eof, &err);
|
||||
if (err) {
|
||||
error("Error parsing simulation file.\n");
|
||||
}
|
||||
if (line.length() > 0) {
|
||||
lines->push_back(line);
|
||||
}
|
||||
if (eof) break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool loadFile(string file, vector<char> *buf)
|
||||
{
|
||||
int blocksize = 1024;
|
||||
|
@ -1055,6 +1136,13 @@ string strdatetime(struct tm *datetime)
|
|||
return string(buf);
|
||||
}
|
||||
|
||||
string strdatetimesec(struct tm *datetime)
|
||||
{
|
||||
char buf[256];
|
||||
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", datetime);
|
||||
return string(buf);
|
||||
}
|
||||
|
||||
AccessCheck checkIfExistsAndSameGroup(string device)
|
||||
{
|
||||
struct stat sb;
|
||||
|
@ -1354,13 +1442,28 @@ size_t memoryUsage()
|
|||
|
||||
vector<string> alarm_shells_;
|
||||
|
||||
void logAlarm(string type, string msg)
|
||||
const char* toString(Alarm type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case Alarm::DeviceFailure: return "DeviceFailure";
|
||||
case Alarm::RegularResetFailure: return "RegularResetFailure";
|
||||
case Alarm::DeviceInactivity: return "DeviceInactivity";
|
||||
case Alarm::SpecifiedDeviceNotFound: return "SpecifiedDeviceNotFound";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
void logAlarm(Alarm type, string info)
|
||||
{
|
||||
vector<string> envs;
|
||||
envs.push_back("ALARM_TYPE="+type);
|
||||
string ts = toString(type);
|
||||
envs.push_back("ALARM_TYPE="+ts);
|
||||
|
||||
string msg = tostrprintf("(alarm %s) %s", ts.c_str(), info.c_str());
|
||||
envs.push_back("ALARM_MESSAGE="+msg);
|
||||
|
||||
warning("(alarm) %s: %s\n", type.c_str(), msg.c_str());
|
||||
warning("%s\n", msg.c_str());
|
||||
|
||||
for (auto &s : alarm_shells_)
|
||||
{
|
||||
|
@ -1375,3 +1478,97 @@ void setAlarmShells(vector<string> &alarm_shells)
|
|||
{
|
||||
alarm_shells_ = alarm_shells;
|
||||
}
|
||||
|
||||
bool stringFoundCaseIgnored(string haystack, string needle)
|
||||
{
|
||||
// Modify haystack and needle, in place, to become lowercase.
|
||||
std::for_each(haystack.begin(), haystack.end(), [](char & c) {
|
||||
c = ::tolower(c);
|
||||
});
|
||||
std::for_each(needle.begin(), needle.end(), [](char & c) {
|
||||
c = ::tolower(c);
|
||||
});
|
||||
|
||||
// Now use default c++ find, return true if needle was found in haystack.
|
||||
return haystack.find(needle) != string::npos;
|
||||
}
|
||||
|
||||
vector<string> splitString(string &s, char c)
|
||||
{
|
||||
auto end = s.cend();
|
||||
auto start = end;
|
||||
|
||||
std::vector<std::string> v;
|
||||
for (auto i = s.cbegin(); i != end; ++i)
|
||||
{
|
||||
if (*i != c)
|
||||
{
|
||||
if (start == end)
|
||||
{
|
||||
start = i;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (start != end)
|
||||
{
|
||||
v.emplace_back(start, i);
|
||||
start = end;
|
||||
}
|
||||
}
|
||||
if (start != end)
|
||||
{
|
||||
v.emplace_back(start, end);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
uint32_t indexFromRtlSdrName(string &s)
|
||||
{
|
||||
size_t p = s.find('_');
|
||||
if (p == string::npos) return -1;
|
||||
string n = s.substr(0, p);
|
||||
return (uint32_t)atoi(n.c_str());
|
||||
}
|
||||
|
||||
#define KB 1024ull
|
||||
|
||||
string helper(size_t scale, size_t s, string suffix)
|
||||
{
|
||||
size_t o = s;
|
||||
s /= scale;
|
||||
size_t diff = o-(s*scale);
|
||||
if (diff == 0) {
|
||||
return to_string(s) + ".00"+suffix;
|
||||
}
|
||||
size_t dec = (int)(100*(diff+1) / scale);
|
||||
return to_string(s) + ((dec<10)?".0":".") + to_string(dec) + suffix;
|
||||
}
|
||||
|
||||
string humanReadableTwoDecimals(size_t s)
|
||||
{
|
||||
if (s < KB)
|
||||
{
|
||||
return to_string(s) + " B";
|
||||
}
|
||||
if (s < KB * KB)
|
||||
{
|
||||
return helper(KB, s, " KiB");
|
||||
}
|
||||
if (s < KB * KB * KB)
|
||||
{
|
||||
return helper(KB*KB, s, " MiB");
|
||||
}
|
||||
#if SIZEOF_SIZE_T == 8
|
||||
if (s < KB * KB * KB * KB)
|
||||
{
|
||||
return helper(KB*KB*KB, s, " GiB");
|
||||
}
|
||||
if (s < KB * KB * KB * KB * KB)
|
||||
{
|
||||
return helper(KB*KB*KB*KB, s, " TiB");
|
||||
}
|
||||
return helper(KB*KB*KB*KB*KB, s, " PiB");
|
||||
#else
|
||||
return helper(KB*KB*KB, s, " GiB");
|
||||
#endif
|
||||
}
|
||||
|
|
27
src/util.h
27
src/util.h
|
@ -47,10 +47,16 @@ std::string bin2hex(std::vector<uchar> &target);
|
|||
std::string bin2hex(std::vector<uchar>::iterator data, std::vector<uchar>::iterator end, int len);
|
||||
std::string safeString(std::vector<uchar> &target);
|
||||
void strprintf(std::string &s, const char* fmt, ...);
|
||||
std::string tostrprintf(const char* fmt, ...);
|
||||
|
||||
// Return for example: 2010-03-21
|
||||
std::string strdate(struct tm *date);
|
||||
// Return for example: 2010-03-21 15:22:03
|
||||
// Return for example: 2010-03-21 15:22
|
||||
std::string strdatetime(struct tm *date);
|
||||
// Return for example: 2010-03-21 15:22:03
|
||||
std::string strdatetimesec(struct tm *date);
|
||||
|
||||
bool stringFoundCaseIgnored(std::string haystack, std::string needle);
|
||||
|
||||
void xorit(uchar *srca, uchar *srcb, uchar *dest, int len);
|
||||
void shiftLeft(uchar *srca, uchar *srcb, int len);
|
||||
|
@ -83,19 +89,30 @@ void debugPayload(std::string intro, std::vector<uchar> &payload);
|
|||
void debugPayload(std::string intro, std::vector<uchar> &payload, std::vector<uchar>::iterator &pos);
|
||||
void logTelegram(std::string intro, std::vector<uchar> &parsed, int header_size, int suffix_size);
|
||||
|
||||
void logAlarm(std::string type, std::string msg);
|
||||
enum class Alarm
|
||||
{
|
||||
DeviceFailure,
|
||||
RegularResetFailure,
|
||||
DeviceInactivity,
|
||||
SpecifiedDeviceNotFound
|
||||
};
|
||||
|
||||
const char* toString(Alarm type);
|
||||
void logAlarm(Alarm type, std::string info);
|
||||
void setAlarmShells(std::vector<std::string> &alarm_shells);
|
||||
|
||||
bool isValidMatchExpression(std::string id, bool non_compliant);
|
||||
bool isValidMatchExpressions(std::string ids, bool non_compliant);
|
||||
bool doesIdMatchExpression(std::string id, std::string match);
|
||||
bool doesIdMatchExpressions(std::string& id, std::vector<std::string>& ids);
|
||||
bool isValidId(std::string id, bool accept_non_compliant);
|
||||
|
||||
bool isValidKey(std::string& key, MeterType mt);
|
||||
bool isFrequency(std::string& fq);
|
||||
bool isNumber(std::string& fq);
|
||||
|
||||
std::vector<std::string> splitMatchExpressions(std::string& mes);
|
||||
std::vector<std::string> splitString(std::string &s, char c);
|
||||
|
||||
void incrementIV(uchar *iv, size_t len);
|
||||
|
||||
|
@ -104,11 +121,13 @@ bool checkFileExists(const char *file);
|
|||
bool checkIfSimulationFile(const char *file);
|
||||
bool checkIfDirExists(const char *dir);
|
||||
bool listFiles(std::string dir, std::vector<std::string> *files);
|
||||
int loadFile(std::string file, std::vector<std::string> *lines);
|
||||
bool loadFile(std::string file, std::vector<char> *buf);
|
||||
|
||||
std::string eatTo(std::vector<uchar> &v, std::vector<uchar>::iterator &i, int c, size_t max, bool *eof, bool *err);
|
||||
|
||||
void padWithZeroesTo(std::vector<uchar> *content, size_t len, std::vector<uchar> *full_content);
|
||||
std::string padLeft(std::string input, int width);
|
||||
|
||||
// Parse text string into seconds, 5h = (3600*5) 2m = (60*2) 1s = 1
|
||||
int parseTime(std::string time);
|
||||
|
@ -163,4 +182,8 @@ bool startsWith(std::string s, std::vector<uchar> &data);
|
|||
// Sum the memory used by the heap and stack.
|
||||
size_t memoryUsage();
|
||||
|
||||
std::string humanReadableTwoDecimals(size_t s);
|
||||
|
||||
uint32_t indexFromRtlSdrName(std::string &s);
|
||||
|
||||
#endif
|
||||
|
|
891
src/wmbus.cc
891
src/wmbus.cc
Plik diff jest za duży
Load Diff
253
src/wmbus.h
253
src/wmbus.h
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2017-2019 Fredrik Öhrström
|
||||
Copyright (C) 2017-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -30,6 +30,87 @@
|
|||
bool trimCRCsFrameFormatA(std::vector<uchar> &payload);
|
||||
bool trimCRCsFrameFormatB(std::vector<uchar> &payload);
|
||||
|
||||
// A wmbus specified device is supplied on the command line or in the config file.
|
||||
// It has this format "file:type(id):fq:bps:linkmods:CMD(command)"
|
||||
struct SpecifiedDevice
|
||||
{
|
||||
int index; // 0,1,2,3 the order on the command line / config file.
|
||||
std::string file; // simulation_meter.txt, stdin, file.raw, /dev/ttyUSB0
|
||||
bool is_tty{}, is_stdin{}, is_file{}, is_simulation{};
|
||||
std::string type; // im871a, rtlwmbus
|
||||
std::string id; // 12345678 for wmbus dongles or 0,1 for rtlwmbus indexes.
|
||||
std::string fq; // 868.95M
|
||||
std::string bps; // 9600
|
||||
std::string linkmodes; // c1,t1,s1
|
||||
std::string command; // command line of background process that streams data into wmbusmeters
|
||||
|
||||
bool handled {}; // Set to true when this device has been detected/handled.
|
||||
time_t last_alarm {}; // Last time an alarm was sent for this device not being found.
|
||||
|
||||
void clear();
|
||||
string str();
|
||||
bool parse(string &s);
|
||||
};
|
||||
|
||||
#define LIST_OF_MBUS_DEVICES \
|
||||
X(UNKNOWN,unknown) \
|
||||
X(AMB8465,amb8465) \
|
||||
X(CUL,cul) \
|
||||
X(D1TC,d1tc) \
|
||||
X(IM871A,im871a) \
|
||||
X(RAWTTY,rawtty) \
|
||||
X(RC1180,rc1180) \
|
||||
X(RTL433,rtl433) \
|
||||
X(RTLWMBUS,rtlwmbus) \
|
||||
X(SIMULATION,simulation)
|
||||
|
||||
enum WMBusDeviceType {
|
||||
#define X(name,text) DEVICE_ ## name,
|
||||
LIST_OF_MBUS_DEVICES
|
||||
#undef X
|
||||
};
|
||||
|
||||
const char *toString(WMBusDeviceType t);
|
||||
const char *toLowerCaseString(WMBusDeviceType t);
|
||||
WMBusDeviceType toWMBusDeviceType(string &t);
|
||||
|
||||
struct Detected
|
||||
{
|
||||
SpecifiedDevice specified_device {}; // Device as specified from the command line / config file.
|
||||
|
||||
string found_file; // The device file to use.
|
||||
string found_device_id; // An "unique" identifier, typically the id used by the dongle as its own wmbus id, if it transmits.
|
||||
WMBusDeviceType found_type {}; // IM871A, AMB8465 etc.
|
||||
int found_bps {}; // Serial speed of tty, overrides
|
||||
bool found_tty_override {}; // override tty
|
||||
bool found_cmd_override {}; // override cmd
|
||||
string found_command;
|
||||
|
||||
void setSpecifiedDeviceAsAuto()
|
||||
{
|
||||
specified_device.clear();
|
||||
}
|
||||
|
||||
void setSpecifiedDevice(SpecifiedDevice sd)
|
||||
{
|
||||
specified_device = sd;
|
||||
}
|
||||
|
||||
void setAsFound(string id, WMBusDeviceType t, int b, bool to, bool co)
|
||||
{
|
||||
found_device_id = id;
|
||||
found_type = t;
|
||||
found_bps = b;
|
||||
found_tty_override = to;
|
||||
found_cmd_override = co;
|
||||
}
|
||||
|
||||
std::string str()
|
||||
{
|
||||
return found_file+":"+string(toString(found_type))+"["+found_device_id+"]"+":"+to_string(found_bps)+"/"+to_string(found_tty_override);
|
||||
}
|
||||
};
|
||||
|
||||
#define LIST_OF_LINK_MODES \
|
||||
X(Any,any,--anylinkmode,0xffff) \
|
||||
X(C1,c1,--c1,0x1) \
|
||||
|
@ -44,11 +125,18 @@ bool trimCRCsFrameFormatB(std::vector<uchar> &payload);
|
|||
X(N1f,n1f,--n1f,0x200) \
|
||||
X(UNKNOWN,unknown,----,0x0)
|
||||
|
||||
// In link mode S1, is used when both the transmitter and receiver are stationary.
|
||||
// It can be transmitted relatively seldom.
|
||||
|
||||
// In link mode T1, the meter transmits a telegram every few seconds or minutes.
|
||||
// Suitable for drive-by/walk-by collection of meter values.
|
||||
|
||||
// Link mode C1 is like T1 but uses less energy when transmitting due to
|
||||
// a different radio encoding.
|
||||
// a different radio encoding. Also significant is:
|
||||
// S1/T1 usually uses the A format for the data link layer, more CRCs.
|
||||
// C1 usually uses the B format for the data link layer, less CRCs = less overhead.
|
||||
|
||||
// The im871a can for example receive C1a, but it is unclear if there are any meters that use it.
|
||||
|
||||
enum class LinkMode {
|
||||
#define X(name,lcname,option,val) name,
|
||||
|
@ -103,6 +191,7 @@ private:
|
|||
};
|
||||
|
||||
LinkModeSet parseLinkModes(string modes);
|
||||
bool isValidLinkModes(string modes);
|
||||
|
||||
enum class CONNECTION
|
||||
{
|
||||
|
@ -225,15 +314,28 @@ struct MeterKeys
|
|||
{
|
||||
vector<uchar> confidentiality_key;
|
||||
vector<uchar> authentication_key;
|
||||
bool simulation {};
|
||||
|
||||
bool hasConfidentialityKey() { return confidentiality_key.size() > 0; }
|
||||
bool hasAuthenticationKey() { return authentication_key.size() > 0; }
|
||||
bool isSimulation() { return simulation; }
|
||||
};
|
||||
|
||||
struct AboutTelegram
|
||||
{
|
||||
// wmbus device used to receive this telegram.
|
||||
string device;
|
||||
// The device's opinion of the rssi, best effort conversion into the dbm scale.
|
||||
// -100 dbm = 0.1 pico Watt to -20 dbm = 10 micro W
|
||||
// Measurements smaller than -100 and larger than -10 are unlikely.
|
||||
int rssi_dbm {};
|
||||
|
||||
AboutTelegram(string dv, int rs) : device(dv), rssi_dbm(rs) {}
|
||||
AboutTelegram() {}
|
||||
};
|
||||
|
||||
struct Telegram
|
||||
{
|
||||
AboutTelegram about;
|
||||
|
||||
// The meter address as a string usually printed on the meter.
|
||||
string id;
|
||||
// If decryption failed, set this to true, to prevent further processing.
|
||||
|
@ -344,6 +446,7 @@ struct Telegram
|
|||
void explainParse(string intro, int from);
|
||||
|
||||
bool isSimulated() { return is_simulated_; }
|
||||
void markAsSimulated() { is_simulated_ = true; }
|
||||
|
||||
// Extracted mbus values.
|
||||
std::map<std::string,std::pair<int,DVEntry>> values;
|
||||
|
@ -391,43 +494,39 @@ private:
|
|||
|
||||
struct Meter;
|
||||
|
||||
#define LIST_OF_MBUS_DEVICES \
|
||||
X(DEVICE_UNKNOWN) \
|
||||
X(DEVICE_CUL)\
|
||||
X(DEVICE_D1TC)\
|
||||
X(DEVICE_IM871A)\
|
||||
X(DEVICE_AMB8465)\
|
||||
X(DEVICE_RFMRX2)\
|
||||
X(DEVICE_SIMULATOR)\
|
||||
X(DEVICE_RTLWMBUS)\
|
||||
X(DEVICE_RTL433)\
|
||||
X(DEVICE_RAWTTY)\
|
||||
X(DEVICE_WMB13U)
|
||||
|
||||
enum WMBusDeviceType {
|
||||
#define X(name) name,
|
||||
LIST_OF_MBUS_DEVICES
|
||||
#undef X
|
||||
};
|
||||
|
||||
const char *toString(WMBusDeviceType t);
|
||||
|
||||
struct WMBus
|
||||
{
|
||||
virtual WMBusDeviceType type() = 0;
|
||||
// I wmbus device identifier consists of:
|
||||
// device:type[id] for example:
|
||||
// /dev/ttyUSB1:im871a[12345678]
|
||||
|
||||
virtual std::string device() = 0;
|
||||
virtual bool ping() = 0;
|
||||
virtual uint32_t getDeviceId() = 0;
|
||||
virtual WMBusDeviceType type() = 0;
|
||||
// The device id is the changeable id of the dongle.
|
||||
// For im871a,amb8465 it is the transmit address.
|
||||
// For rtlsdr it is the id set using rtl_eeprom.
|
||||
// Not all dongles have this.
|
||||
virtual string getDeviceId() = 0;
|
||||
// The im871a and amb8465 dongles does have a unique, immutable id as well.
|
||||
// Not all dongles have this.
|
||||
virtual string getDeviceUniqueId() = 0;
|
||||
|
||||
virtual std::string hr() = 0;
|
||||
|
||||
virtual LinkModeSet getLinkModes() = 0;
|
||||
virtual bool ping() = 0;
|
||||
virtual LinkModeSet supportedLinkModes() = 0;
|
||||
virtual int numConcurrentLinkModes() = 0;
|
||||
virtual bool canSetLinkModes(LinkModeSet lms) = 0;
|
||||
virtual void setMeters(vector<unique_ptr<Meter>> *meters) = 0;
|
||||
virtual void setLinkModes(LinkModeSet lms) = 0;
|
||||
virtual void onTelegram(function<bool(vector<uchar>)> cb) = 0;
|
||||
virtual void onTelegram(function<bool(AboutTelegram&,vector<uchar>)> cb) = 0;
|
||||
virtual SerialDevice *serial() = 0;
|
||||
// Return true of the serial has been overridden, usually with stdin or a file.
|
||||
virtual bool serialOverride() = 0;
|
||||
virtual void simulate() = 0;
|
||||
// This will check if the wmbus devices needs reset.
|
||||
// Return true if underlying device is ok and device in general seems to be working.
|
||||
virtual bool isWorking() = 0;
|
||||
// This will check if the wmbus devices needs a reset and then immediately perform the reset.
|
||||
virtual void checkStatus() = 0;
|
||||
// Close any underlying ttys or software and restart/reinitialize.
|
||||
// Return true if ok.
|
||||
|
@ -436,42 +535,43 @@ struct WMBus
|
|||
// within seconds, then invoke reset(). However do not reset
|
||||
// when no activity is expected.
|
||||
virtual void setTimeout(int seconds, std::string expected_activity) = 0;
|
||||
// Set a regular interval for resetting the wmbus device.
|
||||
// Default is once ever 24 hours.
|
||||
virtual void setResetInterval(int seconds) = 0;
|
||||
// Close this device.
|
||||
virtual void close() = 0;
|
||||
// Remember how this device was detected.
|
||||
virtual void setDetected(Detected detected) = 0;
|
||||
virtual Detected *getDetected() = 0;
|
||||
virtual ~WMBus() = 0;
|
||||
};
|
||||
|
||||
Detected detectWMBusDeviceWithFile(SpecifiedDevice &specified_device,
|
||||
shared_ptr<SerialCommunicationManager> manager);
|
||||
Detected detectWMBusDeviceWithCommand(SpecifiedDevice &specified_device,
|
||||
shared_ptr<SerialCommunicationManager> handler);
|
||||
|
||||
struct Detected
|
||||
{
|
||||
WMBusDeviceType type; // IM871A, AMB8465 etc
|
||||
string devicefile; // /dev/ttyUSB0 /dev/ttyACM0 stdin simulation_abc.txt telegrams.raw
|
||||
int baudrate; // If the suffix is a number, store the number here.
|
||||
// If the override_tty is true, then do not allow the wmbus driver to open the tty,
|
||||
// instead open the devicefile first. This is to allow feeding the wmbus drivers using stdin
|
||||
// or a file or for internal testing.
|
||||
bool override_tty;
|
||||
};
|
||||
|
||||
Detected detectWMBusDeviceSetting(string devicefile, string suffix,
|
||||
SerialCommunicationManager *manager);
|
||||
|
||||
unique_ptr<WMBus> openIM871A(string device, SerialCommunicationManager *manager,
|
||||
unique_ptr<SerialDevice> serial_override);
|
||||
unique_ptr<WMBus> openAMB8465(string device, SerialCommunicationManager *manager,
|
||||
unique_ptr<SerialDevice> serial_override);
|
||||
unique_ptr<WMBus> openRawTTY(string device, int baudrate, SerialCommunicationManager *manager,
|
||||
unique_ptr<SerialDevice> serial_override);
|
||||
unique_ptr<WMBus> openRTLWMBUS(string device, SerialCommunicationManager *manager, std::function<void()> on_exit,
|
||||
unique_ptr<SerialDevice> serial_override);
|
||||
unique_ptr<WMBus> openRTL433(string device, SerialCommunicationManager *manager, std::function<void()> on_exit,
|
||||
unique_ptr<SerialDevice> serial_override);
|
||||
unique_ptr<WMBus> openCUL(string device, SerialCommunicationManager *manager,
|
||||
unique_ptr<SerialDevice> serial_override);
|
||||
unique_ptr<WMBus> openD1TC(string device, SerialCommunicationManager *manager,
|
||||
unique_ptr<SerialDevice> serial_override);
|
||||
unique_ptr<WMBus> openWMB13U(string device, SerialCommunicationManager *manager,
|
||||
unique_ptr<SerialDevice> serial_override);
|
||||
unique_ptr<WMBus> openSimulator(string file, SerialCommunicationManager *manager,
|
||||
unique_ptr<SerialDevice> serial_override);
|
||||
shared_ptr<WMBus> openIM871A(string device, shared_ptr<SerialCommunicationManager> manager,
|
||||
shared_ptr<SerialDevice> serial_override);
|
||||
shared_ptr<WMBus> openAMB8465(string device, shared_ptr<SerialCommunicationManager> manager,
|
||||
shared_ptr<SerialDevice> serial_override);
|
||||
shared_ptr<WMBus> openRawTTY(string device, int baudrate, shared_ptr<SerialCommunicationManager> manager,
|
||||
shared_ptr<SerialDevice> serial_override);
|
||||
shared_ptr<WMBus> openRC1180(string device, shared_ptr<SerialCommunicationManager> manager,
|
||||
shared_ptr<SerialDevice> serial_override);
|
||||
shared_ptr<WMBus> openRTLWMBUS(string identifier, string command, shared_ptr<SerialCommunicationManager> manager, std::function<void()> on_exit,
|
||||
shared_ptr<SerialDevice> serial_override);
|
||||
shared_ptr<WMBus> openRTL433(string identifier, string command, shared_ptr<SerialCommunicationManager> manager, std::function<void()> on_exit,
|
||||
shared_ptr<SerialDevice> serial_override);
|
||||
shared_ptr<WMBus> openCUL(string device, shared_ptr<SerialCommunicationManager> manager,
|
||||
shared_ptr<SerialDevice> serial_override);
|
||||
shared_ptr<WMBus> openD1TC(string device, shared_ptr<SerialCommunicationManager> manager,
|
||||
shared_ptr<SerialDevice> serial_override);
|
||||
shared_ptr<WMBus> openWMB13U(string device, shared_ptr<SerialCommunicationManager> manager,
|
||||
shared_ptr<SerialDevice> serial_override);
|
||||
shared_ptr<WMBus> openSimulator(string file, shared_ptr<SerialCommunicationManager> manager,
|
||||
shared_ptr<SerialDevice> serial_override);
|
||||
|
||||
string manufacturer(int m_field);
|
||||
string manufacturerFlag(int m_field);
|
||||
|
@ -504,14 +604,14 @@ MeasurementType difMeasurementType(int dif);
|
|||
string linkModeName(LinkMode link_mode);
|
||||
string measurementTypeName(MeasurementType mt);
|
||||
|
||||
AccessCheck findAndDetect(SerialCommunicationManager *manager,
|
||||
AccessCheck findAndDetect(shared_ptr<SerialCommunicationManager> manager,
|
||||
string *out_device,
|
||||
function<AccessCheck(string,SerialCommunicationManager*)> check,
|
||||
function<AccessCheck(string,shared_ptr<SerialCommunicationManager>)> check,
|
||||
string dongle_name,
|
||||
string device_root);
|
||||
|
||||
AccessCheck checkAccessAndDetect(SerialCommunicationManager *manager,
|
||||
function<AccessCheck(string,SerialCommunicationManager*)> check,
|
||||
AccessCheck checkAccessAndDetect(shared_ptr<SerialCommunicationManager> manager,
|
||||
function<AccessCheck(string,shared_ptr<SerialCommunicationManager>)> check,
|
||||
string dongle_name,
|
||||
string device);
|
||||
|
||||
|
@ -523,15 +623,22 @@ FrameStatus checkWMBusFrame(vector<uchar> &data,
|
|||
int *payload_len_out,
|
||||
int *payload_offset);
|
||||
|
||||
AccessCheck detectIM871A(string device, SerialCommunicationManager *handler);
|
||||
AccessCheck detectAMB8465(string device, SerialCommunicationManager *handler);
|
||||
AccessCheck detectRawTTY(string device, int baud, SerialCommunicationManager *handler);
|
||||
AccessCheck detectRTLSDR(string device, SerialCommunicationManager *handler);
|
||||
AccessCheck detectCUL(string device, SerialCommunicationManager *handler);
|
||||
AccessCheck detectWMB13U(string device, SerialCommunicationManager *handler);
|
||||
AccessCheck detectDevice(Detected *detected, shared_ptr<SerialCommunicationManager> handler);
|
||||
|
||||
AccessCheck detectAMB8465(Detected *detected, shared_ptr<SerialCommunicationManager> handler);
|
||||
AccessCheck detectCUL(Detected *detected, shared_ptr<SerialCommunicationManager> handler);
|
||||
AccessCheck detectD1TC(Detected *detected, shared_ptr<SerialCommunicationManager> manager);
|
||||
AccessCheck detectIM871A(Detected *detected, shared_ptr<SerialCommunicationManager> handler);
|
||||
AccessCheck detectRAWTTY(Detected *detected, shared_ptr<SerialCommunicationManager> handler);
|
||||
AccessCheck detectRC1180(Detected *detected, shared_ptr<SerialCommunicationManager> handler);
|
||||
AccessCheck detectRTL433(Detected *detected, shared_ptr<SerialCommunicationManager> handler);
|
||||
AccessCheck detectRTLWMBUS(Detected *detected, shared_ptr<SerialCommunicationManager> handler);
|
||||
AccessCheck detectWMB13U(Detected *detected, shared_ptr<SerialCommunicationManager> handler);
|
||||
|
||||
// Try to factory reset an AMB8465 by trying all possible serial speeds and
|
||||
// restore to factory settings.
|
||||
AccessCheck factoryResetAMB8465(string device, SerialCommunicationManager *handler, int *was_baud);
|
||||
AccessCheck factoryResetAMB8465(string tty, shared_ptr<SerialCommunicationManager> handler, int *was_baud);
|
||||
|
||||
Detected detectWMBusDeviceOnTTY(string tty, shared_ptr<SerialCommunicationManager> handler);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -16,13 +16,14 @@
|
|||
*/
|
||||
|
||||
#include"wmbus.h"
|
||||
#include"wmbus_common_implementation.h"
|
||||
#include"wmbus_utils.h"
|
||||
#include"wmbus_amb8465.h"
|
||||
#include"serial.h"
|
||||
#include"threads.h"
|
||||
|
||||
#include<assert.h>
|
||||
#include<pthread.h>
|
||||
#include<semaphore.h>
|
||||
#include<errno.h>
|
||||
#include<unistd.h>
|
||||
#include<sys/time.h>
|
||||
|
@ -30,10 +31,56 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
struct ConfigAMB8465
|
||||
{
|
||||
uchar uart_ctl0 {};
|
||||
uchar uart_ctl1 {};
|
||||
uchar received_frames_as_cmd {};
|
||||
uchar c_field {};
|
||||
uint16_t mfct {};
|
||||
uint32_t id {};
|
||||
uchar version {};
|
||||
uchar media {};
|
||||
|
||||
uchar auto_rssi {};
|
||||
|
||||
string dongleId()
|
||||
{
|
||||
return tostrprintf("%08x", id);
|
||||
}
|
||||
|
||||
string str()
|
||||
{
|
||||
string ids = tostrprintf("id=%08x media=%02x version=%02x c_field=%02x auto_rssi=%02x", id, media, version, c_field, auto_rssi);
|
||||
return ids;
|
||||
}
|
||||
|
||||
bool decode(vector<uchar> &bytes)
|
||||
{
|
||||
size_t o = 5;
|
||||
if (bytes.size() < o) return false;
|
||||
|
||||
uart_ctl0 = bytes[0+o];
|
||||
uart_ctl1 = bytes[0+o];
|
||||
|
||||
received_frames_as_cmd = bytes[5+o];
|
||||
c_field = bytes[49+o];
|
||||
id = bytes[51+o]<<8|bytes[50+o];
|
||||
mfct = bytes[55+o]<<24|bytes[54+o]<<16|bytes[53+o]<<8|bytes[52+o];
|
||||
version = bytes[56+o];
|
||||
media = bytes[57+o];
|
||||
|
||||
auto_rssi = bytes[69+o];
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct WMBusAmber : public virtual WMBusCommonImplementation
|
||||
{
|
||||
bool ping();
|
||||
uint32_t getDeviceId();
|
||||
string getDeviceId();
|
||||
string getDeviceUniqueId();
|
||||
LinkModeSet getLinkModes();
|
||||
void deviceSetLinkModes(LinkModeSet lms);
|
||||
void deviceReset();
|
||||
|
@ -64,51 +111,52 @@ struct WMBusAmber : public virtual WMBusCommonImplementation
|
|||
return false;
|
||||
}
|
||||
void processSerialData();
|
||||
void getConfiguration();
|
||||
bool getConfiguration();
|
||||
void simulate() { }
|
||||
|
||||
WMBusAmber(unique_ptr<SerialDevice> serial, SerialCommunicationManager *manager);
|
||||
~WMBusAmber() { }
|
||||
WMBusAmber(shared_ptr<SerialDevice> serial, shared_ptr<SerialCommunicationManager> manager);
|
||||
~WMBusAmber() {
|
||||
manager_->onDisappear(this->serial(), NULL);
|
||||
}
|
||||
|
||||
private:
|
||||
vector<uchar> read_buffer_;
|
||||
pthread_mutex_t command_lock_ = PTHREAD_MUTEX_INITIALIZER;
|
||||
sem_t command_wait_ {};
|
||||
int sent_command_ {};
|
||||
int received_command_ {};
|
||||
vector<uchar> request_;
|
||||
vector<uchar> response_;
|
||||
|
||||
LinkModeSet link_modes_ {};
|
||||
vector<uchar> received_payload_;
|
||||
bool rssi_expected_ {};
|
||||
struct timeval timestamp_last_rx_ {};
|
||||
|
||||
void waitForResponse();
|
||||
ConfigAMB8465 device_config_;
|
||||
|
||||
FrameStatus checkAMB8465Frame(vector<uchar> &data,
|
||||
size_t *frame_length,
|
||||
int *msgid_out,
|
||||
int *payload_len_out,
|
||||
int *payload_offset,
|
||||
uchar *rssi);
|
||||
void handleMessage(int msgid, vector<uchar> &frame);
|
||||
int *rssi_dbm);
|
||||
void handleMessage(int msgid, vector<uchar> &frame, int rssi_dbm);
|
||||
};
|
||||
|
||||
unique_ptr<WMBus> openAMB8465(string device, SerialCommunicationManager *manager, unique_ptr<SerialDevice> serial_override)
|
||||
shared_ptr<WMBus> openAMB8465(string device, shared_ptr<SerialCommunicationManager> manager, shared_ptr<SerialDevice> serial_override)
|
||||
{
|
||||
assert(device != "");
|
||||
|
||||
if (serial_override)
|
||||
{
|
||||
WMBusAmber *imp = new WMBusAmber(std::move(serial_override), manager);
|
||||
return unique_ptr<WMBus>(imp);
|
||||
WMBusAmber *imp = new WMBusAmber(serial_override, manager);
|
||||
return shared_ptr<WMBus>(imp);
|
||||
}
|
||||
|
||||
auto serial = manager->createSerialDeviceTTY(device.c_str(), 9600);
|
||||
WMBusAmber *imp = new WMBusAmber(std::move(serial), manager);
|
||||
return unique_ptr<WMBus>(imp);
|
||||
auto serial = manager->createSerialDeviceTTY(device.c_str(), 9600, "amb8465");
|
||||
WMBusAmber *imp = new WMBusAmber(serial, manager);
|
||||
return shared_ptr<WMBus>(imp);
|
||||
}
|
||||
|
||||
WMBusAmber::WMBusAmber(unique_ptr<SerialDevice> serial, SerialCommunicationManager *manager) :
|
||||
WMBusCommonImplementation(DEVICE_AMB8465, manager, std::move(serial))
|
||||
WMBusAmber::WMBusAmber(shared_ptr<SerialDevice> serial, shared_ptr<SerialCommunicationManager> manager) :
|
||||
WMBusCommonImplementation(DEVICE_AMB8465, manager, serial)
|
||||
{
|
||||
sem_init(&command_wait_, 0, 0);
|
||||
manager_->listenTo(this->serial(),call(this,processSerialData));
|
||||
rssi_expected_ = true;
|
||||
reset();
|
||||
}
|
||||
|
@ -118,10 +166,11 @@ void WMBusAmber::deviceReset()
|
|||
timerclear(×tamp_last_rx_);
|
||||
}
|
||||
|
||||
uchar xorChecksum(vector<uchar> msg, int len)
|
||||
uchar xorChecksum(vector<uchar> &msg, size_t len)
|
||||
{
|
||||
assert(msg.size() >= len);
|
||||
uchar c = 0;
|
||||
for (int i=0; i<len; ++i) {
|
||||
for (size_t i=0; i<len; ++i) {
|
||||
c ^= msg[i];
|
||||
}
|
||||
return c;
|
||||
|
@ -131,49 +180,54 @@ bool WMBusAmber::ping()
|
|||
{
|
||||
if (serial()->readonly()) return true; // Feeding from stdin or file.
|
||||
|
||||
pthread_mutex_lock(&command_lock_);
|
||||
// Ping it...
|
||||
pthread_mutex_unlock(&command_lock_);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t WMBusAmber::getDeviceId()
|
||||
string WMBusAmber::getDeviceId()
|
||||
{
|
||||
if (serial()->readonly()) { return 0; } // Feeding from stdin or file.
|
||||
if (serial()->readonly()) { return "?"; } // Feeding from stdin or file.
|
||||
if (cached_device_id_ != "") return cached_device_id_;
|
||||
|
||||
pthread_mutex_lock(&command_lock_);
|
||||
bool ok = getConfiguration();
|
||||
if (!ok) return "ERR";
|
||||
|
||||
vector<uchar> msg(4);
|
||||
msg[0] = AMBER_SERIAL_SOF;
|
||||
msg[1] = CMD_SERIALNO_REQ;
|
||||
msg[2] = 0; // No payload
|
||||
msg[3] = xorChecksum(msg, 3);
|
||||
cached_device_id_ = device_config_.dongleId();
|
||||
|
||||
assert(msg[3] == 0xf4);
|
||||
return cached_device_id_;
|
||||
}
|
||||
|
||||
sent_command_ = CMD_SERIALNO_REQ;
|
||||
verbose("(amb8465) get device id\n");
|
||||
bool sent = serial()->send(msg);
|
||||
string WMBusAmber::getDeviceUniqueId()
|
||||
{
|
||||
if (serial()->readonly()) { return "?"; } // Feeding from stdin or file.
|
||||
if (cached_device_unique_id_ != "") return cached_device_unique_id_;
|
||||
|
||||
uint32_t id = 0;
|
||||
LOCK_WMBUS_EXECUTING_COMMAND(get_device_unique_id);
|
||||
|
||||
if (sent)
|
||||
{
|
||||
waitForResponse();
|
||||
request_.resize(4);
|
||||
request_[0] = AMBER_SERIAL_SOF;
|
||||
request_[1] = CMD_SERIALNO_REQ;
|
||||
request_[2] = 0; // No payload
|
||||
request_[3] = xorChecksum(request_, 3);
|
||||
|
||||
if (received_command_ == (CMD_SERIALNO_REQ | 0x80))
|
||||
{
|
||||
id = received_payload_[4] << 24 |
|
||||
received_payload_[5] << 16 |
|
||||
received_payload_[6] << 8 |
|
||||
received_payload_[7];
|
||||
verbose("(amb8465) device id %08x\n", id);
|
||||
}
|
||||
}
|
||||
verbose("(amb8465) get device unique id\n");
|
||||
bool sent = serial()->send(request_);
|
||||
if (!sent) return "?";
|
||||
|
||||
pthread_mutex_unlock(&command_lock_);
|
||||
return id;
|
||||
bool ok = waitForResponse(CMD_SERIALNO_REQ | 0x80);
|
||||
if (!ok) return "?";
|
||||
|
||||
if (response_.size() < 5) return "ERR";
|
||||
|
||||
uint32_t idv =
|
||||
response_[1] << 24 |
|
||||
response_[2] << 16 |
|
||||
response_[3] << 8 |
|
||||
response_[4];
|
||||
|
||||
verbose("(amb8465) unique device id %08x\n", idv);
|
||||
|
||||
cached_device_unique_id_ = tostrprintf("%08x", idv);
|
||||
return cached_device_unique_id_;
|
||||
}
|
||||
|
||||
LinkModeSet WMBusAmber::getLinkModes()
|
||||
|
@ -186,54 +240,30 @@ LinkModeSet WMBusAmber::getLinkModes()
|
|||
return link_modes_;
|
||||
}
|
||||
|
||||
void WMBusAmber::getConfiguration()
|
||||
bool WMBusAmber::getConfiguration()
|
||||
{
|
||||
if (serial()->readonly()) { return; } // Feeding from stdin or file.
|
||||
if (serial()->readonly()) { return true; } // Feeding from stdin or file.
|
||||
|
||||
pthread_mutex_lock(&command_lock_);
|
||||
LOCK_WMBUS_EXECUTING_COMMAND(getConfiguration);
|
||||
|
||||
vector<uchar> msg(6);
|
||||
msg[0] = AMBER_SERIAL_SOF;
|
||||
msg[1] = CMD_GET_REQ;
|
||||
msg[2] = 0x02;
|
||||
msg[3] = 0x00;
|
||||
msg[4] = 0x80;
|
||||
msg[5] = xorChecksum(msg, 5);
|
||||
request_.resize(6);
|
||||
request_[0] = AMBER_SERIAL_SOF;
|
||||
request_[1] = CMD_GET_REQ;
|
||||
request_[2] = 0x02;
|
||||
request_[3] = 0x00;
|
||||
request_[4] = 0x80;
|
||||
request_[5] = xorChecksum(request_, 5);
|
||||
|
||||
assert(msg[5] == 0x77);
|
||||
assert(request_[5] == 0x77);
|
||||
|
||||
verbose("(amb8465) get config\n");
|
||||
bool sent = serial()->send(msg);
|
||||
bool sent = serial()->send(request_);
|
||||
if (!sent) return false;
|
||||
|
||||
if (!sent)
|
||||
{
|
||||
pthread_mutex_unlock(&command_lock_);
|
||||
return;
|
||||
}
|
||||
bool ok = waitForResponse(CMD_GET_REQ | 0x80);
|
||||
if (!ok) return false;
|
||||
|
||||
waitForResponse();
|
||||
|
||||
if (received_command_ == (0x80|CMD_GET_REQ))
|
||||
{
|
||||
// These are the non-volatile values stored inside the dongle.
|
||||
// However the link mode, radio channel etc might not be the one
|
||||
// that we are actually using! Since setting the link mode
|
||||
// is possible without changing the non-volatile memory.
|
||||
// But there seems to be no way of reading out the set link mode....???
|
||||
// Ie there is a disconnect between the flash and the actual running dongle.
|
||||
// Oh well.
|
||||
//
|
||||
// These are just some random config settings store in non-volatile memory.
|
||||
verbose("(amb8465) config: uart %02x\n", received_payload_[2]);
|
||||
verbose("(amb8465) config: IND output enabled %02x\n", received_payload_[5+2]);
|
||||
verbose("(amb8465) config: radio Channel %02x\n", received_payload_[60+2]);
|
||||
uchar re = received_payload_[69+2];
|
||||
verbose("(amb8465) config: rssi enabled %02x\n", re);
|
||||
rssi_expected_ = (re != 0) ? true : false;
|
||||
verbose("(amb8465) config: mode Preselect %02x\n", received_payload_[70+2]);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&command_lock_);
|
||||
return device_config_.decode(response_);
|
||||
}
|
||||
|
||||
void WMBusAmber::deviceSetLinkModes(LinkModeSet lms)
|
||||
|
@ -246,56 +276,47 @@ void WMBusAmber::deviceSetLinkModes(LinkModeSet lms)
|
|||
error("(amb8465) setting link mode(s) %s is not supported for amb8465\n", modes.c_str());
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&command_lock_);
|
||||
LOCK_WMBUS_EXECUTING_COMMAND(devicesSetLinkModes);
|
||||
|
||||
vector<uchar> msg(8);
|
||||
msg[0] = AMBER_SERIAL_SOF;
|
||||
msg[1] = CMD_SET_MODE_REQ;
|
||||
sent_command_ = msg[1];
|
||||
msg[2] = 1; // Len
|
||||
request_.resize(8);
|
||||
request_[0] = AMBER_SERIAL_SOF;
|
||||
request_[1] = CMD_SET_MODE_REQ;
|
||||
request_[2] = 1; // Len
|
||||
if (lms.has(LinkMode::C1) && lms.has(LinkMode::T1))
|
||||
{
|
||||
// Listening to both C1 and T1!
|
||||
msg[3] = 0x09;
|
||||
request_[3] = 0x09;
|
||||
}
|
||||
else if (lms.has(LinkMode::C1))
|
||||
{
|
||||
// Listening to only C1.
|
||||
msg[3] = 0x0E;
|
||||
request_[3] = 0x0E;
|
||||
}
|
||||
else if (lms.has(LinkMode::T1))
|
||||
{
|
||||
// Listening to only T1.
|
||||
msg[3] = 0x08;
|
||||
request_[3] = 0x08;
|
||||
}
|
||||
else if (lms.has(LinkMode::S1) || lms.has(LinkMode::S1m))
|
||||
{
|
||||
// Listening only to S1 and S1-m
|
||||
msg[3] = 0x03;
|
||||
request_[3] = 0x03;
|
||||
}
|
||||
msg[4] = xorChecksum(msg, 4);
|
||||
request_[4] = xorChecksum(request_, 4);
|
||||
|
||||
verbose("(amb8465) set link mode %02x\n", msg[3]);
|
||||
bool sent = serial()->send(msg);
|
||||
verbose("(amb8465) set link mode %02x\n", request_[3]);
|
||||
bool sent = serial()->send(request_);
|
||||
|
||||
if (sent) waitForResponse();
|
||||
|
||||
link_modes_ = lms;
|
||||
pthread_mutex_unlock(&command_lock_);
|
||||
}
|
||||
|
||||
void WMBusAmber::waitForResponse()
|
||||
{
|
||||
while (manager_->isRunning())
|
||||
if (sent)
|
||||
{
|
||||
int rc = sem_wait(&command_wait_);
|
||||
if (rc==0) break;
|
||||
if (rc==-1)
|
||||
bool ok = waitForResponse(CMD_SET_MODE_REQ | 0x80);
|
||||
if (!ok)
|
||||
{
|
||||
if (errno==EINTR) continue;
|
||||
break;
|
||||
warning("Warning! Did not get confirmation on set link mode for amb8465\n");
|
||||
}
|
||||
}
|
||||
|
||||
link_modes_ = lms;
|
||||
}
|
||||
|
||||
FrameStatus WMBusAmber::checkAMB8465Frame(vector<uchar> &data,
|
||||
|
@ -303,7 +324,7 @@ FrameStatus WMBusAmber::checkAMB8465Frame(vector<uchar> &data,
|
|||
int *msgid_out,
|
||||
int *payload_len_out,
|
||||
int *payload_offset,
|
||||
uchar *rssi)
|
||||
int *rssi_dbm)
|
||||
{
|
||||
if (data.size() < 2) return PartialFrame;
|
||||
debugPayload("(amb8465) checkAMB8465Frame", data);
|
||||
|
@ -341,9 +362,9 @@ FrameStatus WMBusAmber::checkAMB8465Frame(vector<uchar> &data,
|
|||
|
||||
if (rssi_len)
|
||||
{
|
||||
*rssi = data[*frame_length-2];
|
||||
signed int dbm = (*rssi >= 128) ? (*rssi - 256) / 2 - 74 : *rssi / 2 - 74;
|
||||
verbose("(amb8465) rssi %d (%d dBm)\n", *rssi, dbm);
|
||||
int rssi = (int)data[*frame_length-2];
|
||||
*rssi_dbm = (rssi >= 128) ? (rssi - 256) / 2 - 74 : rssi / 2 - 74;
|
||||
verbose("(amb8465) rssi %d (%d dBm)\n", rssi, *rssi_dbm);
|
||||
}
|
||||
|
||||
return FullFrame;
|
||||
|
@ -386,9 +407,9 @@ FrameStatus WMBusAmber::checkAMB8465Frame(vector<uchar> &data,
|
|||
|
||||
if (rssi_expected_)
|
||||
{
|
||||
*rssi = data[*frame_length-1];
|
||||
signed int dbm = (*rssi >= 128) ? (*rssi - 256) / 2 - 74 : *rssi / 2 - 74;
|
||||
verbose("(amb8465) rssi %d (%d dBm)\n", *rssi, dbm);
|
||||
int rssi = data[*frame_length-1];
|
||||
*rssi_dbm = (rssi >= 128) ? (rssi - 256) / 2 - 74 : rssi / 2 - 74;
|
||||
verbose("(amb8465) rssi %d (%d dBm)\n", rssi, *rssi_dbm);
|
||||
}
|
||||
|
||||
return FullFrame;
|
||||
|
@ -427,11 +448,11 @@ void WMBusAmber::processSerialData()
|
|||
size_t frame_length;
|
||||
int msgid;
|
||||
int payload_len, payload_offset;
|
||||
uchar rssi;
|
||||
int rssi_dbm;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
FrameStatus status = checkAMB8465Frame(read_buffer_, &frame_length, &msgid, &payload_len, &payload_offset, &rssi);
|
||||
FrameStatus status = checkAMB8465Frame(read_buffer_, &frame_length, &msgid, &payload_len, &payload_offset, &rssi_dbm);
|
||||
|
||||
if (status == PartialFrame)
|
||||
{
|
||||
|
@ -467,111 +488,128 @@ void WMBusAmber::processSerialData()
|
|||
|
||||
read_buffer_.erase(read_buffer_.begin(), read_buffer_.begin()+frame_length);
|
||||
|
||||
handleMessage(msgid, payload);
|
||||
handleMessage(msgid, payload, rssi_dbm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WMBusAmber::handleMessage(int msgid, vector<uchar> &frame)
|
||||
void WMBusAmber::handleMessage(int msgid, vector<uchar> &frame, int rssi_dbm)
|
||||
{
|
||||
switch (msgid) {
|
||||
case (0):
|
||||
{
|
||||
handleTelegram(frame);
|
||||
AboutTelegram about("amb8465["+cached_device_id_+"]", rssi_dbm);
|
||||
handleTelegram(about, frame);
|
||||
break;
|
||||
}
|
||||
case (0x80|CMD_SET_MODE_REQ):
|
||||
{
|
||||
verbose("(amb8465) set link mode completed\n");
|
||||
received_command_ = msgid;
|
||||
received_payload_.clear();
|
||||
received_payload_.insert(received_payload_.end(), frame.begin(), frame.end());
|
||||
debugPayload("(amb8465) set link mode response", received_payload_);
|
||||
sem_post(&command_wait_);
|
||||
response_.clear();
|
||||
response_.insert(response_.end(), frame.begin(), frame.end());
|
||||
debugPayload("(amb8465) set link mode response", response_);
|
||||
notifyResponseIsHere(0x80|CMD_SET_MODE_REQ);
|
||||
break;
|
||||
}
|
||||
case (0x80|CMD_GET_REQ):
|
||||
{
|
||||
verbose("(amb8465) get config completed\n");
|
||||
received_command_ = msgid;
|
||||
received_payload_.clear();
|
||||
received_payload_.insert(received_payload_.end(), frame.begin(), frame.end());
|
||||
debugPayload("(amb8465) get config response", received_payload_);
|
||||
sem_post(&command_wait_);
|
||||
response_.clear();
|
||||
response_.insert(response_.end(), frame.begin(), frame.end());
|
||||
debugPayload("(amb8465) get config response", response_);
|
||||
notifyResponseIsHere(0x80|CMD_GET_REQ);
|
||||
break;
|
||||
}
|
||||
case (0x80|CMD_SERIALNO_REQ):
|
||||
{
|
||||
verbose("(amb8465) get device id completed\n");
|
||||
received_command_ = msgid;
|
||||
received_payload_.clear();
|
||||
received_payload_.insert(received_payload_.end(), frame.begin(), frame.end());
|
||||
debugPayload("(amb8465) get device id response", received_payload_);
|
||||
sem_post(&command_wait_);
|
||||
response_.clear();
|
||||
response_.insert(response_.end(), frame.begin(), frame.end());
|
||||
debugPayload("(amb8465) get device id response", response_);
|
||||
notifyResponseIsHere(0x80|CMD_SERIALNO_REQ);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
verbose("(amb8465) unhandled device message %d\n", msgid);
|
||||
received_payload_.clear();
|
||||
received_payload_.insert(received_payload_.end(), frame.begin(), frame.end());
|
||||
debugPayload("(amb8465) unknown response", received_payload_);
|
||||
response_.clear();
|
||||
response_.insert(response_.end(), frame.begin(), frame.end());
|
||||
debugPayload("(amb8465) unknown response", response_);
|
||||
}
|
||||
}
|
||||
|
||||
AccessCheck detectAMB8465(string device, SerialCommunicationManager *manager)
|
||||
AccessCheck detectAMB8465(Detected *detected, shared_ptr<SerialCommunicationManager> manager)
|
||||
{
|
||||
// Talk to the device and expect a very specific answer.
|
||||
auto serial = manager->createSerialDeviceTTY(device.c_str(), 9600);
|
||||
auto serial = manager->createSerialDeviceTTY(detected->found_file.c_str(), 9600, "detect amb8465");
|
||||
serial->disableCallbacks();
|
||||
AccessCheck rc = serial->open(false);
|
||||
if (rc != AccessCheck::AccessOK) return AccessCheck::NotThere;
|
||||
|
||||
vector<uchar> data;
|
||||
vector<uchar> response;
|
||||
// First clear out any data in the queue.
|
||||
serial->receive(&data);
|
||||
data.clear();
|
||||
serial->receive(&response);
|
||||
response.clear();
|
||||
|
||||
vector<uchar> msg(4);
|
||||
msg[0] = AMBER_SERIAL_SOF;
|
||||
msg[1] = CMD_SERIALNO_REQ;
|
||||
msg[2] = 0; // No payload
|
||||
msg[3] = xorChecksum(msg, 3);
|
||||
vector<uchar> request;
|
||||
request.resize(6);
|
||||
request[0] = AMBER_SERIAL_SOF;
|
||||
request[1] = CMD_GET_REQ;
|
||||
request[2] = 0x02;
|
||||
request[3] = 0x00;
|
||||
request[4] = 0x80;
|
||||
request[5] = xorChecksum(request, 5);
|
||||
|
||||
assert(msg[3] == 0xf4);
|
||||
assert(request[5] == 0x77);
|
||||
|
||||
verbose("(amb8465) are you there?\n");
|
||||
serial->send(msg);
|
||||
bool sent = serial->send(request);
|
||||
if (!sent) return AccessCheck::NotThere;
|
||||
|
||||
serial->send(request);
|
||||
// Wait for 100ms so that the USB stick have time to prepare a response.
|
||||
usleep(1000*100);
|
||||
serial->receive(&data);
|
||||
int limit = 0;
|
||||
while (data.size() > 8 && data[0] != 0xff) {
|
||||
// Eat bytes until a 0xff appears to get in sync with the proper response.
|
||||
// Extraneous bytes might be due to a partially read telegram.
|
||||
data.erase(data.begin());
|
||||
vector<uchar> more;
|
||||
serial->receive(&more);
|
||||
if (more.size() > 0) {
|
||||
data.insert(data.end(), more.begin(), more.end());
|
||||
|
||||
vector<uchar> data;
|
||||
int count = 0;
|
||||
while (response.size() < 0x7E && count < 2)
|
||||
{
|
||||
size_t n = serial->receive(&data);
|
||||
if (n == 0)
|
||||
{
|
||||
usleep(1000*100);
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
if (limit++ > 100) break; // Do not wait too long.
|
||||
response.insert(response.end(), data.begin(), data.end());
|
||||
}
|
||||
|
||||
serial->close();
|
||||
|
||||
if (data.size() < 8 ||
|
||||
data[0] != 0xff ||
|
||||
data[1] != (0x80 | msg[1]) ||
|
||||
data[2] != 0x04 ||
|
||||
data[7] != xorChecksum(data, 7)) {
|
||||
// FF8A7A00780080710200000000FFFFFA00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF003200021400FFFFFFFFFF010004000000FFFFFF01440000000000000000FFFF0B040100FFFFFFFFFF00030000FFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF17
|
||||
|
||||
if (response.size() < 8 ||
|
||||
response[0] != 0xff ||
|
||||
response[1] != (0x80 | request[1]) ||
|
||||
response[2] != 0x7A)
|
||||
{
|
||||
verbose("(amb8465) are you there? no.\n");
|
||||
return AccessCheck::NotThere;
|
||||
}
|
||||
|
||||
ConfigAMB8465 config;
|
||||
config.decode(response);
|
||||
|
||||
detected->setAsFound(config.dongleId(), WMBusDeviceType::DEVICE_AMB8465, 9600, false, false);
|
||||
|
||||
verbose("(amb8465) detect %s\n", config.str().c_str());
|
||||
verbose("(amb8465) are you there? yes %s\n", config.dongleId().c_str());
|
||||
|
||||
return AccessCheck::AccessOK;
|
||||
}
|
||||
|
||||
static AccessCheck tryFactoryResetAMB8465(string device, SerialCommunicationManager *manager, int baud)
|
||||
static AccessCheck tryFactoryResetAMB8465(string device, shared_ptr<SerialCommunicationManager> manager, int baud)
|
||||
{
|
||||
// Talk to the device and expect a very specific answer.
|
||||
auto serial = manager->createSerialDeviceTTY(device.c_str(), baud);
|
||||
auto serial = manager->createSerialDeviceTTY(device.c_str(), baud, "reset amb8465");
|
||||
AccessCheck rc = serial->open(false);
|
||||
if (rc != AccessCheck::AccessOK) {
|
||||
verbose("(amb8465) could not open device %s using baud %d\n", device.c_str(), baud);
|
||||
|
@ -583,16 +621,17 @@ static AccessCheck tryFactoryResetAMB8465(string device, SerialCommunicationMana
|
|||
serial->receive(&data);
|
||||
data.clear();
|
||||
|
||||
vector<uchar> msg(4);
|
||||
msg[0] = AMBER_SERIAL_SOF;
|
||||
msg[1] = CMD_FACTORYRESET_REQ;
|
||||
msg[2] = 0; // No payload
|
||||
msg[3] = xorChecksum(msg, 3);
|
||||
vector<uchar> request_;
|
||||
request_.resize(4);
|
||||
request_[0] = AMBER_SERIAL_SOF;
|
||||
request_[1] = CMD_FACTORYRESET_REQ;
|
||||
request_[2] = 0; // No payload
|
||||
request_[3] = xorChecksum(request_, 3);
|
||||
|
||||
assert(msg[3] == 0xee);
|
||||
assert(request_[3] == 0xee);
|
||||
|
||||
verbose("(amb8465) try factory reset %s using baud %d\n", device.c_str(), baud);
|
||||
serial->send(msg);
|
||||
serial->send(request_);
|
||||
// Wait for 100ms so that the USB stick have time to prepare a response.
|
||||
usleep(1000*100);
|
||||
serial->receive(&data);
|
||||
|
@ -630,7 +669,7 @@ static AccessCheck tryFactoryResetAMB8465(string device, SerialCommunicationMana
|
|||
|
||||
int bauds[] = { 1200, 2400, 4800, 9600, 19200, 38400, 56000, 115200, 0 };
|
||||
|
||||
AccessCheck factoryResetAMB8465(string device, SerialCommunicationManager *manager, int *was_baud)
|
||||
AccessCheck factoryResetAMB8465(string device, shared_ptr<SerialCommunicationManager> manager, int *was_baud)
|
||||
{
|
||||
AccessCheck rc = AccessCheck::NotThere;
|
||||
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
Copyright (C) 2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef WMBUS_COMMON_H
|
||||
#define WMBUS_COMMON_H
|
||||
|
||||
#include "util.h"
|
||||
#include "threads.h"
|
||||
#include "wmbus.h"
|
||||
|
||||
struct WMBusCommonImplementation : public virtual WMBus
|
||||
{
|
||||
WMBusCommonImplementation(WMBusDeviceType t, shared_ptr<SerialCommunicationManager> manager, shared_ptr<SerialDevice> serial_override);
|
||||
~WMBusCommonImplementation();
|
||||
|
||||
string hr();
|
||||
WMBusDeviceType type();
|
||||
void onTelegram(function<bool(AboutTelegram&,vector<uchar>)> cb);
|
||||
bool handleTelegram(AboutTelegram &about, vector<uchar> frame);
|
||||
void checkStatus();
|
||||
bool isWorking();
|
||||
string dongleId();
|
||||
void setTimeout(int seconds, std::string expected_activity);
|
||||
void setResetInterval(int seconds);
|
||||
void setLinkModes(LinkModeSet lms);
|
||||
virtual void processSerialData() = 0;
|
||||
void disconnectedFromDevice();
|
||||
bool reset();
|
||||
SerialDevice *serial() { if (serial_) return serial_.get(); else return NULL; }
|
||||
bool serialOverride() { return serial_override_; }
|
||||
void markSerialAsOverriden() { serial_override_ = true; }
|
||||
|
||||
string device() { if (serial_) return serial_->device(); else return "?"; }
|
||||
// Wait for a response to arrive from the device.
|
||||
bool waitForResponse(int id);
|
||||
// Notify the waiter that the response has arrived.
|
||||
bool notifyResponseIsHere(int id);
|
||||
void close();
|
||||
void setDetected(Detected detected) { detected_ = detected; }
|
||||
Detected *getDetected() { return &detected_; }
|
||||
|
||||
protected:
|
||||
|
||||
shared_ptr<SerialCommunicationManager> manager_;
|
||||
void protocolErrorDetected();
|
||||
void resetProtocolErrorCount();
|
||||
bool areLinkModesConfigured();
|
||||
// Device specific set link modes implementation.
|
||||
virtual void deviceSetLinkModes(LinkModeSet lms) = 0;
|
||||
// Device specific reset code, apart from serial->open and setLinkModes.
|
||||
virtual void deviceReset() = 0;
|
||||
virtual void deviceClose();
|
||||
LinkModeSet protectedGetLinkModes(); // Used to read private link_modes_ in subclass.
|
||||
|
||||
private:
|
||||
|
||||
bool is_working_ {};
|
||||
vector<function<bool(AboutTelegram&,vector<uchar>)>> telegram_listeners_;
|
||||
WMBusDeviceType type_ {};
|
||||
int protocol_error_count_ {};
|
||||
time_t timeout_ {}; // If longer silence than timeout, then reset dongle! It might have hanged!
|
||||
string expected_activity_ {}; // During which times should we care about timeouts?
|
||||
time_t last_received_ {}; // When as the last telegram reception?
|
||||
time_t last_reset_ {}; // When did we last attempt a reset of the dongle?
|
||||
int reset_timeout_ {}; // When set to 23*3600 reset the device once every 23 hours.
|
||||
bool link_modes_configured_ {};
|
||||
LinkModeSet link_modes_ {};
|
||||
Detected detected_ {}; // Used to remember how this device was setup.
|
||||
|
||||
shared_ptr<SerialDevice> serial_;
|
||||
|
||||
protected:
|
||||
|
||||
// When a wmbus dongle transmits a telegram, then it will use this id.
|
||||
// It can often be changed by configuring the wmbud dongle.
|
||||
string cached_device_id_;
|
||||
// Generated human readable name: eg
|
||||
// * /dev/ttyUSB0:im871a[12345678]
|
||||
// * rtlmbus[longantenna]
|
||||
string cached_hr_;
|
||||
// Some dongles have a unique id (that cannot be changed) in addition to the transmit id.
|
||||
string cached_device_unique_id_;
|
||||
|
||||
// Lock this mutex when you sent a request to the wmbus device
|
||||
// Unlock when you received the response or it timedout.
|
||||
RecursiveMutex command_mutex_;
|
||||
#define LOCK_WMBUS_EXECUTING_COMMAND(where) WITH(command_mutex_, where)
|
||||
|
||||
// Use waitForRespones/notifyReponseIsHere to wait for a response
|
||||
// while the command_mutex_ is taken.
|
||||
int waiting_for_response_id_ {};
|
||||
Semaphore waiting_for_response_sem_;
|
||||
bool serial_override_ {};
|
||||
};
|
||||
|
||||
#endif
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
|
||||
#include"wmbus.h"
|
||||
#include"wmbus_common_implementation.h"
|
||||
#include"wmbus_utils.h"
|
||||
#include"wmbus_cul.h"
|
||||
#include"serial.h"
|
||||
|
@ -39,7 +40,8 @@ using namespace std;
|
|||
struct WMBusCUL : public virtual WMBusCommonImplementation
|
||||
{
|
||||
bool ping();
|
||||
uint32_t getDeviceId();
|
||||
string getDeviceId();
|
||||
string getDeviceUniqueId();
|
||||
LinkModeSet getLinkModes();
|
||||
void deviceReset();
|
||||
void deviceSetLinkModes(LinkModeSet lms);
|
||||
|
@ -61,7 +63,7 @@ struct WMBusCUL : public virtual WMBusCommonImplementation
|
|||
void processSerialData();
|
||||
void simulate();
|
||||
|
||||
WMBusCUL(unique_ptr<SerialDevice> serial, SerialCommunicationManager *manager);
|
||||
WMBusCUL(shared_ptr<SerialDevice> serial, shared_ptr<SerialCommunicationManager> manager);
|
||||
~WMBusCUL() { }
|
||||
|
||||
private:
|
||||
|
@ -69,12 +71,9 @@ private:
|
|||
LinkModeSet link_modes_ {};
|
||||
vector<uchar> read_buffer_;
|
||||
vector<uchar> received_payload_;
|
||||
sem_t command_wait_;
|
||||
string sent_command_;
|
||||
string received_response_;
|
||||
|
||||
void waitForResponse();
|
||||
|
||||
FrameStatus checkCULFrame(vector<uchar> &data,
|
||||
size_t *hex_frame_length,
|
||||
vector<uchar> &payload);
|
||||
|
@ -82,24 +81,22 @@ private:
|
|||
string setup_;
|
||||
};
|
||||
|
||||
unique_ptr<WMBus> openCUL(string device, SerialCommunicationManager *manager, unique_ptr<SerialDevice> serial_override)
|
||||
shared_ptr<WMBus> openCUL(string device, shared_ptr<SerialCommunicationManager> manager, shared_ptr<SerialDevice> serial_override)
|
||||
{
|
||||
if (serial_override)
|
||||
{
|
||||
WMBusCUL *imp = new WMBusCUL(std::move(serial_override), manager);
|
||||
return unique_ptr<WMBus>(imp);
|
||||
WMBusCUL *imp = new WMBusCUL(serial_override, manager);
|
||||
return shared_ptr<WMBus>(imp);
|
||||
}
|
||||
|
||||
auto serial = manager->createSerialDeviceTTY(device.c_str(), 38400);
|
||||
WMBusCUL *imp = new WMBusCUL(std::move(serial), manager);
|
||||
return unique_ptr<WMBus>(imp);
|
||||
auto serial = manager->createSerialDeviceTTY(device.c_str(), 38400, "cul");
|
||||
WMBusCUL *imp = new WMBusCUL(serial, manager);
|
||||
return shared_ptr<WMBus>(imp);
|
||||
}
|
||||
|
||||
WMBusCUL::WMBusCUL(unique_ptr<SerialDevice> serial, SerialCommunicationManager *manager) :
|
||||
WMBusCommonImplementation(DEVICE_CUL, manager, std::move(serial))
|
||||
WMBusCUL::WMBusCUL(shared_ptr<SerialDevice> serial, shared_ptr<SerialCommunicationManager> manager) :
|
||||
WMBusCommonImplementation(DEVICE_CUL, manager, serial)
|
||||
{
|
||||
sem_init(&command_wait_, 0, 0);
|
||||
manager_->listenTo(this->serial(),call(this,processSerialData));
|
||||
reset();
|
||||
}
|
||||
|
||||
|
@ -109,10 +106,16 @@ bool WMBusCUL::ping()
|
|||
return true;
|
||||
}
|
||||
|
||||
uint32_t WMBusCUL::getDeviceId()
|
||||
string WMBusCUL::getDeviceId()
|
||||
{
|
||||
verbose("(cul) getDeviceId\n");
|
||||
return 0x11111111;
|
||||
return "?";
|
||||
}
|
||||
|
||||
string WMBusCUL::getDeviceUniqueId()
|
||||
{
|
||||
verbose("(cul) getDeviceUniqueId\n");
|
||||
return "?";
|
||||
}
|
||||
|
||||
LinkModeSet WMBusCUL::getLinkModes()
|
||||
|
@ -156,7 +159,7 @@ void WMBusCUL::deviceSetLinkModes(LinkModeSet lms)
|
|||
received_response_ = "";
|
||||
bool sent = serial()->send(msg);
|
||||
|
||||
if (sent) waitForResponse();
|
||||
if (sent) waitForResponse(0);
|
||||
|
||||
sent_command_ = "";
|
||||
debug("(cul) received \"%s\"", received_response_.c_str());
|
||||
|
@ -188,19 +191,6 @@ void WMBusCUL::deviceSetLinkModes(LinkModeSet lms)
|
|||
// Any response here, or does it silently move into listening mode?
|
||||
}
|
||||
|
||||
void WMBusCUL::waitForResponse()
|
||||
{
|
||||
while (manager_->isRunning())
|
||||
{
|
||||
int rc = sem_wait(&command_wait_);
|
||||
if (rc==0) break;
|
||||
if (rc==-1) {
|
||||
if (errno==EINTR) continue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WMBusCUL::simulate()
|
||||
{
|
||||
}
|
||||
|
@ -242,7 +232,7 @@ void WMBusCUL::processSerialData()
|
|||
if (r != "")
|
||||
{
|
||||
received_response_ = r;
|
||||
sem_post(&command_wait_);
|
||||
waiting_for_response_sem_.notify();
|
||||
}
|
||||
}
|
||||
read_buffer_.clear();
|
||||
|
@ -259,7 +249,8 @@ void WMBusCUL::processSerialData()
|
|||
{
|
||||
read_buffer_.erase(read_buffer_.begin(), read_buffer_.begin()+frame_length);
|
||||
|
||||
handleTelegram(payload);
|
||||
AboutTelegram about("", 0);
|
||||
handleTelegram(about, payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -355,10 +346,11 @@ FrameStatus WMBusCUL::checkCULFrame(vector<uchar> &data,
|
|||
}
|
||||
}
|
||||
|
||||
AccessCheck detectCUL(string device, SerialCommunicationManager *manager)
|
||||
AccessCheck detectCUL(Detected *detected, shared_ptr<SerialCommunicationManager> manager)
|
||||
{
|
||||
// Talk to the device and expect a very specific answer.
|
||||
auto serial = manager->createSerialDeviceTTY(device.c_str(), 38400);
|
||||
auto serial = manager->createSerialDeviceTTY(detected->found_file.c_str(), 38400, "detect cul");
|
||||
serial->disableCallbacks();
|
||||
AccessCheck rc = serial->open(false);
|
||||
if (rc != AccessCheck::AccessOK) return AccessCheck::NotThere;
|
||||
|
||||
|
@ -399,5 +391,8 @@ AccessCheck detectCUL(string device, SerialCommunicationManager *manager)
|
|||
// TODO: check version string somehow
|
||||
|
||||
serial->close();
|
||||
|
||||
detected->setAsFound("12345678", WMBusDeviceType::DEVICE_CUL, 38400, false, false);
|
||||
|
||||
return AccessCheck::AccessOK;
|
||||
}
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
*/
|
||||
|
||||
#include"wmbus.h"
|
||||
#include"wmbus_common_implementation.h"
|
||||
#include"wmbus_utils.h"
|
||||
#include"serial.h"
|
||||
|
||||
#include<assert.h>
|
||||
#include<pthread.h>
|
||||
#include<semaphore.h>
|
||||
|
@ -30,7 +30,8 @@ using namespace std;
|
|||
struct WMBusD1TC : public virtual WMBusCommonImplementation
|
||||
{
|
||||
bool ping();
|
||||
uint32_t getDeviceId();
|
||||
string getDeviceId();
|
||||
string getDeviceUniqueId();
|
||||
LinkModeSet getLinkModes();
|
||||
void deviceReset();
|
||||
void deviceSetLinkModes(LinkModeSet lms);
|
||||
|
@ -41,40 +42,36 @@ struct WMBusD1TC : public virtual WMBusCommonImplementation
|
|||
void processSerialData();
|
||||
void simulate() { }
|
||||
|
||||
WMBusD1TC(unique_ptr<SerialDevice> serial, SerialCommunicationManager *manager);
|
||||
WMBusD1TC(shared_ptr<SerialDevice> serial, shared_ptr<SerialCommunicationManager> manager);
|
||||
~WMBusD1TC() { }
|
||||
|
||||
private:
|
||||
|
||||
vector<uchar> read_buffer_;
|
||||
sem_t command_wait_;
|
||||
LinkModeSet link_modes_;
|
||||
vector<uchar> received_payload_;
|
||||
|
||||
void waitForResponse();
|
||||
FrameStatus checkD1TCFrame(vector<uchar> &data,
|
||||
size_t *frame_length,
|
||||
int *payload_len_out,
|
||||
int *payload_offset);
|
||||
};
|
||||
|
||||
unique_ptr<WMBus> openD1TC(string device, SerialCommunicationManager *manager, unique_ptr<SerialDevice> serial_override)
|
||||
shared_ptr<WMBus> openD1TC(string device, shared_ptr<SerialCommunicationManager> manager, shared_ptr<SerialDevice> serial_override)
|
||||
{
|
||||
if (serial_override)
|
||||
{
|
||||
WMBusD1TC *imp = new WMBusD1TC(std::move(serial_override), manager);
|
||||
return unique_ptr<WMBus>(imp);
|
||||
WMBusD1TC *imp = new WMBusD1TC(serial_override, manager);
|
||||
return shared_ptr<WMBus>(imp);
|
||||
}
|
||||
auto serial = manager->createSerialDeviceTTY(device.c_str(), 115200);
|
||||
WMBusD1TC *imp = new WMBusD1TC(std::move(serial), manager);
|
||||
return unique_ptr<WMBus>(imp);
|
||||
auto serial = manager->createSerialDeviceTTY(device.c_str(), 115200, "d1tc");
|
||||
WMBusD1TC *imp = new WMBusD1TC(serial, manager);
|
||||
return shared_ptr<WMBus>(imp);
|
||||
}
|
||||
|
||||
WMBusD1TC::WMBusD1TC(unique_ptr<SerialDevice> serial, SerialCommunicationManager *manager) :
|
||||
WMBusCommonImplementation(DEVICE_D1TC, manager, std::move(serial))
|
||||
WMBusD1TC::WMBusD1TC(shared_ptr<SerialDevice> serial, shared_ptr<SerialCommunicationManager> manager) :
|
||||
WMBusCommonImplementation(DEVICE_D1TC, manager, serial)
|
||||
{
|
||||
sem_init(&command_wait_, 0, 0);
|
||||
manager_->listenTo(this->serial(),call(this,processSerialData));
|
||||
reset();
|
||||
}
|
||||
|
||||
|
@ -83,9 +80,14 @@ bool WMBusD1TC::ping()
|
|||
return true;
|
||||
}
|
||||
|
||||
uint32_t WMBusD1TC::getDeviceId()
|
||||
string WMBusD1TC::getDeviceId()
|
||||
{
|
||||
return 0;
|
||||
return "?";
|
||||
}
|
||||
|
||||
string WMBusD1TC::getDeviceUniqueId()
|
||||
{
|
||||
return "?";
|
||||
}
|
||||
|
||||
LinkModeSet WMBusD1TC::getLinkModes() {
|
||||
|
@ -105,17 +107,6 @@ void WMBusD1TC::deviceSetLinkModes(LinkModeSet lms)
|
|||
// Need manual for d1tc!!!
|
||||
}
|
||||
|
||||
void WMBusD1TC::waitForResponse() {
|
||||
while (manager_->isRunning()) {
|
||||
int rc = sem_wait(&command_wait_);
|
||||
if (rc==0) break;
|
||||
if (rc==-1) {
|
||||
if (errno==EINTR) continue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FrameStatus WMBusD1TC::checkD1TCFrame(vector<uchar> &data,
|
||||
size_t *frame_length,
|
||||
int *payload_len_out,
|
||||
|
@ -219,19 +210,26 @@ void WMBusD1TC::processSerialData()
|
|||
payload.insert(payload.end(), read_buffer_.begin()+payload_offset, read_buffer_.begin()+payload_offset+payload_len);
|
||||
}
|
||||
read_buffer_.erase(read_buffer_.begin(), read_buffer_.begin()+frame_length);
|
||||
handleTelegram(payload);
|
||||
AboutTelegram about("", 0);
|
||||
handleTelegram(about, payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AccessCheck detectD1TC(string device, int baud, SerialCommunicationManager *manager)
|
||||
AccessCheck detectD1TC(Detected *detected, shared_ptr<SerialCommunicationManager> manager)
|
||||
{
|
||||
string tty = detected->specified_device.file;
|
||||
int bps = atoi(detected->specified_device.bps.c_str());
|
||||
|
||||
// Since we do not know how to talk to the other end, it might not
|
||||
// even respond. The only thing we can do is to try to open the serial device.
|
||||
auto serial = manager->createSerialDeviceTTY(device.c_str(), baud);
|
||||
auto serial = manager->createSerialDeviceTTY(tty, bps, "detect d1tc");
|
||||
AccessCheck rc = serial->open(false);
|
||||
if (rc != AccessCheck::AccessOK) return AccessCheck::NotThere;
|
||||
|
||||
serial->close();
|
||||
|
||||
detected->setAsFound("", WMBusDeviceType::DEVICE_D1TC, bps, false, false);
|
||||
|
||||
return AccessCheck::AccessOK;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2017-2019 Fredrik Öhrström
|
||||
Copyright (C) 2017-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -16,22 +16,147 @@
|
|||
*/
|
||||
|
||||
#include"wmbus.h"
|
||||
#include"wmbus_common_implementation.h"
|
||||
#include"wmbus_utils.h"
|
||||
#include"wmbus_im871a.h"
|
||||
#include"serial.h"
|
||||
#include"threads.h"
|
||||
|
||||
#include<assert.h>
|
||||
#include<pthread.h>
|
||||
#include<errno.h>
|
||||
#include<memory.h>
|
||||
#include<semaphore.h>
|
||||
#include<unistd.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
struct DeviceInfo
|
||||
{
|
||||
uchar module_type; // 0x33 = im871a 0x36 = im170a
|
||||
uchar device_mode; // 0 = other 1 = meter
|
||||
uchar firmware_version;
|
||||
uchar hci_version; // serial protocol?
|
||||
uint32_t uid;
|
||||
|
||||
string str()
|
||||
{
|
||||
string s;
|
||||
if (module_type == 0x33) s+="im871a ";
|
||||
else if (module_type == 0x36) s+="im170a ";
|
||||
else s+="unknown_type("+to_string(module_type)+") ";
|
||||
|
||||
if (device_mode == 0) s+="other ";
|
||||
else if (device_mode == 1) s+="meter ";
|
||||
else s+="unknown_mode("+to_string(device_mode)+") ";
|
||||
|
||||
string ss;
|
||||
strprintf(ss, "firmware=%02x hci=%02x uid=%08x", firmware_version, hci_version, uid);
|
||||
return s+ss;
|
||||
}
|
||||
|
||||
bool decode(vector<uchar> &bytes)
|
||||
{
|
||||
if (bytes.size() < 8) return false;
|
||||
int i = 0;
|
||||
module_type = bytes[i++];
|
||||
device_mode = bytes[i++];
|
||||
firmware_version = bytes[i++];
|
||||
hci_version = bytes[i++];
|
||||
uid = bytes[i+3]<<24|bytes[i+2]<<16|bytes[i+1]<<8|bytes[i]; i+=4;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct Config
|
||||
{
|
||||
// first variable group
|
||||
uchar device_mode;
|
||||
uchar link_mode;
|
||||
uchar c_field;
|
||||
uint16_t mfct;
|
||||
uint32_t id;
|
||||
uchar version;
|
||||
uchar media;
|
||||
uchar radio_channel;
|
||||
|
||||
// second variable group
|
||||
uchar radio_power_level;
|
||||
uchar radio_data_rate;
|
||||
uchar radio_rx_window;
|
||||
uchar auto_power_saving;
|
||||
uchar auto_rssi;
|
||||
uchar auto_rx_timestamp;
|
||||
uchar led_control;
|
||||
uchar rtc_control;
|
||||
|
||||
string dongleId()
|
||||
{
|
||||
string s;
|
||||
strprintf(s, "%08x", id);
|
||||
return s;
|
||||
}
|
||||
|
||||
string str()
|
||||
{
|
||||
string s;
|
||||
|
||||
if (device_mode == 0) s+="other ";
|
||||
else if (device_mode == 1) s+="meter ";
|
||||
else s+="unknown_mode("+to_string(device_mode)+") ";
|
||||
|
||||
s += "link_mode="+toString(LinkModeIM871A(link_mode));
|
||||
|
||||
string ids;
|
||||
strprintf(ids, " id=%08x media=%02x version=%02x c_field=%02x auto_rssi=%02x", id, media, version, c_field, auto_rssi);
|
||||
|
||||
return s+ids;
|
||||
}
|
||||
|
||||
bool decode(vector<uchar> &bytes)
|
||||
{
|
||||
if (bytes.size() < 2) return false;
|
||||
size_t i = 0;
|
||||
uchar iiflag1 = bytes[i++]; if (i >= bytes.size()) return false;
|
||||
if (iiflag1 & 0x01) { device_mode = bytes[i++]; } if (i >= bytes.size()) return false;
|
||||
if (iiflag1 & 0x02) { link_mode = bytes[i++]; } if (i >= bytes.size()) return false;
|
||||
if (iiflag1 & 0x04) { c_field = bytes[i++]; } if (i+1 >= bytes.size()) return false;
|
||||
if (iiflag1 & 0x08) { mfct = bytes[i+1]<<8|bytes[i]; i+=2; } if (i+3 >= bytes.size()) return false;
|
||||
if (iiflag1 & 0x10) { id = bytes[i+3]<<24|bytes[i+2]<<16|bytes[i+1]<<8|bytes[i]; i+=4; } if (i >= bytes.size()) return false;
|
||||
if (iiflag1 & 0x20) { version = bytes[i++]; } if (i >= bytes.size()) return false;
|
||||
if (iiflag1 & 0x40) { media = bytes[i++]; } if (i >= bytes.size()) return false;
|
||||
if (iiflag1 & 0x80) { radio_channel = bytes[i++]; } if (i >= bytes.size()) return false;
|
||||
|
||||
uchar iiflag2 = bytes[i++]; if (i >= bytes.size()) return false;
|
||||
if (iiflag2 & 0x01) { radio_power_level = bytes[i++]; } if (i >= bytes.size()) return false;
|
||||
if (iiflag2 & 0x02) { radio_data_rate = bytes[i++]; } if (i >= bytes.size()) return false;
|
||||
if (iiflag2 & 0x04) { radio_rx_window = bytes[i++]; } if (i >= bytes.size()) return false;
|
||||
if (iiflag2 & 0x08) { auto_power_saving = bytes[i++]; } if (i >= bytes.size()) return false;
|
||||
if (iiflag2 & 0x10) { auto_rssi = bytes[i++]; } if (i >= bytes.size()) return false;
|
||||
if (iiflag2 & 0x20) { auto_rx_timestamp = bytes[i++]; } if (i >= bytes.size()) return false;
|
||||
if (iiflag2 & 0x40) { led_control = bytes[i++]; } if (i >= bytes.size()) return false;
|
||||
if (iiflag2 & 0x80) { rtc_control = bytes[i++]; }
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
string toString(LinkModeIM871A lm)
|
||||
{
|
||||
switch (lm)
|
||||
{
|
||||
#define X(name,text) case LinkModeIM871A::name: return #text;
|
||||
LIST_OF_IM871A_LINK_MODES
|
||||
#undef X
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
struct WMBusIM871A : public virtual WMBusCommonImplementation
|
||||
{
|
||||
bool ping();
|
||||
uint32_t getDeviceId();
|
||||
string getDeviceId();
|
||||
string getDeviceUniqueId();
|
||||
LinkModeSet getLinkModes();
|
||||
void deviceReset();
|
||||
void deviceSetLinkModes(LinkModeSet lms);
|
||||
|
@ -60,47 +185,66 @@ struct WMBusIM871A : public virtual WMBusCommonImplementation
|
|||
void processSerialData();
|
||||
void simulate() { }
|
||||
|
||||
WMBusIM871A(unique_ptr<SerialDevice> serial, SerialCommunicationManager *manager);
|
||||
~WMBusIM871A() { }
|
||||
WMBusIM871A(shared_ptr<SerialDevice> serial, shared_ptr<SerialCommunicationManager> manager);
|
||||
~WMBusIM871A() {
|
||||
}
|
||||
|
||||
static FrameStatus checkIM871AFrame(vector<uchar> &data,
|
||||
size_t *frame_length, int *endpoint_out, int *msgid_out,
|
||||
int *payload_len_out, int *payload_offset,
|
||||
int *rssi_dbm);
|
||||
|
||||
private:
|
||||
|
||||
vector<uchar> read_buffer_;
|
||||
pthread_mutex_t command_lock_ = PTHREAD_MUTEX_INITIALIZER;
|
||||
sem_t command_wait_;
|
||||
int sent_command_ {};
|
||||
int received_command_ {};
|
||||
vector<uchar> received_payload_;
|
||||
DeviceInfo device_info_ {};
|
||||
Config device_config_ {};
|
||||
|
||||
void waitForResponse();
|
||||
static FrameStatus checkIM871AFrame(vector<uchar> &data,
|
||||
size_t *frame_length, int *endpoint_out, int *msgid_out,
|
||||
int *payload_len_out, int *payload_offset);
|
||||
friend AccessCheck detectIM871A(string device, SerialCommunicationManager *manager);
|
||||
vector<uchar> read_buffer_;
|
||||
vector<uchar> request_;
|
||||
vector<uchar> response_;
|
||||
|
||||
bool getDeviceInfo();
|
||||
bool getConfig();
|
||||
|
||||
friend AccessCheck detectIM871A(Detected *detected, shared_ptr<SerialCommunicationManager> manager);
|
||||
void handleDevMgmt(int msgid, vector<uchar> &payload);
|
||||
void handleRadioLink(int msgid, vector<uchar> &payload);
|
||||
void handleRadioLink(int msgid, vector<uchar> &payload, int rssi_dbm);
|
||||
void handleRadioLinkTest(int msgid, vector<uchar> &payload);
|
||||
void handleHWTest(int msgid, vector<uchar> &payload);
|
||||
};
|
||||
|
||||
unique_ptr<WMBus> openIM871A(string device, SerialCommunicationManager *manager, unique_ptr<SerialDevice> serial_override)
|
||||
{
|
||||
if (serial_override)
|
||||
{
|
||||
WMBusIM871A *imp = new WMBusIM871A(std::move(serial_override), manager);
|
||||
return unique_ptr<WMBus>(imp);
|
||||
}
|
||||
|
||||
auto serial = manager->createSerialDeviceTTY(device.c_str(), 57600);
|
||||
WMBusIM871A *imp = new WMBusIM871A(std::move(serial), manager);
|
||||
return unique_ptr<WMBus>(imp);
|
||||
int toDBM(int rssi)
|
||||
{
|
||||
// Very course approximation of this graph:
|
||||
// Figure 7-3: RSSI vs. Input Power (Silicon Labs Si1002 datasheet [3])
|
||||
// Stronger rssi:s than 0 dbm will be reported as 0 dbm.
|
||||
// rssi = >230 -> 0 dbm
|
||||
// rssi = 205 -> -20 dbm
|
||||
// rssi = 45 -> -100 dbm
|
||||
#define SLOPE (80.0/(205.0-45.0))
|
||||
if (rssi >= 230) return 0;
|
||||
int dbm = -100+SLOPE*(rssi-45);
|
||||
return dbm;
|
||||
}
|
||||
|
||||
WMBusIM871A::WMBusIM871A(unique_ptr<SerialDevice> serial, SerialCommunicationManager *manager) :
|
||||
WMBusCommonImplementation(DEVICE_IM871A, manager,std::move(serial))
|
||||
shared_ptr<WMBus> openIM871A(string device, shared_ptr<SerialCommunicationManager> manager, shared_ptr<SerialDevice> serial_override)
|
||||
{
|
||||
assert(device != "");
|
||||
if (serial_override)
|
||||
{
|
||||
WMBusIM871A *imp = new WMBusIM871A(serial_override, manager);
|
||||
return shared_ptr<WMBus>(imp);
|
||||
}
|
||||
|
||||
auto serial = manager->createSerialDeviceTTY(device.c_str(), 57600, "im871a");
|
||||
WMBusIM871A *imp = new WMBusIM871A(serial, manager);
|
||||
return shared_ptr<WMBus>(imp);
|
||||
}
|
||||
|
||||
WMBusIM871A::WMBusIM871A(shared_ptr<SerialDevice> serial, shared_ptr<SerialCommunicationManager> manager) :
|
||||
WMBusCommonImplementation(DEVICE_IM871A, manager, serial)
|
||||
{
|
||||
sem_init(&command_wait_, 0, 0);
|
||||
manager_->listenTo(this->serial(),call(this,processSerialData));
|
||||
reset();
|
||||
}
|
||||
|
||||
|
@ -108,219 +252,209 @@ bool WMBusIM871A::ping()
|
|||
{
|
||||
if (serial()->readonly()) return true; // Feeding from stdin or file.
|
||||
|
||||
pthread_mutex_lock(&command_lock_);
|
||||
LOCK_WMBUS_EXECUTING_COMMAND(ping);
|
||||
|
||||
vector<uchar> msg(4);
|
||||
msg[0] = IM871A_SERIAL_SOF;
|
||||
msg[1] = DEVMGMT_ID;
|
||||
msg[2] = DEVMGMT_MSG_PING_REQ;
|
||||
msg[3] = 0;
|
||||
request_.resize(4);
|
||||
request_[0] = IM871A_SERIAL_SOF;
|
||||
request_[1] = DEVMGMT_ID;
|
||||
request_[2] = DEVMGMT_MSG_PING_REQ;
|
||||
request_[3] = 0;
|
||||
|
||||
sent_command_ = DEVMGMT_MSG_PING_REQ;
|
||||
verbose("(im871a) ping\n");
|
||||
bool sent = serial()->send(msg);
|
||||
bool sent = serial()->send(request_);
|
||||
|
||||
if (sent) waitForResponse();
|
||||
if (sent) return waitForResponse(DEVMGMT_MSG_PING_RSP);
|
||||
|
||||
pthread_mutex_unlock(&command_lock_);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t WMBusIM871A::getDeviceId()
|
||||
string WMBusIM871A::getDeviceId()
|
||||
{
|
||||
if (serial()->readonly()) return 0; // Feeding from stdin or file.
|
||||
if (serial()->readonly()) return "?"; // Feeding from stdin or file.
|
||||
if (cached_device_id_ != "") return cached_device_id_;
|
||||
|
||||
pthread_mutex_lock(&command_lock_);
|
||||
bool ok = getConfig();
|
||||
if (!ok) return "ERR";
|
||||
|
||||
vector<uchar> msg(4);
|
||||
msg[0] = IM871A_SERIAL_SOF;
|
||||
msg[1] = DEVMGMT_ID;
|
||||
msg[2] = DEVMGMT_MSG_GET_DEVICEINFO_REQ;
|
||||
msg[3] = 0;
|
||||
cached_device_id_ = tostrprintf("%08x", device_config_.id);
|
||||
|
||||
sent_command_ = DEVMGMT_MSG_GET_DEVICEINFO_REQ;
|
||||
verbose("(im871a) get device info\n");
|
||||
bool sent = serial()->send(msg);
|
||||
verbose("(im871a) got device id %s\n", cached_device_id_.c_str());
|
||||
|
||||
uint32_t id = 0;
|
||||
return cached_device_id_;
|
||||
}
|
||||
|
||||
if (sent)
|
||||
{
|
||||
waitForResponse();
|
||||
string WMBusIM871A::getDeviceUniqueId()
|
||||
{
|
||||
if (serial()->readonly()) return "?"; // Feeding from stdin or file.
|
||||
if (cached_device_unique_id_ != "") return cached_device_unique_id_;
|
||||
|
||||
if (received_command_ == DEVMGMT_MSG_GET_DEVICEINFO_RSP) {
|
||||
verbose("(im871a) device info: module Type %02x\n", received_payload_[0]);
|
||||
verbose("(im871a) device info: device Mode %02x\n", received_payload_[1]);
|
||||
verbose("(im871a) device info: firmware version %02x\n", received_payload_[2]);
|
||||
verbose("(im871a) device info: hci protocol version %02x\n", received_payload_[3]);
|
||||
id = received_payload_[4] << 24 |
|
||||
received_payload_[5] << 16 |
|
||||
received_payload_[6] << 8 |
|
||||
received_payload_[7];
|
||||
verbose("(im871a) devince info: id %08x\n", id);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
id = 0;
|
||||
}
|
||||
bool ok = getDeviceInfo();
|
||||
if (!ok) return "ERR";
|
||||
|
||||
pthread_mutex_unlock(&command_lock_);
|
||||
return id;
|
||||
cached_device_unique_id_ = tostrprintf("%08x", device_info_.uid);
|
||||
|
||||
verbose("(im871a) got device unique id %s\n", cached_device_unique_id_.c_str());
|
||||
|
||||
return cached_device_unique_id_;
|
||||
}
|
||||
|
||||
LinkModeSet WMBusIM871A::getLinkModes()
|
||||
{
|
||||
if (serial()->readonly()) { return Any_bit; } // Feeding from stdin or file.
|
||||
|
||||
pthread_mutex_lock(&command_lock_);
|
||||
LOCK_WMBUS_EXECUTING_COMMAND(get_link_modes);
|
||||
|
||||
vector<uchar> msg(4);
|
||||
msg[0] = IM871A_SERIAL_SOF;
|
||||
msg[1] = DEVMGMT_ID;
|
||||
sent_command_ = msg[2] = DEVMGMT_MSG_GET_CONFIG_REQ;
|
||||
msg[3] = 0;
|
||||
request_.resize(4);
|
||||
request_[0] = IM871A_SERIAL_SOF;
|
||||
request_[1] = DEVMGMT_ID;
|
||||
request_[2] = DEVMGMT_MSG_GET_CONFIG_REQ;
|
||||
request_[3] = 0;
|
||||
|
||||
verbose("(im871a) get config\n");
|
||||
bool sent = serial()->send(msg);
|
||||
bool sent = serial()->send(request_);
|
||||
|
||||
if (!sent)
|
||||
{
|
||||
// If we are using a serial override that will not respond,
|
||||
// then just return a value.
|
||||
pthread_mutex_unlock(&command_lock_);
|
||||
|
||||
// Use the remembered link modes set before.
|
||||
return protectedGetLinkModes();
|
||||
}
|
||||
|
||||
waitForResponse();
|
||||
LinkMode lm = LinkMode::UNKNOWN;
|
||||
if (received_command_ == DEVMGMT_MSG_GET_CONFIG_RSP)
|
||||
{
|
||||
int iff1 = received_payload_[0];
|
||||
bool has_device_mode = (iff1&1)==1;
|
||||
bool has_link_mode = (iff1&2)==2;
|
||||
bool has_wmbus_c_field = (iff1&4)==4;
|
||||
bool has_wmbus_man_id = (iff1&8)==8;
|
||||
bool has_wmbus_device_id = (iff1&16)==16;
|
||||
bool has_wmbus_version = (iff1&32)==32;
|
||||
bool has_wmbus_device_type = (iff1&64)==64;
|
||||
bool has_radio_channel = (iff1&128)==128;
|
||||
bool ok = waitForResponse(DEVMGMT_MSG_GET_CONFIG_RSP);
|
||||
|
||||
int offset = 1;
|
||||
if (has_device_mode)
|
||||
{
|
||||
verbose("(im871a) config: device mode %02x\n", received_payload_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_link_mode)
|
||||
{
|
||||
verbose("(im871a) config: link mode %02x\n", received_payload_[offset]);
|
||||
if (received_payload_[offset] == (int)LinkModeIM871A::C1a) {
|
||||
lm = LinkMode::C1;
|
||||
}
|
||||
if (received_payload_[offset] == (int)LinkModeIM871A::S1) {
|
||||
lm = LinkMode::S1;
|
||||
}
|
||||
if (received_payload_[offset] == (int)LinkModeIM871A::S1m) {
|
||||
lm = LinkMode::S1m;
|
||||
}
|
||||
if (received_payload_[offset] == (int)LinkModeIM871A::T1) {
|
||||
lm = LinkMode::T1;
|
||||
}
|
||||
if (received_payload_[offset] == (int)LinkModeIM871A::N1A) {
|
||||
lm = LinkMode::N1a;
|
||||
}
|
||||
if (received_payload_[offset] == (int)LinkModeIM871A::N1B) {
|
||||
lm = LinkMode::N1b;
|
||||
}
|
||||
if (received_payload_[offset] == (int)LinkModeIM871A::N1C) {
|
||||
lm = LinkMode::N1c;
|
||||
}
|
||||
if (received_payload_[offset] == (int)LinkModeIM871A::N1D) {
|
||||
lm = LinkMode::N1d;
|
||||
}
|
||||
if (received_payload_[offset] == (int)LinkModeIM871A::N1E) {
|
||||
lm = LinkMode::N1e;
|
||||
}
|
||||
if (received_payload_[offset] == (int)LinkModeIM871A::N1F) {
|
||||
lm = LinkMode::N1f;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
if (has_wmbus_c_field) {
|
||||
verbose("(im871a) config: wmbus c-field %02x\n", received_payload_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_wmbus_man_id) {
|
||||
int flagid = 256*received_payload_[offset+1] +received_payload_[offset+0];
|
||||
string flag = manufacturerFlag(flagid);
|
||||
verbose("(im871a) config: wmbus mfg id %02x%02x (%s)\n", received_payload_[offset+1], received_payload_[offset+0],
|
||||
flag.c_str());
|
||||
offset+=2;
|
||||
}
|
||||
if (has_wmbus_device_id) {
|
||||
verbose("(im871a) config: wmbus device id %02x%02x%02x%02x\n", received_payload_[offset+3], received_payload_[offset+2],
|
||||
received_payload_[offset+1], received_payload_[offset+0]);
|
||||
offset+=4;
|
||||
}
|
||||
if (has_wmbus_version) {
|
||||
verbose("(im871a) config: wmbus version %02x\n", received_payload_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_wmbus_device_type) {
|
||||
verbose("(im871a) config: wmbus device type %02x\n", received_payload_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_radio_channel) {
|
||||
verbose("(im871a) config: radio channel %02x\n", received_payload_[offset]);
|
||||
offset++;
|
||||
}
|
||||
int iff2 = received_payload_[offset];
|
||||
offset++;
|
||||
bool has_radio_power_level = (iff2&1)==1;
|
||||
bool has_radio_data_rate = (iff2&2)==2;
|
||||
bool has_radio_rx_window = (iff2&4)==4;
|
||||
bool has_auto_power_saving = (iff2&8)==8;
|
||||
bool has_auto_rssi_attachment = (iff2&16)==16;
|
||||
bool has_auto_rx_timestamp_attachment = (iff2&32)==32;
|
||||
bool has_led_control = (iff2&64)==64;
|
||||
bool has_rtc_control = (iff2&128)==128;
|
||||
if (has_radio_power_level) {
|
||||
verbose("(im871a) config: radio power level %02x\n", received_payload_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_radio_data_rate) {
|
||||
verbose("(im871a) config: radio data rate %02x\n", received_payload_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_radio_rx_window) {
|
||||
verbose("(im871a) config: radio rx window %02x\n", received_payload_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_auto_power_saving) {
|
||||
verbose("(im871a) config: auto power saving %02x\n", received_payload_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_auto_rssi_attachment) {
|
||||
verbose("(im871a) config: auto RSSI attachment %02x\n", received_payload_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_auto_rx_timestamp_attachment) {
|
||||
verbose("(im871a) config: auto rx timestamp attachment %02x\n", received_payload_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_led_control) {
|
||||
verbose("(im871a) config: led control %02x\n", received_payload_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_rtc_control) {
|
||||
verbose("(im871a) config: rtc control %02x\n", received_payload_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (!ok)
|
||||
{
|
||||
LinkModeSet lms;
|
||||
return lms;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&command_lock_);
|
||||
LinkMode lm = LinkMode::UNKNOWN;
|
||||
|
||||
int iff1 = response_[0];
|
||||
bool has_device_mode = (iff1&1)==1;
|
||||
bool has_link_mode = (iff1&2)==2;
|
||||
bool has_wmbus_c_field = (iff1&4)==4;
|
||||
bool has_wmbus_man_id = (iff1&8)==8;
|
||||
bool has_wmbus_device_id = (iff1&16)==16;
|
||||
bool has_wmbus_version = (iff1&32)==32;
|
||||
bool has_wmbus_device_type = (iff1&64)==64;
|
||||
bool has_radio_channel = (iff1&128)==128;
|
||||
|
||||
int offset = 1;
|
||||
if (has_device_mode)
|
||||
{
|
||||
verbose("(im871a) config: device mode %02x\n", response_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_link_mode)
|
||||
{
|
||||
verbose("(im871a) config: link mode %02x\n", response_[offset]);
|
||||
if (response_[offset] == (int)LinkModeIM871A::C1a) {
|
||||
lm = LinkMode::C1;
|
||||
}
|
||||
if (response_[offset] == (int)LinkModeIM871A::S1) {
|
||||
lm = LinkMode::S1;
|
||||
}
|
||||
if (response_[offset] == (int)LinkModeIM871A::S1m) {
|
||||
lm = LinkMode::S1m;
|
||||
}
|
||||
if (response_[offset] == (int)LinkModeIM871A::T1) {
|
||||
lm = LinkMode::T1;
|
||||
}
|
||||
if (response_[offset] == (int)LinkModeIM871A::N1A) {
|
||||
lm = LinkMode::N1a;
|
||||
}
|
||||
if (response_[offset] == (int)LinkModeIM871A::N1B) {
|
||||
lm = LinkMode::N1b;
|
||||
}
|
||||
if (response_[offset] == (int)LinkModeIM871A::N1C) {
|
||||
lm = LinkMode::N1c;
|
||||
}
|
||||
if (response_[offset] == (int)LinkModeIM871A::N1D) {
|
||||
lm = LinkMode::N1d;
|
||||
}
|
||||
if (response_[offset] == (int)LinkModeIM871A::N1E) {
|
||||
lm = LinkMode::N1e;
|
||||
}
|
||||
if (response_[offset] == (int)LinkModeIM871A::N1F) {
|
||||
lm = LinkMode::N1f;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
if (has_wmbus_c_field) {
|
||||
verbose("(im871a) config: wmbus c-field %02x\n", response_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_wmbus_man_id) {
|
||||
int flagid = 256*response_[offset+1] +response_[offset+0];
|
||||
string flag = manufacturerFlag(flagid);
|
||||
verbose("(im871a) config: wmbus mfg id %02x%02x (%s)\n", response_[offset+1], response_[offset+0],
|
||||
flag.c_str());
|
||||
offset+=2;
|
||||
}
|
||||
if (has_wmbus_device_id) {
|
||||
verbose("(im871a) config: wmbus device id %02x%02x%02x%02x\n", response_[offset+3], response_[offset+2],
|
||||
response_[offset+1], response_[offset+0]);
|
||||
offset+=4;
|
||||
}
|
||||
if (has_wmbus_version) {
|
||||
verbose("(im871a) config: wmbus version %02x\n", response_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_wmbus_device_type) {
|
||||
verbose("(im871a) config: wmbus device type %02x\n", response_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_radio_channel) {
|
||||
verbose("(im871a) config: radio channel %02x\n", response_[offset]);
|
||||
offset++;
|
||||
}
|
||||
int iff2 = response_[offset];
|
||||
offset++;
|
||||
bool has_radio_power_level = (iff2&1)==1;
|
||||
bool has_radio_data_rate = (iff2&2)==2;
|
||||
bool has_radio_rx_window = (iff2&4)==4;
|
||||
bool has_auto_power_saving = (iff2&8)==8;
|
||||
bool has_auto_rssi_attachment = (iff2&16)==16;
|
||||
bool has_auto_rx_timestamp_attachment = (iff2&32)==32;
|
||||
bool has_led_control = (iff2&64)==64;
|
||||
bool has_rtc_control = (iff2&128)==128;
|
||||
if (has_radio_power_level) {
|
||||
verbose("(im871a) config: radio power level %02x\n", response_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_radio_data_rate) {
|
||||
verbose("(im871a) config: radio data rate %02x\n", response_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_radio_rx_window) {
|
||||
verbose("(im871a) config: radio rx window %02x\n", response_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_auto_power_saving) {
|
||||
verbose("(im871a) config: auto power saving %02x\n", response_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_auto_rssi_attachment) {
|
||||
verbose("(im871a) config: auto RSSI attachment %02x\n", response_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_auto_rx_timestamp_attachment) {
|
||||
verbose("(im871a) config: auto rx timestamp attachment %02x\n", response_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_led_control) {
|
||||
verbose("(im871a) config: led control %02x\n", response_[offset]);
|
||||
offset++;
|
||||
}
|
||||
if (has_rtc_control) {
|
||||
verbose("(im871a) config: rtc control %02x\n", response_[offset]);
|
||||
offset++;
|
||||
}
|
||||
|
||||
|
||||
LinkModeSet lms;
|
||||
lms.addLinkMode(lm);
|
||||
return lms;
|
||||
|
@ -344,67 +478,60 @@ void WMBusIM871A::deviceSetLinkModes(LinkModeSet lms)
|
|||
error("(im871a) setting link mode(s) %s is not supported for im871a\n", modes.c_str());
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&command_lock_);
|
||||
LOCK_WMBUS_EXECUTING_COMMAND(set_link_modes);
|
||||
|
||||
vector<uchar> msg(10);
|
||||
msg[0] = IM871A_SERIAL_SOF;
|
||||
msg[1] = DEVMGMT_ID;
|
||||
sent_command_ = msg[2] = DEVMGMT_MSG_SET_CONFIG_REQ;
|
||||
msg[3] = 6; // Len
|
||||
msg[4] = 0; // Temporary
|
||||
msg[5] = 2; // iff1 bits: Set Radio Mode
|
||||
request_.resize(10);
|
||||
request_[0] = IM871A_SERIAL_SOF;
|
||||
request_[1] = DEVMGMT_ID;
|
||||
request_[2] = DEVMGMT_MSG_SET_CONFIG_REQ;
|
||||
request_[3] = 6; // Len
|
||||
request_[4] = 0; // Temporary
|
||||
request_[5] = 2; // iff1 bits: Set Radio Mode
|
||||
if (lms.has(LinkMode::C1)) {
|
||||
msg[6] = (int)LinkModeIM871A::C1a;
|
||||
request_[6] = (int)LinkModeIM871A::C1a;
|
||||
} else if (lms.has(LinkMode::S1)) {
|
||||
msg[6] = (int)LinkModeIM871A::S1;
|
||||
request_[6] = (int)LinkModeIM871A::S1;
|
||||
} else if (lms.has(LinkMode::S1m)) {
|
||||
msg[6] = (int)LinkModeIM871A::S1m;
|
||||
request_[6] = (int)LinkModeIM871A::S1m;
|
||||
} else if (lms.has(LinkMode::T1)) {
|
||||
msg[6] = (int)LinkModeIM871A::T1;
|
||||
request_[6] = (int)LinkModeIM871A::T1;
|
||||
} else if (lms.has(LinkMode::N1a)) {
|
||||
msg[6] = (int)LinkModeIM871A::N1A;
|
||||
request_[6] = (int)LinkModeIM871A::N1A;
|
||||
} else if (lms.has(LinkMode::N1b)) {
|
||||
msg[6] = (int)LinkModeIM871A::N1B;
|
||||
request_[6] = (int)LinkModeIM871A::N1B;
|
||||
} else if (lms.has(LinkMode::N1c)) {
|
||||
msg[6] = (int)LinkModeIM871A::N1C;
|
||||
request_[6] = (int)LinkModeIM871A::N1C;
|
||||
} else if (lms.has(LinkMode::N1d)) {
|
||||
msg[6] = (int)LinkModeIM871A::N1D;
|
||||
request_[6] = (int)LinkModeIM871A::N1D;
|
||||
} else if (lms.has(LinkMode::N1e)) {
|
||||
msg[6] = (int)LinkModeIM871A::N1E;
|
||||
request_[6] = (int)LinkModeIM871A::N1E;
|
||||
} else if (lms.has(LinkMode::N1f)) {
|
||||
msg[6] = (int)LinkModeIM871A::N1F;
|
||||
request_[6] = (int)LinkModeIM871A::N1F;
|
||||
} else {
|
||||
msg[6] = (int)LinkModeIM871A::C1a; // Defaults to C1a
|
||||
request_[6] = (int)LinkModeIM871A::C1a; // Defaults to C1a
|
||||
}
|
||||
|
||||
msg[7] = 16+32; // iff2 bits: Set rssi+timestamp
|
||||
msg[8] = 1; // Enable rssi
|
||||
msg[9] = 1; // Enable timestamp
|
||||
request_[7] = 0x10 | 0x20; // iff2 bits: Set rssi 0x10, timestamp 0x20
|
||||
request_[8] = 1; // Enable rssi
|
||||
request_[9] = 0; // Disable timestamp
|
||||
|
||||
verbose("(im871a) set link mode %02x\n", msg[6]);
|
||||
bool sent = serial()->send(msg);
|
||||
verbose("(im871a) set config to set link mode %02x\n", request_[6]);
|
||||
bool sent = serial()->send(request_);
|
||||
|
||||
if (sent) waitForResponse();
|
||||
|
||||
pthread_mutex_unlock(&command_lock_);
|
||||
}
|
||||
|
||||
void WMBusIM871A::waitForResponse()
|
||||
{
|
||||
while (manager_->isRunning())
|
||||
if (sent)
|
||||
{
|
||||
int rc = sem_wait(&command_wait_);
|
||||
if (rc==0) break;
|
||||
if (rc==-1) {
|
||||
if (errno==EINTR) continue;
|
||||
break;
|
||||
bool ok = waitForResponse(DEVMGMT_MSG_SET_CONFIG_RSP);
|
||||
if (!ok)
|
||||
{
|
||||
warning("Warning! Did not get confirmation on set link mode for im871a\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FrameStatus WMBusIM871A::checkIM871AFrame(vector<uchar> &data,
|
||||
size_t *frame_length, int *endpoint_out, int *msgid_out,
|
||||
int *payload_len_out, int *payload_offset)
|
||||
int *payload_len_out, int *payload_offset,
|
||||
int *rssi_dbm)
|
||||
{
|
||||
if (data.size() == 0) return PartialFrame;
|
||||
|
||||
|
@ -505,8 +632,9 @@ FrameStatus WMBusIM871A::checkIM871AFrame(vector<uchar> &data,
|
|||
i += 4;
|
||||
}
|
||||
if (has_rssi) {
|
||||
uint32_t rssi = data[i];
|
||||
verbose("(im871a) rssi %02x\n", rssi);
|
||||
int rssi = data[i];
|
||||
*rssi_dbm = toDBM(rssi);
|
||||
debug("(im871a) rssi %d (%d dBm)\n", rssi, *rssi_dbm);
|
||||
i++;
|
||||
}
|
||||
if (has_crc16) {
|
||||
|
@ -539,10 +667,11 @@ void WMBusIM871A::processSerialData()
|
|||
int endpoint;
|
||||
int msgid;
|
||||
int payload_len, payload_offset;
|
||||
int rssi_dbm = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
FrameStatus status = checkIM871AFrame(read_buffer_, &frame_length, &endpoint, &msgid, &payload_len, &payload_offset);
|
||||
FrameStatus status = checkIM871AFrame(read_buffer_, &frame_length, &endpoint, &msgid, &payload_len, &payload_offset, &rssi_dbm);
|
||||
|
||||
if (status == PartialFrame)
|
||||
{
|
||||
|
@ -580,7 +709,7 @@ void WMBusIM871A::processSerialData()
|
|||
// It can be wmbus receiver-dongle messages or wmbus remote meter messages received over the radio.
|
||||
switch (endpoint) {
|
||||
case DEVMGMT_ID: handleDevMgmt(msgid, payload); break;
|
||||
case RADIOLINK_ID: handleRadioLink(msgid, payload); break;
|
||||
case RADIOLINK_ID: handleRadioLink(msgid, payload, rssi_dbm); break;
|
||||
case RADIOLINKTEST_ID: handleRadioLinkTest(msgid, payload); break;
|
||||
case HWTEST_ID: handleHWTest(msgid, payload); break;
|
||||
}
|
||||
|
@ -593,41 +722,41 @@ void WMBusIM871A::handleDevMgmt(int msgid, vector<uchar> &payload)
|
|||
switch (msgid) {
|
||||
case DEVMGMT_MSG_PING_RSP: // 0x02
|
||||
verbose("(im871a) pong\n");
|
||||
received_command_ = msgid;
|
||||
sem_post(&command_wait_);
|
||||
notifyResponseIsHere(DEVMGMT_MSG_PING_RSP);
|
||||
break;
|
||||
case DEVMGMT_MSG_SET_CONFIG_RSP: // 0x04
|
||||
verbose("(im871a) set config completed\n");
|
||||
received_command_ = msgid;
|
||||
received_payload_.clear();
|
||||
received_payload_.insert(received_payload_.end(), payload.begin(), payload.end());
|
||||
sem_post(&command_wait_);
|
||||
response_.clear();
|
||||
response_.insert(response_.end(), payload.begin(), payload.end());
|
||||
notifyResponseIsHere(DEVMGMT_MSG_SET_CONFIG_RSP);
|
||||
break;
|
||||
case DEVMGMT_MSG_GET_CONFIG_RSP: // 0x06
|
||||
verbose("(im871a) get config completed\n");
|
||||
received_command_ = msgid;
|
||||
received_payload_.clear();
|
||||
received_payload_.insert(received_payload_.end(), payload.begin(), payload.end());
|
||||
sem_post(&command_wait_);
|
||||
response_.clear();
|
||||
response_.insert(response_.end(), payload.begin(), payload.end());
|
||||
notifyResponseIsHere(DEVMGMT_MSG_GET_CONFIG_RSP);
|
||||
break;
|
||||
case DEVMGMT_MSG_GET_DEVICEINFO_RSP: // 0x10
|
||||
verbose("(im871a) device info completed\n");
|
||||
received_command_ = msgid;
|
||||
received_payload_.clear();
|
||||
received_payload_.insert(received_payload_.end(), payload.begin(), payload.end());
|
||||
sem_post(&command_wait_);
|
||||
response_.clear();
|
||||
response_.insert(response_.end(), payload.begin(), payload.end());
|
||||
notifyResponseIsHere(DEVMGMT_MSG_GET_DEVICEINFO_RSP);
|
||||
break;
|
||||
default:
|
||||
verbose("(im871a) Unhandled device management message %d\n", msgid);
|
||||
}
|
||||
}
|
||||
|
||||
void WMBusIM871A::handleRadioLink(int msgid, vector<uchar> &frame)
|
||||
void WMBusIM871A::handleRadioLink(int msgid, vector<uchar> &frame, int rssi_dbm)
|
||||
{
|
||||
switch (msgid) {
|
||||
case RADIOLINK_MSG_WMBUSMSG_IND: // 0x03
|
||||
handleTelegram(frame);
|
||||
break;
|
||||
{
|
||||
// Invoke common telegram reception code in WMBusCommonImplementation.
|
||||
AboutTelegram about("im871a["+cached_device_id_+"]", rssi_dbm);
|
||||
handleTelegram(about, frame);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
verbose("(im871a) Unhandled radio link message %d\n", msgid);
|
||||
}
|
||||
|
@ -649,44 +778,140 @@ void WMBusIM871A::handleHWTest(int msgid, vector<uchar> &payload)
|
|||
}
|
||||
}
|
||||
|
||||
AccessCheck detectIM871A(string device, SerialCommunicationManager *manager)
|
||||
bool extract_response(vector<uchar> &data, vector<uchar> &response, int expected_endpoint, int expected_msgid)
|
||||
{
|
||||
size_t frame_length;
|
||||
int endpoint, msgid, payload_len, payload_offset, rssi_dbm;
|
||||
FrameStatus status = WMBusIM871A::checkIM871AFrame(data,
|
||||
&frame_length, &endpoint, &msgid,
|
||||
&payload_len, &payload_offset, &rssi_dbm);
|
||||
if (status != FullFrame ||
|
||||
endpoint != expected_endpoint ||
|
||||
msgid != expected_msgid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
response.clear();
|
||||
response.insert(data.end(), data.begin()+payload_offset, data.begin()+payload_offset+payload_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WMBusIM871A::getDeviceInfo()
|
||||
{
|
||||
LOCK_WMBUS_EXECUTING_COMMAND(get_device_info);
|
||||
|
||||
request_.resize(4);
|
||||
request_[0] = IM871A_SERIAL_SOF;
|
||||
request_[1] = DEVMGMT_ID;
|
||||
request_[2] = DEVMGMT_MSG_GET_DEVICEINFO_REQ;
|
||||
request_[3] = 0;
|
||||
|
||||
verbose("(im871a) get device info\n");
|
||||
|
||||
bool sent = serial()->send(request_);
|
||||
|
||||
if (!sent) return false; // tty overridden with stdin/file
|
||||
|
||||
/*
|
||||
if (!use_manager)
|
||||
{
|
||||
// Wait for 100ms so that the USB stick have time to prepare a response.
|
||||
usleep(1000*100);
|
||||
vector<uchar> data;
|
||||
serial->receive(&data);
|
||||
|
||||
bool ok = extract_response(data, response_, 1, 16);
|
||||
if (!ok) return false;
|
||||
}*/
|
||||
bool ok = waitForResponse(DEVMGMT_MSG_GET_DEVICEINFO_RSP);
|
||||
if (!ok) return false; // timeout
|
||||
|
||||
// Now device info response is in response_ vector.
|
||||
|
||||
device_info_.decode(response_);
|
||||
|
||||
verbose("(im871a) device info: %s\n", device_info_.str().c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WMBusIM871A::getConfig()
|
||||
{
|
||||
if (serial()->readonly()) return true;
|
||||
|
||||
LOCK_WMBUS_EXECUTING_COMMAND(getConfig);
|
||||
|
||||
request_.resize(4);
|
||||
request_[0] = IM871A_SERIAL_SOF;
|
||||
request_[1] = DEVMGMT_ID;
|
||||
request_[2] = DEVMGMT_MSG_GET_CONFIG_REQ;
|
||||
request_[3] = 0;
|
||||
|
||||
verbose("(im871a) get config\n");
|
||||
|
||||
bool sent = serial()->send(request_);
|
||||
if (!sent) return "ERR";
|
||||
|
||||
bool ok = waitForResponse(DEVMGMT_MSG_GET_CONFIG_RSP);
|
||||
if (!ok) return "ERR";
|
||||
|
||||
return device_config_.decode(response_);
|
||||
}
|
||||
|
||||
AccessCheck detectIM871A(Detected *detected, shared_ptr<SerialCommunicationManager> manager)
|
||||
{
|
||||
// Talk to the device and expect a very specific answer.
|
||||
auto serial = manager->createSerialDeviceTTY(device.c_str(), 57600);
|
||||
auto serial = manager->createSerialDeviceTTY(detected->found_file.c_str(), 57600, "detect im871a");
|
||||
serial->disableCallbacks();
|
||||
AccessCheck rc = serial->open(false);
|
||||
if (rc != AccessCheck::AccessOK) return AccessCheck::NotThere;
|
||||
|
||||
vector<uchar> data;
|
||||
vector<uchar> response;
|
||||
// First clear out any data in the queue.
|
||||
serial->receive(&data);
|
||||
data.clear();
|
||||
serial->receive(&response);
|
||||
response.clear();
|
||||
|
||||
vector<uchar> msg(4);
|
||||
msg[0] = IM871A_SERIAL_SOF;
|
||||
msg[1] = DEVMGMT_ID;
|
||||
msg[2] = DEVMGMT_MSG_PING_REQ;
|
||||
msg[3] = 0;
|
||||
vector<uchar> request;
|
||||
request.resize(4);
|
||||
request[0] = IM871A_SERIAL_SOF;
|
||||
request[1] = DEVMGMT_ID;
|
||||
request[2] = DEVMGMT_MSG_GET_CONFIG_REQ;
|
||||
request[3] = 0;
|
||||
|
||||
verbose("(im871a) are you there?\n");
|
||||
serial->send(msg);
|
||||
serial->send(request);
|
||||
// Wait for 100ms so that the USB stick have time to prepare a response.
|
||||
usleep(1000*100);
|
||||
serial->receive(&data);
|
||||
serial->close();
|
||||
|
||||
string sent = bin2hex(msg);
|
||||
string recv = bin2hex(data);
|
||||
serial->receive(&response);
|
||||
|
||||
size_t frame_length;
|
||||
int endpoint, msgid, payload_len, payload_offset;
|
||||
FrameStatus status = WMBusIM871A::checkIM871AFrame(data,
|
||||
int endpoint, msgid, payload_len, payload_offset, rssi_dbm;
|
||||
FrameStatus status = WMBusIM871A::checkIM871AFrame(response,
|
||||
&frame_length, &endpoint, &msgid,
|
||||
&payload_len, &payload_offset);
|
||||
&payload_len, &payload_offset, &rssi_dbm);
|
||||
if (status != FullFrame ||
|
||||
endpoint != 1 ||
|
||||
msgid != 2)
|
||||
msgid != DEVMGMT_MSG_GET_CONFIG_RSP)
|
||||
{
|
||||
verbose("(im871a) are you there? no.\n");
|
||||
return AccessCheck::NotThere;
|
||||
}
|
||||
|
||||
serial->close();
|
||||
|
||||
vector<uchar> payload;
|
||||
payload.insert(payload.end(), response.begin()+payload_offset, response.begin()+payload_offset+payload_len);
|
||||
|
||||
debugPayload("(device config bytes)", payload);
|
||||
|
||||
Config co;
|
||||
co.decode(payload);
|
||||
|
||||
debug("(im871a) config: %s\n", co.str().c_str());
|
||||
|
||||
detected->setAsFound(co.dongleId(), WMBusDeviceType::DEVICE_IM871A, 57600, false, false);
|
||||
|
||||
verbose("(im871a) are you there? yes %s\n", co.dongleId().c_str());
|
||||
|
||||
return AccessCheck::AccessOK;
|
||||
}
|
||||
|
|
|
@ -58,11 +58,35 @@
|
|||
#define HWTEST_MSG_RADIOTEST_RSP 0x02
|
||||
|
||||
// LinkModeIM871A::S1 is 0, S1m is 1 etc. These numbers are what the dongle requires.
|
||||
#define LIST_OF_IM871A_LINK_MODES X(S1)X(S1m)X(S2)X(T1)X(T2)X(R2)X(C1a)X(C1b)X(C2a)X(C2b) \
|
||||
X(N1A)X(N2A)X(N1B)X(N2B)X(N1C)X(N2C)X(N1D)X(N2D)X(N1E)X(N2E)X(N1F)X(N2F)X(UNKNOWN)
|
||||
#define LIST_OF_IM871A_LINK_MODES \
|
||||
X(S1,s1)\
|
||||
X(S1m,s1m)\
|
||||
X(S2,s2)\
|
||||
X(T1,t1)\
|
||||
X(T2,t2)\
|
||||
X(R2,r2)\
|
||||
X(C1a,c1a)\
|
||||
X(C1b,cab)\
|
||||
X(C2a,c2a)\
|
||||
X(C2b,c2b)\
|
||||
X(N1A,n1a)\
|
||||
X(N2A,n2a)\
|
||||
X(N1B,n1b)\
|
||||
X(N2B,n2b)\
|
||||
X(N1C,n1c)\
|
||||
X(N2C,n2c)\
|
||||
X(N1D,n1d)\
|
||||
X(N2D,n2d)\
|
||||
X(N1E,n1e)\
|
||||
X(N2E,n2e)\
|
||||
X(N1F,n1f)\
|
||||
X(N2F,n2f)\
|
||||
X(UNKNOWN,unknown)
|
||||
|
||||
enum class LinkModeIM871A {
|
||||
#define X(name) name,
|
||||
#define X(name,text) name,
|
||||
LIST_OF_IM871A_LINK_MODES
|
||||
#undef X
|
||||
};
|
||||
|
||||
string toString(LinkModeIM871A lm);
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
|
||||
#include"wmbus.h"
|
||||
#include"wmbus_common_implementation.h"
|
||||
#include"wmbus_utils.h"
|
||||
#include"serial.h"
|
||||
|
||||
|
@ -30,7 +31,8 @@ using namespace std;
|
|||
struct WMBusRawTTY : public virtual WMBusCommonImplementation
|
||||
{
|
||||
bool ping();
|
||||
uint32_t getDeviceId();
|
||||
string getDeviceId();
|
||||
string getDeviceUniqueId();
|
||||
LinkModeSet getLinkModes();
|
||||
void deviceReset();
|
||||
void deviceSetLinkModes(LinkModeSet lms);
|
||||
|
@ -41,36 +43,33 @@ struct WMBusRawTTY : public virtual WMBusCommonImplementation
|
|||
void processSerialData();
|
||||
void simulate() { }
|
||||
|
||||
WMBusRawTTY(unique_ptr<SerialDevice> serial, SerialCommunicationManager *manager);
|
||||
WMBusRawTTY(shared_ptr<SerialDevice> serial, shared_ptr<SerialCommunicationManager> manager);
|
||||
~WMBusRawTTY() { }
|
||||
|
||||
private:
|
||||
|
||||
vector<uchar> read_buffer_;
|
||||
sem_t command_wait_;
|
||||
LinkModeSet link_modes_;
|
||||
vector<uchar> received_payload_;
|
||||
|
||||
void waitForResponse();
|
||||
};
|
||||
|
||||
unique_ptr<WMBus> openRawTTY(string device, int baudrate, SerialCommunicationManager *manager, unique_ptr<SerialDevice> serial_override)
|
||||
shared_ptr<WMBus> openRawTTY(string device, int baudrate, shared_ptr<SerialCommunicationManager> manager, shared_ptr<SerialDevice> serial_override)
|
||||
{
|
||||
assert(device != "");
|
||||
|
||||
if (serial_override)
|
||||
{
|
||||
WMBusRawTTY *imp = new WMBusRawTTY(std::move(serial_override), manager);
|
||||
return unique_ptr<WMBus>(imp);
|
||||
WMBusRawTTY *imp = new WMBusRawTTY(serial_override, manager);
|
||||
return shared_ptr<WMBus>(imp);
|
||||
}
|
||||
auto serial = manager->createSerialDeviceTTY(device.c_str(), baudrate);
|
||||
WMBusRawTTY *imp = new WMBusRawTTY(std::move(serial), manager);
|
||||
return unique_ptr<WMBus>(imp);
|
||||
auto serial = manager->createSerialDeviceTTY(device.c_str(), baudrate, "rawtty");
|
||||
WMBusRawTTY *imp = new WMBusRawTTY(serial, manager);
|
||||
return shared_ptr<WMBus>(imp);
|
||||
}
|
||||
|
||||
WMBusRawTTY::WMBusRawTTY(unique_ptr<SerialDevice> serial, SerialCommunicationManager *manager) :
|
||||
WMBusCommonImplementation(DEVICE_RAWTTY, manager, std::move(serial))
|
||||
WMBusRawTTY::WMBusRawTTY(shared_ptr<SerialDevice> serial, shared_ptr<SerialCommunicationManager> manager) :
|
||||
WMBusCommonImplementation(DEVICE_RAWTTY, manager, serial)
|
||||
{
|
||||
sem_init(&command_wait_, 0, 0);
|
||||
manager_->listenTo(this->serial(),call(this,processSerialData));
|
||||
reset();
|
||||
}
|
||||
|
||||
|
@ -79,9 +78,14 @@ bool WMBusRawTTY::ping()
|
|||
return true;
|
||||
}
|
||||
|
||||
uint32_t WMBusRawTTY::getDeviceId()
|
||||
string WMBusRawTTY::getDeviceId()
|
||||
{
|
||||
return 0;
|
||||
return "?";
|
||||
}
|
||||
|
||||
string WMBusRawTTY::getDeviceUniqueId()
|
||||
{
|
||||
return "?";
|
||||
}
|
||||
|
||||
LinkModeSet WMBusRawTTY::getLinkModes() {
|
||||
|
@ -96,20 +100,8 @@ void WMBusRawTTY::deviceSetLinkModes(LinkModeSet lms)
|
|||
{
|
||||
}
|
||||
|
||||
void WMBusRawTTY::waitForResponse() {
|
||||
while (manager_->isRunning()) {
|
||||
int rc = sem_wait(&command_wait_);
|
||||
if (rc==0) break;
|
||||
if (rc==-1) {
|
||||
if (errno==EINTR) continue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WMBusRawTTY::processSerialData()
|
||||
{
|
||||
|
||||
vector<uchar> data;
|
||||
|
||||
// Receive and accumulated serial data until a full frame has been received.
|
||||
|
@ -147,19 +139,26 @@ void WMBusRawTTY::processSerialData()
|
|||
payload.insert(payload.end(), read_buffer_.begin()+payload_offset, read_buffer_.begin()+payload_offset+payload_len);
|
||||
}
|
||||
read_buffer_.erase(read_buffer_.begin(), read_buffer_.begin()+frame_length);
|
||||
handleTelegram(payload);
|
||||
AboutTelegram about("", 0);
|
||||
handleTelegram(about, payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AccessCheck detectRawTTY(string device, int baud, SerialCommunicationManager *manager)
|
||||
AccessCheck detectRAWTTY(Detected *detected, shared_ptr<SerialCommunicationManager> manager)
|
||||
{
|
||||
string tty = detected->specified_device.file;
|
||||
int bps = atoi(detected->specified_device.bps.c_str());
|
||||
|
||||
// Since we do not know how to talk to the other end, it might not
|
||||
// even respond. The only thing we can do is to try to open the serial device.
|
||||
auto serial = manager->createSerialDeviceTTY(device.c_str(), baud);
|
||||
auto serial = manager->createSerialDeviceTTY(tty.c_str(), bps, "detect rawtty");
|
||||
AccessCheck rc = serial->open(false);
|
||||
if (rc != AccessCheck::AccessOK) return AccessCheck::NotThere;
|
||||
|
||||
serial->close();
|
||||
|
||||
detected->setAsFound("", WMBusDeviceType::DEVICE_RAWTTY, bps, false, false);
|
||||
|
||||
return AccessCheck::AccessOK;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,381 @@
|
|||
/*
|
||||
Copyright (C) 2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include"wmbus.h"
|
||||
#include"wmbus_common_implementation.h"
|
||||
#include"wmbus_utils.h"
|
||||
#include"serial.h"
|
||||
|
||||
#include<assert.h>
|
||||
#include<fcntl.h>
|
||||
#include<grp.h>
|
||||
#include<pthread.h>
|
||||
#include<semaphore.h>
|
||||
#include<string.h>
|
||||
#include<errno.h>
|
||||
#include<sys/stat.h>
|
||||
#include<sys/types.h>
|
||||
#include<unistd.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
struct ConfigRC1180
|
||||
{
|
||||
// first variable group
|
||||
uchar radio_channel {}; // S=11 T1=12 R2=1-10
|
||||
uchar radio_power {};
|
||||
uchar radio_data_rate {}; // S=2 T1=3 R2=1
|
||||
uchar mbus_mode {}; // S1=0 T1=1
|
||||
uchar sleep_mode {}; // 0=disable sleep 1=enable sleep
|
||||
uchar rssi_mode {}; // 0=disabled 1=enabled (append rssi to telegram)
|
||||
|
||||
uchar preamble_length {}; // S: 4(short) 70(long) T: 4(meter) 3(other) R: 10
|
||||
|
||||
uint16_t mfct {};
|
||||
uint32_t id {};
|
||||
uchar version {};
|
||||
uchar media {};
|
||||
|
||||
uchar uart_bps {}; // 5=19200
|
||||
uchar uart_flow_ctrl {}; // 0=None 1=CTS only 2=CTS/RTS 3=RXTX(RS485)
|
||||
uchar data_interface {}; // 0=MBUS with DLL 1=App data without mbus header
|
||||
|
||||
string dongleId()
|
||||
{
|
||||
return tostrprintf("%08x", id);
|
||||
}
|
||||
|
||||
string str()
|
||||
{
|
||||
string mfct_flag = manufacturerFlag(mfct);
|
||||
|
||||
return tostrprintf("id=%08x mfct=%04x (%s) media=%02x version=%02x rssi_mode=%02x "
|
||||
"uart_bps=%02x uart_flow_ctrl=%02x data_interface=%02x "
|
||||
"radio_channel=%02x radio_power=%02x radio_data_rate=%02x preamble_length=%02x mbus_mode=%02x",
|
||||
id, mfct, mfct_flag.c_str(), media, version, rssi_mode,
|
||||
uart_bps, uart_flow_ctrl, data_interface,
|
||||
radio_channel, radio_power, radio_data_rate, preamble_length, mbus_mode);
|
||||
}
|
||||
|
||||
bool decode(vector<uchar> &bytes)
|
||||
{
|
||||
if (bytes.size() < 0x20) return false;
|
||||
|
||||
radio_channel = bytes[0];
|
||||
radio_power = bytes[1];
|
||||
radio_data_rate = bytes[2];
|
||||
mbus_mode = bytes[3];
|
||||
sleep_mode = bytes[4];
|
||||
rssi_mode = bytes[5];
|
||||
|
||||
preamble_length = bytes[0x0a];
|
||||
|
||||
mfct = bytes[0x1a] << 8 | bytes[0x19];
|
||||
id = bytes[0x1e] << 24 | bytes[0x1d] << 16 | bytes[0x1c] << 8 | bytes[0x1b];
|
||||
version = bytes[0x1f];
|
||||
media = bytes[0x20];
|
||||
|
||||
uart_bps = bytes[0x30];
|
||||
uart_flow_ctrl = bytes[0x35];
|
||||
data_interface = bytes[0x36];
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct WMBusRC1180 : public virtual WMBusCommonImplementation
|
||||
{
|
||||
bool ping();
|
||||
string getDeviceId();
|
||||
string getDeviceUniqueId();
|
||||
LinkModeSet getLinkModes();
|
||||
void deviceReset();
|
||||
void deviceSetLinkModes(LinkModeSet lms);
|
||||
LinkModeSet supportedLinkModes() {
|
||||
return
|
||||
T1_bit;
|
||||
// This device can be set to S1,S1-m,S2,T1,T2,R2
|
||||
// with a combination of radio_channel+radio_data_rate+mbus_mode+preamble_length.
|
||||
// However it is unclear from the documentation if these
|
||||
// settings are for transmission only or also for listening...?
|
||||
// My dongle has mbus_mode=1 and hears T1 telegrams,
|
||||
// even though the radio_channel and the preamble_length
|
||||
// is wrongfor T1 mode. So I will leave this
|
||||
// dongle in default mode, which seems to be T1
|
||||
// until someone can double check with an s1 meter.
|
||||
}
|
||||
int numConcurrentLinkModes() { return 1; }
|
||||
bool canSetLinkModes(LinkModeSet lms)
|
||||
{
|
||||
if (0 == countSetBits(lms.bits())) return false;
|
||||
if (!supportedLinkModes().supports(lms)) return false;
|
||||
// Ok, the supplied link modes are compatible,
|
||||
// but rc1180 can only listen to one at a time.
|
||||
return 1 == countSetBits(lms.bits());
|
||||
}
|
||||
void processSerialData();
|
||||
void simulate();
|
||||
|
||||
WMBusRC1180(shared_ptr<SerialDevice> serial, shared_ptr<SerialCommunicationManager> manager);
|
||||
~WMBusRC1180() { }
|
||||
|
||||
private:
|
||||
ConfigRC1180 device_config_;
|
||||
|
||||
vector<uchar> read_buffer_;
|
||||
vector<uchar> request_;
|
||||
vector<uchar> response_;
|
||||
|
||||
LinkModeSet link_modes_ {};
|
||||
string sent_command_;
|
||||
string received_response_;
|
||||
|
||||
FrameStatus checkRC1180Frame(vector<uchar> &data,
|
||||
size_t *hex_frame_length,
|
||||
vector<uchar> &payload);
|
||||
|
||||
string setup_;
|
||||
};
|
||||
|
||||
shared_ptr<WMBus> openRC1180(string device, shared_ptr<SerialCommunicationManager> manager, shared_ptr<SerialDevice> serial_override)
|
||||
{
|
||||
assert(device != "");
|
||||
|
||||
if (serial_override)
|
||||
{
|
||||
WMBusRC1180 *imp = new WMBusRC1180(serial_override, manager);
|
||||
return shared_ptr<WMBus>(imp);
|
||||
}
|
||||
|
||||
auto serial = manager->createSerialDeviceTTY(device.c_str(), 19200, "rc1180");
|
||||
WMBusRC1180 *imp = new WMBusRC1180(serial, manager);
|
||||
return shared_ptr<WMBus>(imp);
|
||||
}
|
||||
|
||||
WMBusRC1180::WMBusRC1180(shared_ptr<SerialDevice> serial, shared_ptr<SerialCommunicationManager> manager) :
|
||||
WMBusCommonImplementation(DEVICE_RC1180, manager, serial)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
bool WMBusRC1180::ping()
|
||||
{
|
||||
verbose("(rc1180) ping\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
string WMBusRC1180::getDeviceId()
|
||||
{
|
||||
if (serial()->readonly()) return "?"; // Feeding from stdin or file.
|
||||
if (cached_device_id_ != "") return cached_device_id_;
|
||||
|
||||
bool ok = false;
|
||||
string hex;
|
||||
|
||||
LOCK_WMBUS_EXECUTING_COMMAND(getDeviceId);
|
||||
|
||||
serial()->disableCallbacks();
|
||||
|
||||
request_.resize(1);
|
||||
request_[0] = 0;
|
||||
|
||||
verbose("(rc1180) get config to get device id\n");
|
||||
|
||||
// Enter config mode.
|
||||
ok = serial()->send(request_);
|
||||
if (!ok) goto err;
|
||||
|
||||
// Wait for the dongle to enter config mode.
|
||||
usleep(200*1000);
|
||||
|
||||
// Config mode active....
|
||||
ok = serial()->waitFor('>');
|
||||
if (!ok) goto err;
|
||||
|
||||
response_.clear();
|
||||
|
||||
// send config command '0' to get all config data
|
||||
request_[0] = '0';
|
||||
|
||||
ok = serial()->send(request_);
|
||||
if (!ok) goto err;
|
||||
|
||||
// Wait for the dongle to respond.
|
||||
usleep(200*1000);
|
||||
|
||||
ok = serial()->receive(&response_);
|
||||
if (!ok) goto err;
|
||||
|
||||
device_config_.decode(response_);
|
||||
|
||||
cached_device_id_ = tostrprintf("%08x", device_config_.id);
|
||||
|
||||
verbose("(rc1180) got device id %s\n", cached_device_id_.c_str());
|
||||
|
||||
// Send config command 'X' to exit config mode.
|
||||
request_[0] = 'X';
|
||||
ok = serial()->send(request_);
|
||||
if (!ok) goto err;
|
||||
|
||||
serial()->enableCallbacks();
|
||||
|
||||
return cached_device_id_;
|
||||
|
||||
err:
|
||||
serial()->enableCallbacks();
|
||||
|
||||
return "ERR";
|
||||
}
|
||||
|
||||
string WMBusRC1180::getDeviceUniqueId()
|
||||
{
|
||||
return "?";
|
||||
}
|
||||
|
||||
LinkModeSet WMBusRC1180::getLinkModes()
|
||||
{
|
||||
return link_modes_;
|
||||
}
|
||||
|
||||
void WMBusRC1180::deviceReset()
|
||||
{
|
||||
// No device specific settings needed right now.
|
||||
// The common code in wmbus.cc reset()
|
||||
// will open the serial device and potentially
|
||||
// set the link modes properly.
|
||||
}
|
||||
|
||||
void WMBusRC1180::deviceSetLinkModes(LinkModeSet lms)
|
||||
{
|
||||
if (serial()->readonly()) return; // Feeding from stdin or file.
|
||||
|
||||
if (!canSetLinkModes(lms))
|
||||
{
|
||||
string modes = lms.hr();
|
||||
error("(rc1180) setting link mode(s) %s is not supported\n", modes.c_str());
|
||||
}
|
||||
|
||||
// Do not actually try to change the link mode, we assume it is T1.
|
||||
return;
|
||||
}
|
||||
|
||||
void WMBusRC1180::simulate()
|
||||
{
|
||||
}
|
||||
|
||||
void WMBusRC1180::processSerialData()
|
||||
{
|
||||
vector<uchar> data;
|
||||
|
||||
// Receive and accumulated serial data until a full frame has been received.
|
||||
serial()->receive(&data);
|
||||
|
||||
read_buffer_.insert(read_buffer_.end(), data.begin(), data.end());
|
||||
|
||||
size_t frame_length;
|
||||
int payload_len, payload_offset;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
FrameStatus status = checkWMBusFrame(read_buffer_, &frame_length, &payload_len, &payload_offset);
|
||||
|
||||
if (status == PartialFrame)
|
||||
{
|
||||
// Partial frame, stop eating.
|
||||
break;
|
||||
}
|
||||
if (status == ErrorInFrame)
|
||||
{
|
||||
verbose("(rawtty) protocol error in message received!\n");
|
||||
string msg = bin2hex(read_buffer_);
|
||||
debug("(rawtty) protocol error \"%s\"\n", msg.c_str());
|
||||
read_buffer_.clear();
|
||||
break;
|
||||
}
|
||||
if (status == FullFrame)
|
||||
{
|
||||
vector<uchar> payload;
|
||||
if (payload_len > 0)
|
||||
{
|
||||
uchar l = payload_len;
|
||||
payload.insert(payload.end(), &l, &l+1); // Re-insert the len byte.
|
||||
payload.insert(payload.end(), read_buffer_.begin()+payload_offset, read_buffer_.begin()+payload_offset+payload_len);
|
||||
}
|
||||
read_buffer_.erase(read_buffer_.begin(), read_buffer_.begin()+frame_length);
|
||||
AboutTelegram about("", 0);
|
||||
handleTelegram(about, payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AccessCheck detectRC1180(Detected *detected, shared_ptr<SerialCommunicationManager> manager)
|
||||
{
|
||||
// Talk to the device and expect a very specific answer.
|
||||
auto serial = manager->createSerialDeviceTTY(detected->found_file.c_str(), 19200, "detect rc1180");
|
||||
serial->disableCallbacks();
|
||||
AccessCheck rc = serial->open(false);
|
||||
if (rc != AccessCheck::AccessOK) return AccessCheck::NotThere;
|
||||
|
||||
vector<uchar> data;
|
||||
vector<uchar> msg(1);
|
||||
|
||||
// Send a single 0x00 byte. This will trigger the device to enter command mode
|
||||
// the device then responds with '>'
|
||||
msg[0] = 0;
|
||||
|
||||
serial->send(msg);
|
||||
usleep(200*1000);
|
||||
serial->receive(&data);
|
||||
|
||||
if (data[0] != '>')
|
||||
{
|
||||
// no RC1180 device detected
|
||||
serial->close();
|
||||
verbose("(rc1180) are you there? no.\n");
|
||||
return AccessCheck::NotThere;
|
||||
}
|
||||
|
||||
data.clear();
|
||||
|
||||
// send '0' to get get the version string: "V 1.67 nanoRC1180868" or similar
|
||||
msg[0] = '0';
|
||||
|
||||
serial->send(msg);
|
||||
// Wait for 200ms so that the USB stick have time to prepare a response.
|
||||
usleep(1000*200);
|
||||
|
||||
serial->receive(&data);
|
||||
string hex = bin2hex(data);
|
||||
|
||||
ConfigRC1180 co;
|
||||
co.decode(data);
|
||||
|
||||
debug("(rc1180) config: %s\n", co.str().c_str());
|
||||
|
||||
msg[0] = 'X';
|
||||
serial->send(msg);
|
||||
|
||||
usleep(1000*200);
|
||||
|
||||
serial->close();
|
||||
|
||||
detected->setAsFound(co.dongleId(), WMBusDeviceType::DEVICE_RC1180, 19200, false, false);
|
||||
|
||||
verbose("(rc1180) are you there? yes %s\n", co.dongleId().c_str());
|
||||
|
||||
return AccessCheck::AccessOK;
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
|
||||
#include"wmbus.h"
|
||||
#include"wmbus_common_implementation.h"
|
||||
#include"wmbus_utils.h"
|
||||
#include"serial.h"
|
||||
|
||||
|
@ -35,7 +36,8 @@ using namespace std;
|
|||
struct WMBusRTL433 : public virtual WMBusCommonImplementation
|
||||
{
|
||||
bool ping();
|
||||
uint32_t getDeviceId();
|
||||
string getDeviceId();
|
||||
string getDeviceUniqueId();
|
||||
LinkModeSet getLinkModes();
|
||||
void deviceReset();
|
||||
void deviceSetLinkModes(LinkModeSet lms);
|
||||
|
@ -55,10 +57,10 @@ struct WMBusRTL433 : public virtual WMBusCommonImplementation
|
|||
void processSerialData();
|
||||
void simulate();
|
||||
|
||||
WMBusRTL433(unique_ptr<SerialDevice> serial, SerialCommunicationManager *manager);
|
||||
WMBusRTL433(shared_ptr<SerialDevice> serial, shared_ptr<SerialCommunicationManager> manager);
|
||||
|
||||
private:
|
||||
unique_ptr<SerialDevice> serial_;
|
||||
shared_ptr<SerialDevice> serial_;
|
||||
vector<uchar> read_buffer_;
|
||||
vector<uchar> received_payload_;
|
||||
bool warning_dll_len_printed_ {};
|
||||
|
@ -72,27 +74,27 @@ private:
|
|||
string setup_;
|
||||
};
|
||||
|
||||
unique_ptr<WMBus> openRTL433(string command, SerialCommunicationManager *manager,
|
||||
function<void()> on_exit, unique_ptr<SerialDevice> serial_override)
|
||||
shared_ptr<WMBus> openRTL433(string identifier, string command, shared_ptr<SerialCommunicationManager> manager,
|
||||
function<void()> on_exit, shared_ptr<SerialDevice> serial_override)
|
||||
{
|
||||
assert(identifier != "");
|
||||
vector<string> args;
|
||||
vector<string> envs;
|
||||
args.push_back("-c");
|
||||
args.push_back(command);
|
||||
if (serial_override)
|
||||
{
|
||||
WMBusRTL433 *imp = new WMBusRTL433(std::move(serial_override), manager);
|
||||
return unique_ptr<WMBus>(imp);
|
||||
WMBusRTL433 *imp = new WMBusRTL433(serial_override, manager);
|
||||
return shared_ptr<WMBus>(imp);
|
||||
}
|
||||
auto serial = manager->createSerialDeviceCommand("/bin/sh", args, envs, on_exit);
|
||||
WMBusRTL433 *imp = new WMBusRTL433(std::move(serial), manager);
|
||||
return unique_ptr<WMBus>(imp);
|
||||
auto serial = manager->createSerialDeviceCommand(identifier, "/bin/sh", args, envs, on_exit, "rtl433");
|
||||
WMBusRTL433 *imp = new WMBusRTL433(serial, manager);
|
||||
return shared_ptr<WMBus>(imp);
|
||||
}
|
||||
|
||||
WMBusRTL433::WMBusRTL433(unique_ptr<SerialDevice> serial, SerialCommunicationManager *manager) :
|
||||
WMBusCommonImplementation(DEVICE_RTL433, manager, std::move(serial))
|
||||
WMBusRTL433::WMBusRTL433(shared_ptr<SerialDevice> serial, shared_ptr<SerialCommunicationManager> manager) :
|
||||
WMBusCommonImplementation(DEVICE_RTL433, manager, serial)
|
||||
{
|
||||
manager_->listenTo(this->serial(),call(this,processSerialData));
|
||||
reset();
|
||||
}
|
||||
|
||||
|
@ -101,14 +103,18 @@ bool WMBusRTL433::ping()
|
|||
return true;
|
||||
}
|
||||
|
||||
uint32_t WMBusRTL433::getDeviceId()
|
||||
string WMBusRTL433::getDeviceId()
|
||||
{
|
||||
return 0x11111111;
|
||||
return "?";
|
||||
}
|
||||
|
||||
string WMBusRTL433::getDeviceUniqueId()
|
||||
{
|
||||
return "?";
|
||||
}
|
||||
|
||||
LinkModeSet WMBusRTL433::getLinkModes()
|
||||
{
|
||||
|
||||
return Any_bit;
|
||||
}
|
||||
|
||||
|
@ -208,7 +214,8 @@ void WMBusRTL433::processSerialData()
|
|||
payload[0] = payload.size()-1;
|
||||
}
|
||||
}
|
||||
handleTelegram(payload);
|
||||
AboutTelegram about("", 0);
|
||||
handleTelegram(about, payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -295,3 +302,10 @@ FrameStatus WMBusRTL433::checkRTL433Frame(vector<uchar> &data,
|
|||
|
||||
return FullFrame;
|
||||
}
|
||||
|
||||
AccessCheck detectRTL433(Detected *detected, shared_ptr<SerialCommunicationManager> handler)
|
||||
{
|
||||
detected->setAsFound("", WMBusDeviceType::DEVICE_RTLWMBUS, 0, false, false);
|
||||
|
||||
return AccessCheck::AccessOK;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
|
||||
#include"wmbus.h"
|
||||
#include"wmbus_common_implementation.h"
|
||||
#include"wmbus_utils.h"
|
||||
#include"serial.h"
|
||||
|
||||
|
@ -35,7 +36,8 @@ using namespace std;
|
|||
struct WMBusRTLWMBUS : public virtual WMBusCommonImplementation
|
||||
{
|
||||
bool ping();
|
||||
uint32_t getDeviceId();
|
||||
string getDeviceId();
|
||||
string getDeviceUniqueId();
|
||||
LinkModeSet getLinkModes();
|
||||
void deviceReset();
|
||||
void deviceSetLinkModes(LinkModeSet lms);
|
||||
|
@ -55,10 +57,12 @@ struct WMBusRTLWMBUS : public virtual WMBusCommonImplementation
|
|||
void processSerialData();
|
||||
void simulate();
|
||||
|
||||
WMBusRTLWMBUS(unique_ptr<SerialDevice> serial, SerialCommunicationManager *manager);
|
||||
WMBusRTLWMBUS(string serialnr, shared_ptr<SerialDevice> serial, shared_ptr<SerialCommunicationManager> manager);
|
||||
~WMBusRTLWMBUS() { }
|
||||
|
||||
private:
|
||||
|
||||
string serialnr_;
|
||||
vector<uchar> read_buffer_;
|
||||
vector<uchar> received_payload_;
|
||||
bool warning_dll_len_printed_ {};
|
||||
|
@ -66,33 +70,36 @@ private:
|
|||
FrameStatus checkRTLWMBUSFrame(vector<uchar> &data,
|
||||
size_t *hex_frame_length,
|
||||
int *hex_payload_len_out,
|
||||
int *hex_payload_offset);
|
||||
int *hex_payload_offset,
|
||||
double *rssi);
|
||||
void handleMessage(vector<uchar> &frame);
|
||||
|
||||
string setup_;
|
||||
};
|
||||
|
||||
unique_ptr<WMBus> openRTLWMBUS(string command, SerialCommunicationManager *manager,
|
||||
function<void()> on_exit, unique_ptr<SerialDevice> serial_override)
|
||||
shared_ptr<WMBus> openRTLWMBUS(string serialnr, string command, shared_ptr<SerialCommunicationManager> manager,
|
||||
function<void()> on_exit, shared_ptr<SerialDevice> serial_override)
|
||||
{
|
||||
debug("(rtlwmbus) opening %s\n", serialnr.c_str());
|
||||
|
||||
vector<string> args;
|
||||
vector<string> envs;
|
||||
args.push_back("-c");
|
||||
args.push_back(command);
|
||||
if (serial_override)
|
||||
{
|
||||
WMBusRTLWMBUS *imp = new WMBusRTLWMBUS(std::move(serial_override), manager);
|
||||
return unique_ptr<WMBus>(imp);
|
||||
WMBusRTLWMBUS *imp = new WMBusRTLWMBUS(serialnr, serial_override, manager);
|
||||
imp->markSerialAsOverriden();
|
||||
return shared_ptr<WMBus>(imp);
|
||||
}
|
||||
auto serial = manager->createSerialDeviceCommand("/bin/sh", args, envs, on_exit);
|
||||
WMBusRTLWMBUS *imp = new WMBusRTLWMBUS(std::move(serial), manager);
|
||||
return unique_ptr<WMBus>(imp);
|
||||
auto serial = manager->createSerialDeviceCommand(serialnr, "/bin/sh", args, envs, on_exit, "rtlwmbus");
|
||||
WMBusRTLWMBUS *imp = new WMBusRTLWMBUS(serialnr, serial, manager);
|
||||
return shared_ptr<WMBus>(imp);
|
||||
}
|
||||
|
||||
WMBusRTLWMBUS::WMBusRTLWMBUS(unique_ptr<SerialDevice> serial, SerialCommunicationManager *manager) :
|
||||
WMBusCommonImplementation(DEVICE_RTLWMBUS, manager, std::move(serial))
|
||||
WMBusRTLWMBUS::WMBusRTLWMBUS(string serialnr, shared_ptr<SerialDevice> serial, shared_ptr<SerialCommunicationManager> manager) :
|
||||
WMBusCommonImplementation(DEVICE_RTLWMBUS, manager, serial), serialnr_(serialnr)
|
||||
{
|
||||
manager_->listenTo(this->serial(),call(this,processSerialData));
|
||||
reset();
|
||||
}
|
||||
|
||||
|
@ -101,9 +108,14 @@ bool WMBusRTLWMBUS::ping()
|
|||
return true;
|
||||
}
|
||||
|
||||
uint32_t WMBusRTLWMBUS::getDeviceId()
|
||||
string WMBusRTLWMBUS::getDeviceId()
|
||||
{
|
||||
return 0x11111111;
|
||||
return serialnr_;
|
||||
}
|
||||
|
||||
string WMBusRTLWMBUS::getDeviceUniqueId()
|
||||
{
|
||||
return "?";
|
||||
}
|
||||
|
||||
LinkModeSet WMBusRTLWMBUS::getLinkModes()
|
||||
|
@ -137,7 +149,8 @@ void WMBusRTLWMBUS::processSerialData()
|
|||
|
||||
for (;;)
|
||||
{
|
||||
FrameStatus status = checkRTLWMBUSFrame(read_buffer_, &frame_length, &hex_payload_len, &hex_payload_offset);
|
||||
double rssi = 0;
|
||||
FrameStatus status = checkRTLWMBUSFrame(read_buffer_, &frame_length, &hex_payload_len, &hex_payload_offset, &rssi);
|
||||
|
||||
if (status == PartialFrame)
|
||||
{
|
||||
|
@ -192,7 +205,10 @@ void WMBusRTLWMBUS::processSerialData()
|
|||
payload[0] = payload.size()-1;
|
||||
}
|
||||
}
|
||||
handleTelegram(payload);
|
||||
|
||||
string id = string("rtlwmbus[")+getDeviceId()+"]";
|
||||
AboutTelegram about(id, rssi);
|
||||
handleTelegram(about, payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -200,7 +216,8 @@ void WMBusRTLWMBUS::processSerialData()
|
|||
FrameStatus WMBusRTLWMBUS::checkRTLWMBUSFrame(vector<uchar> &data,
|
||||
size_t *hex_frame_length,
|
||||
int *hex_payload_len_out,
|
||||
int *hex_payload_offset)
|
||||
int *hex_payload_offset,
|
||||
double *rssi)
|
||||
{
|
||||
// C1;1;1;2019-02-09 07:14:18.000;117;102;94740459;0x49449344590474943508780dff5f3500827f0000f10007b06effff530100005f2c620100007f2118010000008000800080008000000000000000000e003f005500d4ff2f046d10086922
|
||||
// There might be a second telegram on the same line ;0x4944.......
|
||||
|
@ -257,8 +274,27 @@ FrameStatus WMBusRTLWMBUS::checkRTLWMBUSFrame(vector<uchar> &data,
|
|||
return ErrorInFrame;
|
||||
}
|
||||
}
|
||||
// Look for start of telegram 0x
|
||||
size_t i = 0;
|
||||
int count = 0;
|
||||
// Look for packet rssi
|
||||
for (; i+1 < data.size(); ++i) {
|
||||
if (data[i] == ';') count++;
|
||||
if (count == 4) break;
|
||||
}
|
||||
if (count == 4)
|
||||
{
|
||||
size_t from = i+1;
|
||||
for (i++; i<data.size(); ++i) {
|
||||
if (data[i] == ';') break;
|
||||
}
|
||||
if ((i-from)<5)
|
||||
{
|
||||
string rssis = string(data.begin()+from,data.begin()+i);
|
||||
*rssi = atof(rssis.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Look for start of telegram 0x
|
||||
for (; i+1 < data.size(); ++i) {
|
||||
if (data[i] == '0' && data[i+1] == 'x') break;
|
||||
}
|
||||
|
@ -288,8 +324,8 @@ FrameStatus WMBusRTLWMBUS::checkRTLWMBUSFrame(vector<uchar> &data,
|
|||
return FullFrame;
|
||||
}
|
||||
|
||||
AccessCheck detectRTLSDR(string device, SerialCommunicationManager *manager)
|
||||
AccessCheck detectRTLWMBUS(Detected *detected, shared_ptr<SerialCommunicationManager> handler)
|
||||
{
|
||||
// No more advanced test than that the /dev/rtlsdr link exists.
|
||||
return checkIfExistsAndSameGroup(device);
|
||||
assert(0);
|
||||
return AccessCheck::NotThere;
|
||||
}
|
||||
|
|
|
@ -15,15 +15,17 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include"wmbus.h"
|
||||
#include"wmbus_utils.h"
|
||||
#include"serial.h"
|
||||
#include"util.h"
|
||||
#include"wmbus.h"
|
||||
#include"wmbus_common_implementation.h"
|
||||
#include"wmbus_utils.h"
|
||||
|
||||
#include<assert.h>
|
||||
#include<errno.h>
|
||||
#include<fcntl.h>
|
||||
#include<pthread.h>
|
||||
#include<semaphore.h>
|
||||
#include<errno.h>
|
||||
#include<sys/types.h>
|
||||
#include<unistd.h>
|
||||
|
||||
|
@ -32,7 +34,8 @@ using namespace std;
|
|||
struct WMBusSimulator : public WMBusCommonImplementation
|
||||
{
|
||||
bool ping();
|
||||
uint32_t getDeviceId();
|
||||
string getDeviceId();
|
||||
string getDeviceUniqueId();
|
||||
LinkModeSet getLinkModes();
|
||||
void deviceReset();
|
||||
void deviceSetLinkModes(LinkModeSet lms);
|
||||
|
@ -44,7 +47,7 @@ struct WMBusSimulator : public WMBusCommonImplementation
|
|||
void simulate();
|
||||
string device() { return file_; }
|
||||
|
||||
WMBusSimulator(string file, SerialCommunicationManager *manager);
|
||||
WMBusSimulator(string file, shared_ptr<SerialCommunicationManager> manager);
|
||||
|
||||
private:
|
||||
vector<uchar> received_payload_;
|
||||
|
@ -55,37 +58,37 @@ private:
|
|||
vector<string> lines_;
|
||||
};
|
||||
|
||||
int loadFile(string file, vector<string> *lines);
|
||||
|
||||
unique_ptr<WMBus> openSimulator(string device, SerialCommunicationManager *manager, unique_ptr<SerialDevice> serial_override)
|
||||
shared_ptr<WMBus> openSimulator(string device, shared_ptr<SerialCommunicationManager> manager, shared_ptr<SerialDevice> serial_override)
|
||||
{
|
||||
WMBusSimulator *imp = new WMBusSimulator(device, manager);
|
||||
return unique_ptr<WMBus>(imp);
|
||||
return shared_ptr<WMBus>(imp);
|
||||
}
|
||||
|
||||
WMBusSimulator::WMBusSimulator(string file, SerialCommunicationManager *manager)
|
||||
: WMBusCommonImplementation(DEVICE_SIMULATOR, manager, NULL), file_(file)
|
||||
WMBusSimulator::WMBusSimulator(string file, shared_ptr<SerialCommunicationManager> manager)
|
||||
: WMBusCommonImplementation(DEVICE_SIMULATION, manager, NULL), file_(file)
|
||||
{
|
||||
vector<string> lines;
|
||||
assert(file != "");
|
||||
loadFile(file, &lines_);
|
||||
}
|
||||
|
||||
bool WMBusSimulator::ping() {
|
||||
verbose("(simulator) ping\n");
|
||||
verbose("(simulator) pong\n");
|
||||
bool WMBusSimulator::ping()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t WMBusSimulator::getDeviceId() {
|
||||
verbose("(simulator) get device info\n");
|
||||
verbose("(simulator) device info: 11111111\n");
|
||||
return 0x11111111;
|
||||
string WMBusSimulator::getDeviceId()
|
||||
{
|
||||
return "?";
|
||||
}
|
||||
|
||||
LinkModeSet WMBusSimulator::getLinkModes() {
|
||||
verbose("(simulator) get link mode\n");
|
||||
string WMBusSimulator::getDeviceUniqueId()
|
||||
{
|
||||
return "?";
|
||||
}
|
||||
|
||||
LinkModeSet WMBusSimulator::getLinkModes()
|
||||
{
|
||||
string hr = link_modes_.hr();
|
||||
verbose("(simulator) config: link mode %s\n", hr.c_str());
|
||||
return link_modes_;
|
||||
}
|
||||
|
||||
|
@ -96,51 +99,11 @@ void WMBusSimulator::deviceReset()
|
|||
void WMBusSimulator::deviceSetLinkModes(LinkModeSet lms)
|
||||
{
|
||||
link_modes_ = lms;
|
||||
string hr = lms.hr();
|
||||
verbose("(simulator) set link mode %s\n", hr.c_str());
|
||||
verbose("(simulator) set link mode completed\n");
|
||||
}
|
||||
|
||||
int loadFile(string file, vector<string> *lines)
|
||||
void WMBusSimulator::processSerialData()
|
||||
{
|
||||
char block[32768+1];
|
||||
vector<uchar> buf;
|
||||
|
||||
int fd = open(file.c_str(), O_RDONLY);
|
||||
if (fd == -1) {
|
||||
return -1;
|
||||
}
|
||||
while (true) {
|
||||
ssize_t n = read(fd, block, sizeof(block));
|
||||
if (n == -1) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
error("Could not read file %s errno=%d\n", file.c_str(), errno);
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
buf.insert(buf.end(), block, block+n);
|
||||
if (n < (ssize_t)sizeof(block)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
|
||||
bool eof, err;
|
||||
auto i = buf.begin();
|
||||
for (;;) {
|
||||
string line = eatTo(buf, i, '\n', 32768, &eof, &err);
|
||||
if (err) {
|
||||
error("Error parsing simulation file.\n");
|
||||
}
|
||||
if (line.length() > 0) {
|
||||
lines->push_back(line);
|
||||
}
|
||||
if (eof) break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
assert(0);
|
||||
}
|
||||
|
||||
void WMBusSimulator::simulate()
|
||||
|
@ -167,23 +130,27 @@ void WMBusSimulator::simulate()
|
|||
}
|
||||
if (found_time)
|
||||
{
|
||||
debug("(simulator) from file \"%s\" to trigger at relative time %ld\n", hex.c_str(), rel_time);
|
||||
debug("(simulation) from file \"%s\" to trigger at relative time %ld\n", hex.c_str(), rel_time);
|
||||
time_t curr = time(NULL);
|
||||
if (curr < start_time+rel_time)
|
||||
{
|
||||
debug("(simulator) waiting %d seconds before simulating telegram.\n", (start_time+rel_time)-curr);
|
||||
debug("(simulation) waiting %d seconds before simulating telegram.\n", (start_time+rel_time)-curr);
|
||||
for (;;)
|
||||
{
|
||||
curr = time(NULL);
|
||||
if (curr > start_time + rel_time) break;
|
||||
usleep(1000*1000);
|
||||
if (!manager_->isRunning()) break;
|
||||
if (!manager_->isRunning())
|
||||
{
|
||||
debug("(simulation) exiting early\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
debug("(simulator) from file \"%s\"\n", hex.c_str());
|
||||
debug("(simulation) from file \"%s\"\n", hex.c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -197,7 +164,8 @@ void WMBusSimulator::simulate()
|
|||
{
|
||||
error("Not a valid string of hex bytes! \"%s\"\n", l.c_str());
|
||||
}
|
||||
handleTelegram(payload);
|
||||
AboutTelegram about("", 0);
|
||||
handleTelegram(about, payload);
|
||||
}
|
||||
manager_->stop();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2018-2019 Fredrik Öhrström
|
||||
Copyright (C) 2018-2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#define WMBUS_UTILS_H
|
||||
|
||||
#include "util.h"
|
||||
#include "threads.h"
|
||||
#include "wmbus.h"
|
||||
|
||||
bool decrypt_ELL_AES_CTR(Telegram *t, vector<uchar> &frame, vector<uchar>::iterator &pos, vector<uchar> &aeskey);
|
||||
|
@ -26,48 +27,4 @@ bool decrypt_TPL_AES_CBC_IV(Telegram *t, vector<uchar> &frame, vector<uchar>::it
|
|||
bool decrypt_TPL_AES_CBC_NO_IV(Telegram *t, vector<uchar> &frame, vector<uchar>::iterator &pos, vector<uchar> &aeskey);
|
||||
string frameTypeKamstrupC1(int ft);
|
||||
|
||||
struct WMBusCommonImplementation : public virtual WMBus
|
||||
{
|
||||
WMBusCommonImplementation(WMBusDeviceType t, SerialCommunicationManager *manager, unique_ptr<SerialDevice> serial_override);
|
||||
|
||||
WMBusDeviceType type();
|
||||
void setMeters(vector<unique_ptr<Meter>> *meters);
|
||||
void onTelegram(function<bool(vector<uchar>)> cb);
|
||||
bool handleTelegram(vector<uchar> frame);
|
||||
void checkStatus();
|
||||
void setTimeout(int seconds, std::string expected_activity);
|
||||
void setLinkModes(LinkModeSet lms);
|
||||
bool reset();
|
||||
SerialDevice *serial() { if (serial_) return serial_.get(); else return NULL; }
|
||||
string device() { if (serial_) return serial_->device(); else return "?"; }
|
||||
|
||||
protected:
|
||||
|
||||
SerialCommunicationManager *manager_ {};
|
||||
void protocolErrorDetected();
|
||||
void resetProtocolErrorCount();
|
||||
bool areLinkModesConfigured();
|
||||
// Device specific set link modes implementation.
|
||||
virtual void deviceSetLinkModes(LinkModeSet lms) = 0;
|
||||
// Device specific reset code, apart from serial->open and setLinkModes.
|
||||
virtual void deviceReset() = 0;
|
||||
LinkModeSet protectedGetLinkModes(); // Used to read private link_modes_ in subclass.
|
||||
|
||||
private:
|
||||
|
||||
vector<function<bool(vector<uchar>)>> telegram_listeners_;
|
||||
vector<unique_ptr<Meter>> *meters_;
|
||||
WMBusDeviceType type_ {};
|
||||
int protocol_error_count_ {};
|
||||
time_t timeout_ {}; // If longer silence than timeout, then reset dongle! It might have hanged!
|
||||
string expected_activity_ {}; // During which times should we care about timeouts?
|
||||
time_t last_received_ {}; // When as the last telegram reception?
|
||||
time_t last_reset_ {}; // When did we last attempt a reset of the dongle?
|
||||
|
||||
bool link_modes_configured_ {};
|
||||
LinkModeSet link_modes_ {};
|
||||
|
||||
unique_ptr<SerialDevice> serial_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,376 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2020 Fredrik Öhrström
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include"wmbus.h"
|
||||
#include"wmbus_utils.h"
|
||||
#include"wmbus_cul.h"
|
||||
#include"serial.h"
|
||||
|
||||
#include<assert.h>
|
||||
#include<fcntl.h>
|
||||
#include<grp.h>
|
||||
#include<pthread.h>
|
||||
#include<semaphore.h>
|
||||
#include<string.h>
|
||||
#include<errno.h>
|
||||
#include<sys/stat.h>
|
||||
#include<sys/types.h>
|
||||
#include<unistd.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
/*
|
||||
Sadly, the WMB13U-868 dongle uses a prolific pl2303 USB2Serial converter
|
||||
and it seems like there are bugs in the linux drivers for this converter.
|
||||
Or the device itself is buggy....
|
||||
Anyway, the dongle works when first plugged in, but if
|
||||
the ttyUSB0 is closed and then opened again, it most likely
|
||||
stops working.
|
||||
|
||||
So the dongle can perhaps be used like this:
|
||||
configure the dongle using the Windows software to use your
|
||||
desired C1 or T1 mode. Then plug it into your linux box.
|
||||
This driver intentionally does not write to the dongle,
|
||||
if you are lucky, the dongle might receive nicely and
|
||||
not hang.
|
||||
|
||||
Update! It seems like the dongle will hang eventually anyway. :-(
|
||||
*/
|
||||
struct WMBusWMB13U : public virtual WMBusCommonImplementation
|
||||
{
|
||||
bool ping();
|
||||
uint32_t getDeviceId();
|
||||
LinkModeSet getLinkModes();
|
||||
void deviceReset();
|
||||
void deviceSetLinkModes(LinkModeSet lms);
|
||||
LinkModeSet supportedLinkModes() {
|
||||
return
|
||||
C1_bit |
|
||||
S1_bit |
|
||||
T1_bit;
|
||||
}
|
||||
int numConcurrentLinkModes() { return 1; }
|
||||
bool canSetLinkModes(LinkModeSet lms)
|
||||
{
|
||||
if (0 == countSetBits(lms.bits())) return false;
|
||||
if (!supportedLinkModes().supports(lms)) return false;
|
||||
// Ok, the supplied link modes are compatible,
|
||||
// but wmb13u can only listen to one at a time.
|
||||
return 1 == countSetBits(lms.bits());
|
||||
}
|
||||
void processSerialData();
|
||||
void simulate();
|
||||
|
||||
WMBusWMB13U(unique_ptr<SerialDevice> serial, SerialCommunicationManager *manager);
|
||||
~WMBusWMB13U() { }
|
||||
|
||||
private:
|
||||
|
||||
SerialCommunicationManager *manager_ {};
|
||||
LinkModeSet link_modes_ {};
|
||||
vector<uchar> read_buffer_;
|
||||
pthread_mutex_t serial_lock_ = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
FrameStatus checkWMB13UFrame(vector<uchar> &data,
|
||||
size_t *frame_length,
|
||||
vector<uchar> &payload);
|
||||
bool getConfiguration();
|
||||
bool enterConfigModee();
|
||||
bool exitConfigModee();
|
||||
vector<uchar> config_;
|
||||
};
|
||||
|
||||
unique_ptr<WMBus> openWMB13U(string device, SerialCommunicationManager *manager, unique_ptr<SerialDevice> serial_override)
|
||||
{
|
||||
if (serial_override)
|
||||
{
|
||||
WMBusWMB13U *imp = new WMBusWMB13U(std::move(serial_override), manager);
|
||||
return unique_ptr<WMBus>(imp);
|
||||
}
|
||||
|
||||
auto serial = manager->createSerialDeviceTTY(device.c_str(), 19200);
|
||||
WMBusWMB13U *imp = new WMBusWMB13U(std::move(serial), manager);
|
||||
return unique_ptr<WMBus>(imp);
|
||||
}
|
||||
|
||||
WMBusWMB13U::WMBusWMB13U(unique_ptr<SerialDevice> serial, SerialCommunicationManager *manager) :
|
||||
WMBusCommonImplementation(DEVICE_WMB13U, manager, std::move(serial))
|
||||
{
|
||||
manager_->listenTo(this->serial(),call(this,processSerialData));
|
||||
reset();
|
||||
}
|
||||
|
||||
bool WMBusWMB13U::ping()
|
||||
{
|
||||
if (serial()->readonly()) return true; // Feeding from stdin or file.
|
||||
|
||||
/*
|
||||
if (!enterConfigMode()) return false;
|
||||
if (!exitConfigMode()) return false;*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t WMBusWMB13U::getDeviceId()
|
||||
{
|
||||
return 0x11111111;
|
||||
/*getConfiguration();
|
||||
uchar a = config_[0x22];
|
||||
uchar b = config_[0x23];
|
||||
uchar c = config_[0x24];
|
||||
uchar d = config_[0x25];
|
||||
return a << 24 | b << 16 | c << 8 | d;*/
|
||||
}
|
||||
|
||||
LinkModeSet WMBusWMB13U::getLinkModes()
|
||||
{
|
||||
if (serial()->readonly()) { return Any_bit; } // Feeding from stdin or file.
|
||||
|
||||
// getConfiguration();
|
||||
return link_modes_;
|
||||
}
|
||||
|
||||
void WMBusWMB13U::deviceReset()
|
||||
{
|
||||
}
|
||||
|
||||
void WMBusWMB13U::deviceSetLinkModes(LinkModeSet lms)
|
||||
{
|
||||
if (serial()->readonly()) return; // Feeding from stdin or file.
|
||||
|
||||
if (!canSetLinkModes(lms))
|
||||
{
|
||||
string modes = lms.hr();
|
||||
error("(wmb13u) setting link mode(s) %s is not supported for wmb13u\n", modes.c_str());
|
||||
}
|
||||
|
||||
/*
|
||||
if (!enterConfigMode()) return;
|
||||
|
||||
vector<uchar> atg(4);
|
||||
atg[0] = 'A';
|
||||
atg[1] = 'T';
|
||||
atg[2] = 'G';
|
||||
if (lms.has(LinkMode::C1))
|
||||
{
|
||||
// Listening to only C1.
|
||||
atg[3] = 0x10;
|
||||
}
|
||||
else if (lms.has(LinkMode::T1))
|
||||
{
|
||||
// Listening to only T1.
|
||||
atg[3] = 0x01;
|
||||
}
|
||||
else if (lms.has(LinkMode::S1))
|
||||
{
|
||||
// Listening to only S1.
|
||||
atg[3] = 0x03;
|
||||
}
|
||||
|
||||
verbose("(wmb13u) set link mode %02x\n", atg[3]);
|
||||
serial_->send(atg);
|
||||
*/
|
||||
link_modes_ = lms;
|
||||
|
||||
// exitConfigMode();
|
||||
}
|
||||
|
||||
void WMBusWMB13U::simulate()
|
||||
{
|
||||
}
|
||||
|
||||
void WMBusWMB13U::processSerialData()
|
||||
{
|
||||
vector<uchar> data;
|
||||
|
||||
// Try to get the serial lock, if not possible, then we
|
||||
// are in config mode. Stop this processing.
|
||||
if (pthread_mutex_trylock(&serial_lock_) != 0) return;
|
||||
// Receive and accumulated serial data until a full frame has been received.
|
||||
serial()->receive(&data);
|
||||
// Unlock the serial lock.
|
||||
pthread_mutex_unlock(&serial_lock_);
|
||||
|
||||
read_buffer_.insert(read_buffer_.end(), data.begin(), data.end());
|
||||
|
||||
size_t frame_length;
|
||||
int payload_len, payload_offset;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
FrameStatus status = checkWMBusFrame(read_buffer_, &frame_length, &payload_len, &payload_offset);
|
||||
|
||||
if (status == PartialFrame)
|
||||
{
|
||||
// Partial frame, stop eating.
|
||||
break;
|
||||
}
|
||||
if (status == ErrorInFrame)
|
||||
{
|
||||
verbose("(wmb13u) protocol error in message received!\n");
|
||||
string msg = bin2hex(read_buffer_);
|
||||
debug("(wmb13u) protocol error \"%s\"\n", msg.c_str());
|
||||
read_buffer_.clear();
|
||||
break;
|
||||
}
|
||||
if (status == FullFrame)
|
||||
{
|
||||
vector<uchar> payload;
|
||||
if (payload_len > 0)
|
||||
{
|
||||
// It appens RSSI and 2 CRC bytes. Remove those.
|
||||
uchar l = payload_len - 3;
|
||||
payload.insert(payload.end(), &l, &l+1); // Re-insert the len byte.
|
||||
payload.insert(payload.end(), read_buffer_.begin()+payload_offset, read_buffer_.begin()+payload_offset+payload_len-3);
|
||||
}
|
||||
read_buffer_.erase(read_buffer_.begin(), read_buffer_.begin()+frame_length);
|
||||
handleTelegram(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool WMBusWMB13U::enterConfigModee()
|
||||
{
|
||||
pthread_mutex_lock(&serial_lock_);
|
||||
|
||||
vector<uchar> data;
|
||||
|
||||
// send 0xFF to wake up, if sleeping, just to be sure.
|
||||
vector<uchar> wakeup(1), at(2);
|
||||
wakeup[0] = 0xff;
|
||||
|
||||
serial()->send(wakeup);
|
||||
usleep(1000*100);
|
||||
serial()->receive(&data);
|
||||
|
||||
if (!startsWith("OK", data)) goto fail;
|
||||
|
||||
// send AT to enter configuration mode.
|
||||
at[0] = 'A';
|
||||
at[1] = 'T';
|
||||
|
||||
serial()->send(at);
|
||||
usleep(1000*100);
|
||||
serial()->receive(&data);
|
||||
|
||||
if (!startsWith("OK", data)) goto fail;
|
||||
|
||||
return true;
|
||||
|
||||
fail:
|
||||
pthread_mutex_unlock(&serial_lock_);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WMBusWMB13U::exitConfigModee()
|
||||
{
|
||||
vector<uchar> data;
|
||||
|
||||
// send ATQ to exit config mode.
|
||||
vector<uchar> atq(3);
|
||||
atq[0] = 'A';
|
||||
atq[1] = 'T';
|
||||
atq[2] = 'Q';
|
||||
|
||||
serial()->send(atq);
|
||||
usleep(1000*100);
|
||||
serial()->receive(&data);
|
||||
|
||||
// Always unlock....
|
||||
pthread_mutex_unlock(&serial_lock_);
|
||||
|
||||
if (!startsWith("OK", data)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *lmname(int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0x00: return "S2";
|
||||
case 0x01: return "T1";
|
||||
case 0x02: return "T2";
|
||||
case 0x03: return "S1";
|
||||
case 0x04: return "R2";
|
||||
case 0x10: return "C1";
|
||||
case 0x11: return "C2";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
bool WMBusWMB13U::getConfiguration()
|
||||
{
|
||||
bool ok;
|
||||
|
||||
ok = enterConfigModee();
|
||||
if (!ok) return false;
|
||||
|
||||
// send AT0 to acquire configuration.
|
||||
vector<uchar> at0(3);
|
||||
at0[0] = 'A';
|
||||
at0[1] = 'T';
|
||||
at0[2] = '0';
|
||||
|
||||
serial()->send(at0);
|
||||
usleep(1000*100);
|
||||
serial()->receive(&config_);
|
||||
|
||||
verbose("(wmb13u) config: link mode %02x (%s)\n", config_[0x01], lmname(config_[0x01]));
|
||||
verbose("(wmb13u) config: data frame format %02x\n", config_[0x35]);
|
||||
|
||||
ok = exitConfigModee();
|
||||
if (!ok) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AccessCheck detectWMB13U(string device, SerialCommunicationManager *manager)
|
||||
{
|
||||
// Talk to the device and expect a very specific answer.
|
||||
auto serial = manager->createSerialDeviceTTY(device.c_str(), 19200);
|
||||
AccessCheck rc = serial->open(false);
|
||||
if (rc != AccessCheck::AccessOK) return AccessCheck::NotThere;
|
||||
|
||||
verbose("(wmb13u) are you there?\n");
|
||||
|
||||
vector<uchar> data;
|
||||
|
||||
// send 0xFF to wake up, if sleeping, just to be sure.
|
||||
vector<uchar> wakeup(1);
|
||||
wakeup[0] = 0xff;
|
||||
|
||||
serial->send(wakeup);
|
||||
usleep(1000*100);
|
||||
serial->receive(&data);
|
||||
|
||||
if (!startsWith("OK", data)) return AccessCheck::NotThere;
|
||||
|
||||
// send AT to enter configuration mode.
|
||||
vector<uchar> at(2);
|
||||
at[0] = 'A';
|
||||
at[1] = 'T';
|
||||
|
||||
serial->send(at);
|
||||
usleep(1000*100);
|
||||
serial->receive(&data);
|
||||
|
||||
if (!startsWith("OK", data)) return AccessCheck::NotThere;
|
||||
|
||||
serial->close();
|
||||
return AccessCheck::AccessOK;
|
||||
}
|
10
test.sh
10
test.sh
|
@ -3,6 +3,12 @@
|
|||
PROG="$1"
|
||||
TESTINTERNAL=$(dirname $PROG)/testinternals
|
||||
|
||||
if [ ! -x $PROG ]
|
||||
then
|
||||
echo No such executable \"$PROG\"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
$TESTINTERNAL
|
||||
if [ "$?" = "0" ]; then
|
||||
echo OK: test internals
|
||||
|
@ -54,8 +60,8 @@ if [ "$?" != "0" ]; then RC="1"; fi
|
|||
tests/test_fields.sh $PROG
|
||||
if [ "$?" != "0" ]; then RC="1"; fi
|
||||
|
||||
#tests/test_oneshot.sh $PROG broken test
|
||||
#if [ "$?" != "0" ]; then RC="1"; fi
|
||||
tests/test_oneshot.sh $PROG broken test
|
||||
if [ "$?" != "0" ]; then RC="1"; fi
|
||||
|
||||
tests/test_wrongkeys.sh $PROG
|
||||
if [ "$?" != "0" ]; then RC="1"; fi
|
||||
|
|
|
@ -3,4 +3,3 @@ device=simulations/simulation_conversionsadded.txt
|
|||
logtelegrams=false
|
||||
format=json
|
||||
addconversions=GJ,L,F
|
||||
reopenafter=1h
|
|
@ -9,7 +9,7 @@ shell=echo METER =="$METER_JSON"== >> /tmp/wmbusmeters_telegram_test
|
|||
# The alarm will always be logged in the log file.
|
||||
alarmshell=echo ALARM_SHELL "$ALARM_TYPE" "$ALARM_MESSAGE" >> /tmp/wmbusmeters_alarm_test
|
||||
# Expect a received telegram no longer than 1 second since the last telegram!
|
||||
alarmtimeout=1s
|
||||
alarmtimeout=4s
|
||||
# Only sound the alarm if the timeout is reached when the radio is actually
|
||||
# expected to be transmitting. Some meters disable transmissions during nights
|
||||
# and weekends. Change this to mon-fri(08-19)
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
#!/bin/sh
|
||||
echo "T1;1;1;2019-04-03 19:00:42.000;97;148;88888888;0x6e4401068888888805077a85006085bc2630713819512eb4cd87fba554fb43f67cf9654a68ee8e194088160df752e716238292e8af1ac20986202ee561d743602466915e42f1105d9c6782a54504e4f099e65a7656b930c73a30775122d2fdf074b5035cfaa7e0050bf32faae03a77"
|
||||
#{"media":"water","meter":"apator162","name":"ApWater","id":"88888888","total_m3":4.848,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
#{"media":"water","meter":"apator162","name":"ApWater","id":"88888888","total_m3":4.848,"timestamp":"1111-11-11T11:11:11Z","device":"rtlwmbus[cmd_0]","rssi_dbm":97}
|
||||
|
|
|
@ -10,7 +10,7 @@ TESTRESULT="ERROR"
|
|||
cat simulations/simulation_additional_json.txt | grep '^{' > $TEST/test_expected.txt
|
||||
$PROG --format=json --json_floor=5 --json_address="RoodRd 42" simulations/simulation_additional_json.txt \
|
||||
MyTapWater multical21 76348799 "" \
|
||||
> $TEST/test_output.txt
|
||||
> $TEST/test_output.txt 2> $TEST/test_stderr.txt
|
||||
|
||||
if [ "$?" = "0" ]
|
||||
then
|
||||
|
@ -29,8 +29,7 @@ if [ "$TESTRESULT" = "ERROR" ]; then echo ERROR: $TESTNAME; exit 1; fi
|
|||
TESTNAME="Test additional shell envs from cmdline"
|
||||
TESTRESULT="ERROR"
|
||||
|
||||
$PROG --format=json --json_floor=5 --json_house="alfa beta" --listenvs --listento=c1 simulations/simulation_additional_json.txt \
|
||||
Vatten multical21 76348799 "" | grep METER_ > $TEST/test_output.txt
|
||||
$PROG --json_floor=5 --json_house="alfa beta" --listenvs=multical21 > $TEST/test_output.txt 2> $TEST/test_stderr.txt
|
||||
|
||||
ENVS=$(cat $TEST/test_output.txt | tr '\n' ' ')
|
||||
|
||||
|
@ -66,7 +65,7 @@ if [ "$TESTRESULT" = "ERROR" ]; then echo ERROR: $TESTNAME; exit 1; fi
|
|||
TESTNAME="Test additional json from wmbusmeters.conf and from meter file"
|
||||
TESTRESULT="ERROR"
|
||||
|
||||
$PROG --useconfig=tests/config6 --device=simulations/simulation_shell.txt --listento=t1 > $TEST/test_output.txt
|
||||
$PROG --useconfig=tests/config6 --device=simulations/simulation_shell.txt > $TEST/test_output.txt 2> $TEST/test_stderr.txt
|
||||
|
||||
if [ "$?" = "0" ]
|
||||
then
|
||||
|
|
|
@ -16,7 +16,7 @@ cat $TEST/test_input.txt | $PROG --format=json "stdin:rtlwmbus" \
|
|||
ApWater apator162 88888888 00000000000000000000000000000000 \
|
||||
Vatten multical21 76348799 28F64A24988064A079AA2C807D6102AE \
|
||||
Wasser supercom587 77777777 5065747220486F6C79737A6577736B69 \
|
||||
> $TEST/test_output.txt
|
||||
> $TEST/test_output.txt 2> $TEST/test_stderr.txt
|
||||
|
||||
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_response.txt
|
||||
diff $TEST/test_expected.txt $TEST/test_response.txt
|
||||
|
|
|
@ -12,10 +12,20 @@ echo "RUNNING $TESTNAME ..."
|
|||
> /tmp/wmbusmeters_telegram_test
|
||||
> /tmp/wmbusmeters_alarm_test
|
||||
|
||||
$PROG --useconfig=tests/config7 --device=simulations/simulation_alarm.txt | sed 's/....-..-.. ..:../1111-11-11 11:11/' > $TEST/test_output.txt
|
||||
$PROG --useconfig=tests/config7 --device=simulations/simulation_alarm.txt 2> $TEST/test_stderr.txt | sed 's/....-..-..T..:..:..Z/1111-11-11T11:11:11Z/' > $TEST/test_output.txt
|
||||
|
||||
echo "STDERR---------------------------------"
|
||||
cat $TEST/test_stderr.txt
|
||||
echo "STDOUT---------------------------------"
|
||||
cat $TEST/test_output.txt
|
||||
echo "TMP/OUTPUT-----------------------------"
|
||||
cat /tmp/wmbusmeters_telegram_test
|
||||
echo "TMP/ALARM------------------------------"
|
||||
cat /tmp/wmbusmeters_alarm_test
|
||||
echo "---------------------------------------"
|
||||
|
||||
cat > $TEST/test_expected.txt <<EOF
|
||||
(alarm) inactivity: 2 seconds of inactivity resetting simulations/simulation_alarm.txt DEVICE_SIMULATOR (timeout 1s expected mon-sun(00-23) now 1111-11-11 11:11)
|
||||
(alarm DeviceInactivity) 4 seconds of inactivity resetting simulations/simulation_alarm.txt SIMULATION (timeout 4s expected mon-sun(00-23) now 1111-11-11 11:11)
|
||||
(wmbus) successfully reset wmbus device
|
||||
EOF
|
||||
|
||||
|
@ -25,16 +35,18 @@ METER =={"media":"cold water","meter":"multical21","name":"Water","id":"76348799
|
|||
EOF
|
||||
|
||||
cat > /tmp/wmbusmeters_alarm_expected <<EOF
|
||||
ALARM_SHELL inactivity 2 seconds of inactivity resetting simulations/simulation_alarm.txt DEVICE_SIMULATOR (timeout 1s expected mon-sun(00-23) now 1111-11-11 11:11)
|
||||
ALARM_SHELL DeviceInactivity (alarm DeviceInactivity) 4 seconds of inactivity resetting simulations/simulation_alarm.txt SIMULATION (timeout 4s expected mon-sun(00-23) now 1111-11-11 11:11)
|
||||
EOF
|
||||
|
||||
REST=$(diff $TEST/test_output.txt $TEST/test_expected.txt)
|
||||
cat $TEST/test_stderr.txt | sed 's/now ....-..-.. ..:../now 1111-11-11 11:11/' > $TEST/test_responses.txt
|
||||
|
||||
REST=$(diff $TEST/test_responses.txt $TEST/test_expected.txt)
|
||||
|
||||
if [ ! -z "$REST" ]
|
||||
then
|
||||
echo ERROR STDOUT: $TESTNAME
|
||||
echo ERROR STDERR: $TESTNAME
|
||||
echo -----------------
|
||||
diff $TEST/test_output.txt $TEST/test_expected.txt
|
||||
diff $TEST/test_responses.txt $TEST/test_expected.txt
|
||||
echo -----------------
|
||||
TESTRESULT="ERROR"
|
||||
fi
|
||||
|
|
|
@ -18,7 +18,7 @@ METERS="Wasser apator162 20202020 NOKEY
|
|||
MyTapWatere apator162 27202020 NOKEY"
|
||||
|
||||
cat simulations/simulation_apas.txt | grep '^{' > $TEST/test_expected.txt
|
||||
$PROG --format=json simulations/simulation_apas.txt $METERS > $TEST/test_output.txt
|
||||
$PROG --format=json simulations/simulation_apas.txt $METERS > $TEST/test_output.txt 2> $TEST/test_stderr.txt
|
||||
if [ "$?" = "0" ]
|
||||
then
|
||||
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
|
||||
|
@ -31,7 +31,7 @@ then
|
|||
fi
|
||||
|
||||
cat simulations/simulation_apas.txt | grep '^|' | sed 's/^|//' > $TEST/test_expected.txt
|
||||
$PROG --format=fields simulations/simulation_apas.txt $METERS > $TEST/test_output.txt
|
||||
$PROG --format=fields simulations/simulation_apas.txt $METERS > $TEST/test_output.txt 2> $TEST/test_stderr.txt
|
||||
if [ "$?" = "0" ]
|
||||
then
|
||||
cat $TEST/test_output.txt | sed 's/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9].[0-9][0-9]$/1111-11-11 11:11.11/' > $TEST/test_responses.txt
|
||||
|
|
|
@ -17,7 +17,7 @@ $PROG --format=json simulations/simulation_c1.txt \
|
|||
MyElement qcaloric 78563412 "" \
|
||||
Rum cma12w 66666666 "" \
|
||||
My403Cooling multical403 78780102 "" \
|
||||
> $TEST/test_output.txt
|
||||
> $TEST/test_output.txt 2> $TEST/test_stderr.txt
|
||||
|
||||
if [ "$?" = "0" ]
|
||||
then
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Ładowanie…
Reference in New Issue