Merge pull request #156 from weetmuts/NewDetectionCode

New detection code
pull/172/head 1.0.0
Fredrik Öhrström 2020-10-25 16:07:19 +01:00 zatwierdzone przez GitHub
commit 941318aa54
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
114 zmienionych plików z 13645 dodań i 3310 usunięć

Wyświetl plik

@ -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
Wyświetl plik

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

Wyświetl plik

@ -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
Wyświetl plik

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

Wyświetl plik

@ -0,0 +1,2 @@
# Empty so far

1480
autoconf/config.guess vendored 100644

Plik diff jest za duży Load Diff

Wyświetl plik

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

1801
autoconf/config.sub vendored 100644

Plik diff jest za duży Load Diff

Wyświetl plik

Wyświetl plik

@ -0,0 +1,14 @@
X:=
SPACE:=$(X) $(X)
COMMA:=,
HASH:=\#
SQUOTE:='
#'
DQUOTE:="
#"
define NEWLINE
endef
CONF_NAME:=@CONF_NAME@

3649
configure vendored

Plik diff jest za duży Load Diff

50
configure.ac 100644
Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -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;
}
*/

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Plik diff jest za duży Load Diff

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -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 += ",";

Wyświetl plik

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

Wyświetl plik

@ -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_ {};

Wyświetl plik

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

Wyświetl plik

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

164
src/rtlsdr.cc 100644
Wyświetl plik

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

30
src/rtlsdr.h 100644
Wyświetl plik

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

Plik diff jest za duży Load Diff

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

237
src/threads.cc 100644
Wyświetl plik

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

118
src/threads.h 100644
Wyświetl plik

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

Wyświetl plik

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

618
src/ui.cc 100644
Wyświetl plik

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

54
src/ui.h 100644
Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Plik diff jest za duży Load Diff

Wyświetl plik

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

Wyświetl plik

@ -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(&timestamp_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;

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

381
src/wmbus_rc1180.cc 100644
Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -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
Wyświetl plik

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

Wyświetl plik

@ -3,4 +3,3 @@ device=simulations/simulation_conversionsadded.txt
logtelegrams=false
format=json
addconversions=GJ,L,F
reopenafter=1h

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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