├── Makefile ├── README.md ├── androidbuild.bat ├── atan2.h ├── build-deb.sh ├── debian ├── changelog ├── control ├── copyright ├── patches │ ├── 1000-build-install-deb.patch │ ├── README │ └── series ├── rules ├── source │ └── format └── watch ├── filter ├── bp_iir_cheb1_1600kHz_90kHz_98kHz_102kHz_110kHz.m ├── bp_iir_cheb1_800kHz_22kHz_30kHz_34kHz_42kHz.m ├── bp_iir_cheb1_800kHz_90kHz_98Hz_102kHz_110kHz.m ├── lp_fir_butter_1600kHz_160kHz_200kHz.m ├── lp_fir_butter_800kHz_100kHz_160kHz.m ├── lp_fir_butter_800kHz_32kHz_36kHz.m ├── lp_ppf_butter_1600kHz_160kHz_200kHz.m ├── print_fir_filter_coef.m ├── print_iir_filter_coef.m ├── print_ppf_filter_coef.m └── rtl_wmbus.m ├── fir.h ├── iir.h ├── include ├── fixedptc │ ├── .hgtags │ ├── Makefile │ ├── README.txt │ ├── fixedptc.h │ ├── test.c │ └── verify.c ├── mode_s_util.h └── mode_t_util.h ├── moving_average_filter.h ├── net_support.h ├── pics ├── 1. open_demod_bin.png ├── 2. demod_bin.png ├── 3. zoom_into_datgram1.png └── demod.bin ├── ppf.h ├── readme.pdf ├── rtl_wmbus.c ├── rtl_wmbus.py ├── rtl_wmbus.vpj ├── rtl_wmbus.vpw ├── rtl_wmbus_util.h ├── s1_packet_decoder.h ├── samples ├── rtlsdr_868.625M_2M4_issue48.cu8 ├── rtlsdr_868.950M_1M6_issue47.cu8 ├── rtlsdr_868.950M_1M6_issue49.cu8 └── rtlsdr_868.950M_1M6_samples2.cu8 └── t1_c1_packet_decoder.h /Makefile: -------------------------------------------------------------------------------- 1 | RM=rm 2 | MKDIR=mkdir 3 | CC=gcc 4 | STRIP=strip 5 | 6 | OUTDIR?=build 7 | OUTFILE="$(OUTDIR)/rtl_wmbus" 8 | CFLAGS+=-Iinclude -std=gnu99 9 | CFLAGS_WARNINGS?=-Wall -W -Waggregate-return -Wbad-function-cast -Wcast-align -Wcast-qual -Wchar-subscripts -Wcomment -Wno-float-equal -Winline -Wmain -Wmissing-noreturn -Wno-missing-prototypes -Wparentheses -Wpointer-arith -Wredundant-decls -Wreturn-type -Wshadow -Wsign-compare -Wstrict-prototypes -Wswitch -Wunreachable-code -Wno-unused -Wuninitialized 10 | LIB?=-lm 11 | SRC=rtl_wmbus.c 12 | 13 | $(shell $(MKDIR) -p $(OUTDIR)) 14 | 15 | # Create a version number based on the latest git tag. 16 | COMMIT_HASH?=$(shell git log --pretty=format:'%H' -n 1) 17 | TAG?=$(shell git describe --tags --always) 18 | BRANCH?=$(shell git rev-parse --abbrev-ref HEAD) 19 | CHANGES?=$(shell git status -s | grep -v '?? ') 20 | 21 | # Prefix with any development branch. 22 | ifeq ($(BRANCH),master) 23 | BRANCH:= 24 | else 25 | BRANCH:=$(BRANCH)_ 26 | endif 27 | 28 | # The version is the git tag or tag-N-hash if there are N commits after the tag. 29 | VERSION:=$(BRANCH)$(TAG) 30 | 31 | ifneq ($(strip $(CHANGES)),) 32 | # There are local non-committed changes! Add this to the version string as well! 33 | VERSION:=$(VERSION) with local changes 34 | COMMIT_HASH:=$(COMMIT_HASH) with local changes 35 | endif 36 | 37 | $(shell echo "#define VERSION \"$(VERSION)\"" > $(OUTDIR)/version.h.tmp) 38 | $(shell echo "#define COMMIT \"$(COMMIT_HASH)\"" >> $(OUTDIR)/version.h.tmp) 39 | 40 | PREV_VERSION=$(shell cat -n $(OUTDIR)/version.h 2> /dev/null) 41 | CURR_VERSION=$(shell cat -n $(OUTDIR)/version.h.tmp 2>/dev/null) 42 | ifneq ($(PREV_VERSION),$(CURR_VERSION)) 43 | $(shell mv $(OUTDIR)/version.h.tmp $(OUTDIR)/version.h) 44 | else 45 | $(shell rm $(OUTDIR)/version.h.tmp) 46 | endif 47 | 48 | $(info Building $(VERSION)) 49 | 50 | all: release 51 | 52 | release: 53 | $(CC) -DNDEBUG -O3 $(CFLAGS) $(CFLAGS_WARNINGS) -o $(OUTFILE) $(SRC) $(LIB) 54 | 55 | debug: 56 | $(CC) -DDEBUG -O0 -g3 -ggdb -p -pg $(CFLAGS) $(CFLAGS_WARNINGS) -o $(OUTFILE) $(SRC) $(LIB) 57 | 58 | # Will build on Raspberry Pi 1 only 59 | pi1: 60 | $(CC) -DNDEBUG -O3 -march=armv6 -mtune=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp -ffast-math $(CFLAGS) $(CFLAGS_WARNINGS) -o $(OUTFILE) $(SRC) $(LIB) 61 | 62 | rebuild: clean all 63 | 64 | install: 65 | @if [ ! -f $(OUTFILE) ] ; then echo "Cannot find the binary to install! You have to run just \"make\" first!" ; exit 1 ; fi 66 | install -d $(DESTDIR)/usr/bin 67 | install $(OUTFILE) $(DESTDIR)/usr/bin 68 | 69 | clean: 70 | $(RM) -rf "$(OUTDIR)" 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rtl-wmbus: software defined receiver for Wireless-M-Bus with RTL-SDR 2 | 3 | rtl-wmbus is a software defined receiver for Wireless-M-Bus. It is written in plain C and uses RTL-SDR (https://github.com/osmocom/rtl-sdr) to interface with RTL2832-based hardware. 4 | 5 | It can also use LimeSDR or other SDR receiver as backend through rx_sdr (see https://github.com/rxseger/rx_tools), which in turn is using SoapySDR to interface with underlying hardware in a vendor-neutral way. 6 | 7 | Wireless-M-Bus is the wireless version of M-Bus ("Meter-Bus", http://www.m-bus.com), which is an European standard for remote reading of smart meters. 8 | 9 | The primary purpose of rtl-wmbus is experimenting with digital signal processing and software radio. rtl-wmbus can be used on resource constrained devices such Raspberry Pi Zero or Raspberry PI B+ overclocked to 1GHz. Any Android based tablet will do the same too. 10 | 11 | rtl-wmbus provides: 12 | * filtering 13 | * FSK demodulation 14 | * dc offset removing 15 | * clock recovering 16 | * mode T1 and mode C1 packet decoding 17 | * mode S1 packet decoding 18 | 19 | rtl-wmbus requires: 20 | * Linux or Android 21 | * C99 22 | * supported DVB-T receiver 23 | * RTL-SDR library for Linux: http://sdr.osmocom.org/trac/wiki/rtl-sdr or 24 | * (optional, only for android builds) RTL-SDR library port for Android: https://github.com/martinmarinov/rtl_tcp_andro- 25 | 26 | For the latest version, see https://github.com/xaelsouth/rtl-wmbus 27 | 28 | 29 | Installing 30 | ---------- 31 | 32 | The Osmocom RTL-SDR library must be installed before you can build rtl-wmbus. See http://sdr.osmocom.org/trac/wiki/rtl-sdr for more 33 | information. RTL-SDR library for Android would be installed via Google Play. 34 | 35 | To install rtl-wmbus, download, unpack the source code and go to the top level directory. Then use one of these three options: 36 | 37 | * make debug # (no optimization, with all debug options on) 38 | 39 | * make release # (-O3 optimized version, without any debugging options) 40 | 41 | * make pi1 # (Raspberry Pi optimized version, without any debugging options, will build on RasPi1) only 42 | 43 | Before building Android version the SDK and NDK have to be installed. See androidbuild.bat for how to build and install. 44 | 45 | For Windows users: 46 | * Download and install https://downloads.myriadrf.org/builds/PothosSDR/PothosSDR-2021.02.28-vc16-x64.exe 47 | * Compile rtl-wmbus with MSYS2-MinGW just by executing make in MSYS2-MinGW-Console... 48 | * Or use precompiled binary from the build directory 49 | * Start in the console: "c:\Program Files\PothosSDR\bin\rtl_sdr" -f 868.95M -s 1600000 - 2>NUL | build\rtl_wmbus_x64.exe 50 | * Optionally replace the path to rtl_sdr with that where you have PothosSDR installed 51 | 52 | Usage 53 | ----- 54 | To save an I/Q-stream on disk and decode that off-line: 55 | * rtl_sdr samples.cu8 -f 868.95M -s 1600000 56 | * cat samples.cu8 | build/rtl_wmbus 57 | 58 | To save an I/Q-stream and decode this immediately to see what's going on right now: 59 | * rtl_sdr -f 868.95M -s 1600000 - 2>/dev/null | tee samples.cu8 | build/rtl_wmbus 60 | 61 | To run continuously without saving anything on disk: 62 | * rtl_sdr -f 868.95M -s 1600000 - 2>/dev/null | build/rtl_wmbus 63 | 64 | To run continuously with rx_sdr (or even with a higher sampling and decimation rate) 65 | * rx_sdr -f 868.95M -s 1600000 - 2>/dev/null | build/rtl_wmbus 66 | * rx_sdr -f 868.95M -s 4000000 - 2>/dev/null | build/rtl_wmbus -d 5 67 | 68 | Notice "-d 5" in the last line: it's a multiple of 800kHz resulting from the sample rate of 4MHz. 69 | 70 | To count "good" (no 3 out of 6 errors, no checksum errors) packets: 71 | * cat samples.cu8 | build/rtl_wmbus 2>/dev/null | grep "[T,C,S]1;1;1" | wc -l 72 | 73 | Carrier-frequency given at "-f" must be set properly. With my DVB-T-Receiver I had to choose carrier 50kHz under the standard of 868.95MHz. Sample rate at 1.6Ms/s should be used or use a multiple of 800kHz. RTL-SDR supports sampling rate up to 3.2 MSamples, so you can choose 1.6 MSamples, 2.4 MSamples or 3.2 MSamples. 74 | 75 | See samples/rtlsdr_868.950M_1M6_samples2.cu8 for an example of two T1 mode devices. 76 | 77 | On Android the driver must be started first with options given above. I/Q-data goes to a port which is to be set in the driver settings. Use get_net to get I/Q-data into rtl_wmbus. 78 | 79 | The output data is semicolon separated and the meaning of the columns are: 80 | 81 | `MODE;CRC_OK;3OUTOF6OK;TIMESTAMP;PACKET_RSSI;CURRENT_RSSI;LINK_LAYER_IDENT_NO;DATAGRAM_WITHOUT_CRC_BYTES.` 82 | 83 | 3OUTOF6OK is only relevant for T1 and can be ignored for C1 and S1 (always set to 1). 84 | 85 | Bugfixing 86 | ----- 87 | Mode C1 datagram type B is supported now - thanks to Fredrik Öhrström for spotting this and for providing raw datagram samples. An another thanks goes to Kjell Braden (afflux) and to carlos62 for the idea how to implement this. 88 | 89 | Redefining CFLAGS and OUTPUT directory is allowed now (patch sent by dwrobel). 90 | 91 | L(ength) field from C1 mode B datagrams does not include CRC bytes anymore: L field will now be printed as if the datagram would be received from a T1 or C1 mode A meter. 92 | 93 | Significantly improved C1 receiver quality. Sad, but in the low-pass-filter was a bug: the stopband edge frequency was specified as 10kHz instead of 110kHz. I have changed the latter to 160kHz and recalculated filter coefficients. 94 | 95 | Packet rssi value fixed for S1 mode (was always 0): thanks to alalons. 96 | 97 | Improvements 98 | ----- 99 | A new method for picking datagrams out of the bit stream that _could_ probably better perform in C1 mode has been implemented. 100 | I called that "run length algorithm". I don't know if any similar term already exists in the literature. 101 | The new method is very sensitive to bit glitches, which have to be filtered out of the bit stream with an asymmetrical deglitch filter. 102 | The deglitch filter _must_ be implemented asymmetrical in this case, because RTL-SDR produces more "0" bits than "1" bits on it's output. 103 | The latter seems more to be a hardware feature rather than a bug. 104 | 105 | Run length algorithm is running in parallel (and is fully independant) to the time2 method. You will eventually get two 106 | identical datagrams, where each has been decoded by its own methods. If you really want avoiding duplicates, then start 107 | rtl_wmbus with "-r 0" or "-t 0" argument to prevent executing of run length or time2 method respectively. 108 | You can play with arguments and check which method performs better for you. Please note, that both methods are active by default. 109 | 110 | The advantage of the run length algorithm is that it works without IIR filter (which are needed only for clock recovery by time2 method). Therefore the run length algorithm can be applied to all RF ICs providing raw _demodulated_ signal without clock in the "transparent serial mode" like TI CC1125 (swru295e.pdf, 8.7.2) does. 111 | 112 | An additional method introduces more calculation steps, so I'm not sure if Raspberry Pi 1 will still work with that. 113 | 114 | Run length algorithm works well with a few mode C1 devices I had around me, but can still be improved with your help. 115 | 116 | S1 mode datagrams can now be received! You have to start rtl_wmbus at 868.3MHz with 117 | * rtl_sdr -f 868.3M -s 1600000 - 2>/dev/null | build/rtl_wmbus 118 | 119 | Last but not least, you can try to receive all datagrams (S1, T1, C1) _simultaneously_: 120 | * rtl_sdr -f 868.625M -s 1600000 - 2>/dev/null | build/rtl_wmbus -s 121 | 122 | Notice in the last line: 123 | * "-s": which is needed to inform rtl_wmbus about required frequency translation 124 | * "868.625M": the new frequency to receive at 125 | 126 | rtl_wmbus will then shift all frequencies 127 | * by +325kHz to new center frequency at 868.95Mhz (T1 and C1) 128 | * by -325kHz to new center frequency at 868.3Mhz (S1) 129 | 130 | I have tested this so far and can confirm that it works for T1/C1 and S1. Thanks to alalons for providing me with bitstreams! 131 | 132 | Optimization on frequencies translation by rearranging compute steps implemented as proposed by alalons. 133 | 134 | Alalons (have I thanked you already?!) proposed a speed optimized arctan function. Performance gain is notable (factor ~2) but could reduce sensitivity slightly. I have seen that on receiving C1 mode datagrams - that's why the speed optimized version is not in use by default. A speed optimized arctan version can be activated by "-a" in the program options. 135 | 136 | A new options "-o", which means "remove dc offset", was introduced. The overall sensitivity is better _without_ removing dc offset, so the user may try to start rtl_wmbus with this option if no datagrams comes: 137 | * cat samples/rtlsdr_868.950M_1M6_issue47.cu8 | build/rtl_wmbus -o 138 | * cat samples/rtlsdr_868.950M_1M6_issue49.cu8 | build/rtl_wmbus -o 139 | * cat samples/rtlsdr_868.625M_2M4_issue48.cu8 | build/rtl_wmbus -d 3 -s -o 140 | 141 | License 142 | ------- 143 | 144 | Copyright (c) 2024 145 | 146 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions 147 | are met: 148 | 149 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 150 | 151 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 152 | 153 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 154 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 155 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 156 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 157 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 158 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 159 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 160 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 161 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 162 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 163 | SUCH DAMAGE. 164 | -------------------------------------------------------------------------------- /androidbuild.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem SETTINGS 4 | set SDK_ROOT=c:\Android\sdk 5 | set NDK_ROOT=c:\Android\android-ndk-r10e 6 | set SYSROOT=%NDK_ROOT%\platforms\android-21\arch-arm 7 | set CC=%NDK_ROOT%\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin\arm-linux-androideabi-gcc --sysroot=%SYSROOT% 8 | 9 | rem BUILD 10 | rd /S /Q Android 11 | md Android 12 | %CC% -o Android/rtl_wmbus rtl_wmbus.c -std=gnu99 -Iinclude -Wall -O3 -g0 -lm 13 | 14 | rem INSTALL 15 | %SDK_ROOT%\platform-tools\adb push Android\rtl_wmbus /mnt/sdcard/wmbus 16 | 17 | -------------------------------------------------------------------------------- /atan2.h: -------------------------------------------------------------------------------- 1 | #ifndef ATAN2_H 2 | #define ATAN2_H 3 | 4 | #include 5 | 6 | 7 | static inline float atan2_libm(float complex y) 8 | { 9 | return cargf(y) * (float)M_1_PI; 10 | } 11 | 12 | 13 | /** https://gist.github.com/volkansalma/2972237 */ 14 | static inline float atan2_approximation(float complex s) 15 | { 16 | static const float ONEQTR_PI = M_PI / 4.0; 17 | static const float THRQTR_PI = 3.0 * M_PI / 4.0; 18 | 19 | float y = cimagf(s), x = crealf(s); 20 | float r, angle; 21 | float abs_y = fabs(y) + 1e-10f; // kludge to prevent 0/0 condition 22 | 23 | if (x < 0.0f) 24 | { 25 | r = (x + abs_y) / (abs_y - x); 26 | angle = THRQTR_PI; 27 | } 28 | else 29 | { 30 | r = (x - abs_y) / (x + abs_y); 31 | angle = ONEQTR_PI; 32 | } 33 | 34 | angle += (0.1963f*(float)M_1_PI * r * r - 0.9817f*(float)M_1_PI) * r; 35 | 36 | if (y < 0.0f) 37 | angle = -angle; // negate if in quad III or IV 38 | 39 | return angle; 40 | } 41 | 42 | 43 | /** https://gist.github.com/volkansalma/2972237 */ 44 | static inline float atan2_approximation2(float complex s) 45 | { 46 | float y = cimagf(s), x = crealf(s); 47 | 48 | if (x == 0.0f) 49 | { 50 | if (y > 0.0f) return 0.5f; 51 | if (y == 0.0f) return 0.0f; 52 | return -0.5f; 53 | } 54 | 55 | float atan; 56 | const float z = y / x; 57 | 58 | if (fabs(z) < 1.0f) 59 | { 60 | atan = z / (1.0f*(float)M_PI + 0.28086f*(float)M_PI*z*z); 61 | if (x < 0.0f) 62 | { 63 | if (y < 0.0f) return atan - 1.0f; 64 | return atan + 1.0f; 65 | } 66 | } 67 | else 68 | { 69 | atan = 0.5f - z / (z*z + 0.28086f) * (float)M_1_PI; 70 | if ( y < 0.0f ) return atan - 1.0f; 71 | } 72 | 73 | return atan; 74 | } 75 | 76 | 77 | #endif /* ATAN2_H */ 78 | -------------------------------------------------------------------------------- /build-deb.sh: -------------------------------------------------------------------------------- 1 | debuild -i -us -uc -b 2 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | rtl-wmbus (0.0-1) unstable; urgency=medium 2 | 3 | * Initial release (Closes: #998720) 4 | 5 | -- Petter Reinholdtsen Thu, 28 Apr 2022 07:17:49 +0200 6 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: rtl-wmbus 2 | Section: unknown 3 | Priority: optional 4 | Maintainer: Petter Reinholdtsen 5 | Build-Depends: debhelper-compat (= 11) 6 | Standards-Version: 4.5.1 7 | Homepage: https://github.com/xaelsouth/rtl-wmbus 8 | Vcs-Browser: https://github.com/xaelsouth/rtl-wmbus 9 | Vcs-Git: https://github.com/xaelsouth/rtl-wmbus.git 10 | Rules-Requires-Root: no 11 | 12 | Package: rtl-wmbus 13 | Architecture: any 14 | Depends: ${shlibs:Depends}, ${misc:Depends} 15 | , rtl-sdr 16 | Description: software defined receiver for Wireless-M-Bus with RTL-SDR 17 | Allow for receiving meter data via radio according to an European 18 | standard for remote reading of smart meters. 19 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: rtl-wmbus 3 | Upstream-Contact: xael.south@yandex.com 4 | Source: https://github.com/xaelsouth/rtl-wmbus 5 | 6 | Files: * 7 | Copyright: 2010-2012 Ivan Voras 8 | 2012 Tim Hartrick 9 | 2017-2021 10 | License: BSD-2-Clause 11 | 12 | Files: debian/* 13 | Copyright: 2022 Petter Reinholdtsen 14 | License: BSD-2-Clause 15 | 16 | License: BSD-2-Clause 17 | Redistribution and use in source and binary forms, with or without 18 | modification, are permitted provided that the following conditions 19 | are met: 20 | 1. Redistributions of source code must retain the above copyright 21 | notice, this list of conditions and the following disclaimer. 22 | 2. Redistributions in binary form must reproduce the above copyright 23 | notice, this list of conditions and the following disclaimer in the 24 | documentation and/or other materials provided with the distribution. 25 | . 26 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 27 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 29 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 30 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 32 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 33 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 34 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 35 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 36 | SUCH DAMAGE. 37 | -------------------------------------------------------------------------------- /debian/patches/1000-build-install-deb.patch: -------------------------------------------------------------------------------- 1 | Description: Adjust build system to work with Debian packagin 2 | Author: Petter Reinholdtsen 3 | Forwarded: https://github.com/xaelsouth/rtl-wmbus/pull/33 4 | Reviewed-By: Petter Reinholdtsen 5 | Last-Update: 2022-04-28 6 | 7 | --- rtl-wmbus-0.0.orig/Makefile 8 | +++ rtl-wmbus-0.0/Makefile 9 | @@ -5,7 +5,7 @@ STRIP=strip 10 | 11 | OUTDIR?=build 12 | OUTFILE="$(OUTDIR)/rtl_wmbus" 13 | -CFLAGS?=-Iinclude -std=gnu99 14 | +CFLAGS+=-Iinclude -std=gnu99 15 | CFLAGS_WARNINGS?=-Wall -W -Waggregate-return -Wbad-function-cast -Wcast-align -Wcast-qual -Wchar-subscripts -Wcomment -Wno-float-equal -Winline -Wmain -Wmissing-noreturn -Wno-missing-prototypes -Wparentheses -Wpointer-arith -Wredundant-decls -Wreturn-type -Wshadow -Wsign-compare -Wstrict-prototypes -Wswitch -Wunreachable-code -Wno-unused -Wuninitialized 16 | LIB?=-lm 17 | SRC=rtl_wmbus.c 18 | @@ -62,7 +62,8 @@ pi1: 19 | rebuild: clean all 20 | 21 | install: release 22 | - cp -f $(OUTFILE) /usr/bin 23 | + install -d $(DESTDIR)/usr/bin 24 | + install $(OUTFILE) $(DESTDIR)/usr/bin 25 | 26 | clean: 27 | $(RM) -rf "$(OUTDIR)" 28 | -------------------------------------------------------------------------------- /debian/patches/README: -------------------------------------------------------------------------------- 1 | 0xxx: Grabbed from upstream development. 2 | 1xxx: Possibly relevant for upstream adoption. 3 | 2xxx: Only relevant for official Debian release. 4 | -------------------------------------------------------------------------------- /debian/patches/series: -------------------------------------------------------------------------------- 1 | 1000-build-install-deb.patch 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # See debhelper(7) (uncomment to enable) 3 | # output every command that modifies files on the build system. 4 | #export DH_VERBOSE = 1 5 | 6 | # see FEATURE AREAS in dpkg-buildflags(1) 7 | export DEB_BUILD_MAINT_OPTIONS = hardening=+all 8 | 9 | # see ENVIRONMENT in dpkg-buildflags(1) 10 | # package maintainers to append CFLAGS 11 | export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic 12 | # package maintainers to append LDFLAGS 13 | export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed 14 | 15 | %: 16 | dh $@ 17 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /debian/watch: -------------------------------------------------------------------------------- 1 | # See uscan(1) for format 2 | 3 | # Compulsory line, this is a version 4 file 4 | version=4 5 | 6 | opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%rtl-wmbus-$1.tar.gz%" \ 7 | https://github.com/xaelsouth/rtl-wmbus/tags \ 8 | (?:.*?/)?v?(\d[\d.]*)\.tar\.gz debian uupdate 9 | -------------------------------------------------------------------------------- /filter/bp_iir_cheb1_1600kHz_90kHz_98kHz_102kHz_110kHz.m: -------------------------------------------------------------------------------- 1 | clear all; 2 | 3 | samplerate = 1600e3; 4 | nyqistrate = samplerate/2; 5 | 6 | Ws1 = 90e3/nyqistrate; 7 | Wp1 = 98e3/nyqistrate; 8 | Wp2 = 102e3/nyqistrate; 9 | Ws2 = 110e3/nyqistrate; 10 | Rp = 1; 11 | Rs = 40; 12 | 13 | % seems to be the best 14 | [n, Wc] = cheb1ord([Wp1, Wp2], [Ws1, Ws2], Rp, Rs); 15 | [b, a] = cheby1(n, Rp, Wc); 16 | 17 | % does not work at all 18 | %[n, Wc] = cheb2ord([Wp1, Wp2], [Ws1, Ws2], Rp, Rs); 19 | %[b, a] = cheby2(n, Rp, Wc); 20 | 21 | % performs badly 22 | %[n, Wc] = ellipord([Wp1, Wp2], [Ws1, Ws2], Rp, Rs); 23 | %[b, a] = ellip(n, Rp, Rs, Wc); 24 | 25 | % big filter order - unpracticable 26 | %[n, Wc] = buttord([Wp1, Wp2], [Ws1, Ws2], Rp, Rs); 27 | %[b, a] = butter(n, Wc); 28 | 29 | print_iir_filter_coef(b ,a); 30 | -------------------------------------------------------------------------------- /filter/bp_iir_cheb1_800kHz_22kHz_30kHz_34kHz_42kHz.m: -------------------------------------------------------------------------------- 1 | clear all; 2 | 3 | samplerate = 800e3; 4 | nyqistrate = samplerate/2; 5 | 6 | Ws1 = 22e3/nyqistrate; 7 | Wp1 = 30e3/nyqistrate; 8 | Wp2 = 34e3/nyqistrate; 9 | Ws2 = 42e3/nyqistrate; 10 | Rp = 1; 11 | Rs = 40; 12 | 13 | % seems to be the best 14 | [n, Wc] = cheb1ord([Wp1, Wp2], [Ws1, Ws2], Rp, Rs); 15 | [b, a] = cheby1(n, Rp, Wc); 16 | 17 | % does not work at all 18 | %[n, Wc] = cheb2ord([Wp1, Wp2], [Ws1, Ws2], Rp, Rs); 19 | %[b, a] = cheby2(n, Rp, Wc); 20 | 21 | % performs badly 22 | %[n, Wc] = ellipord([Wp1, Wp2], [Ws1, Ws2], Rp, Rs); 23 | %[b, a] = ellip(n, Rp, Rs, Wc); 24 | 25 | % big filter order - unpracticable 26 | %[n, Wc] = buttord([Wp1, Wp2], [Ws1, Ws2], Rp, Rs); 27 | %[b, a] = butter(n, Wc); 28 | 29 | print_iir_filter_coef(b ,a); 30 | -------------------------------------------------------------------------------- /filter/bp_iir_cheb1_800kHz_90kHz_98Hz_102kHz_110kHz.m: -------------------------------------------------------------------------------- 1 | clear all; 2 | 3 | samplerate = 800e3; 4 | nyqistrate = samplerate/2; 5 | 6 | Ws1 = 90e3/nyqistrate; 7 | Wp1 = 98e3/nyqistrate; 8 | Wp2 = 102e3/nyqistrate; 9 | Ws2 = 110e3/nyqistrate; 10 | Rp = 1; 11 | Rs = 40; 12 | 13 | % seems to be the best 14 | [n, Wc] = cheb1ord([Wp1, Wp2], [Ws1, Ws2], Rp, Rs); 15 | [b, a] = cheby1(n, Rp, Wc); 16 | 17 | % does not work at all 18 | %[n, Wc] = cheb2ord([Wp1, Wp2], [Ws1, Ws2], Rp, Rs); 19 | %[b, a] = cheby2(n, Rp, Wc); 20 | 21 | % performs badly 22 | %[n, Wc] = ellipord([Wp1, Wp2], [Ws1, Ws2], Rp, Rs); 23 | %[b, a] = ellip(n, Rp, Rs, Wc); 24 | 25 | % big filter order - unpracticable 26 | %[n, Wc] = buttord([Wp1, Wp2], [Ws1, Ws2], Rp, Rs); 27 | %[b, a] = butter(n, Wc); 28 | 29 | print_iir_filter_coef(b ,a); 30 | -------------------------------------------------------------------------------- /filter/lp_fir_butter_1600kHz_160kHz_200kHz.m: -------------------------------------------------------------------------------- 1 | clear all; 2 | 3 | samplerate = 1600e3; 4 | nyqistrate = samplerate/2; 5 | 6 | Wp1 = 160e3/nyqistrate; 7 | Ws1 = 200e3/nyqistrate; 8 | Rp = 1; 9 | Rs = 40; 10 | 11 | [n, Wc] = buttord(Wp1, Ws1, Rp, Rs); 12 | [b] = fir1(n, Wc); 13 | 14 | print_fir_filter_coef(b); 15 | -------------------------------------------------------------------------------- /filter/lp_fir_butter_800kHz_100kHz_160kHz.m: -------------------------------------------------------------------------------- 1 | clear all; 2 | 3 | samplerate = 800e3; 4 | nyqistrate = samplerate/2; 5 | 6 | Wp1 = 100e3/nyqistrate; 7 | Ws1 = 160e3/nyqistrate; 8 | Rp = 1; 9 | Rs = 40; 10 | 11 | [n, Wc] = buttord(Wp1, Ws1, Rp, Rs); 12 | [b] = fir1(n, Wc); 13 | 14 | print_fir_filter_coef(b); 15 | 16 | -------------------------------------------------------------------------------- /filter/lp_fir_butter_800kHz_32kHz_36kHz.m: -------------------------------------------------------------------------------- 1 | clear all; 2 | 3 | samplerate = 800e3; 4 | nyqistrate = samplerate/2; 5 | 6 | Wp1 = 32e3/nyqistrate; 7 | Ws1 = 36e3/nyqistrate; 8 | Rp = 1; 9 | Rs = 40; 10 | 11 | [n, Wc] = buttord(Wp1, Ws1, Rp, Rs); 12 | [b] = fir1(n, Wc); 13 | 14 | print_fir_filter_coef(b); 15 | 16 | -------------------------------------------------------------------------------- /filter/lp_ppf_butter_1600kHz_160kHz_200kHz.m: -------------------------------------------------------------------------------- 1 | clear all; 2 | 3 | samplerate = 1600e3; 4 | nyqistrate = samplerate/2; 5 | 6 | Wp1 = 160e3/nyqistrate; 7 | Ws1 = 200e3/nyqistrate; 8 | Rp = 1; 9 | Rs = 40; 10 | 11 | [n, Wc] = buttord(Wp1, Ws1, Rp, Rs); 12 | [b] = fir1(n, Wc); 13 | 14 | x = 1:22; 15 | y = filter(b,1,x); 16 | y = y(2:2:end); 17 | 18 | phase_channels = 2; 19 | b_poly = buffer(b, phase_channels); 20 | y1 = filter(b_poly(2,:), 1, x(1:2:end)); 21 | y2 = filter(b_poly(1,:), 1, x(2:2:end)); 22 | 23 | y_poly = y1 + y2; 24 | 25 | y_err = y - y_poly; 26 | 27 | print_ppf_filter_coef(b, phase_channels); 28 | -------------------------------------------------------------------------------- /filter/print_fir_filter_coef.m: -------------------------------------------------------------------------------- 1 | function [] = print_fir_filter_coef(b); 2 | 3 | [R, C] = size(b); 4 | 5 | fprintf(stdout, "#define COEFFS %u\n", C); 6 | 7 | fprintf(stdout, "static const float b[COEFFS] = {"); 8 | for c = 1:C, 9 | fprintf(stdout, "%.10g, ", b(c)); 10 | end 11 | fprintf(stdout, "};\n"); 12 | 13 | fprintf(stdout, "#undef COEFFS\n"); 14 | 15 | -------------------------------------------------------------------------------- /filter/print_iir_filter_coef.m: -------------------------------------------------------------------------------- 1 | function [] = print_iir_filter_coef(b, a); 2 | 3 | [s, g] = tf2sos(b, a); 4 | 5 | [R, C] = size(s); 6 | 7 | fprintf(stdout, "#define GAIN %.10g\n", g); 8 | fprintf(stdout, "#define SECTIONS %u\n", R); 9 | 10 | fprintf(stdout, "static const float b[3*SECTIONS] = {"); 11 | for r = 1:R, 12 | for c = 1:C/2, 13 | fprintf(stdout, "%.10g, ", s(r,c)); 14 | end 15 | end 16 | fprintf(stdout, "};\n"); 17 | 18 | fprintf(stdout, "static const float a[3*SECTIONS] = {"); 19 | for r = 1:R, 20 | for c = C/2+1:C, 21 | fprintf(stdout, "%.10g, ", s(r,c)); 22 | end 23 | end 24 | fprintf(stdout, "};\n"); 25 | 26 | fprintf(stdout, "#undef SECTIONS\n"); 27 | fprintf(stdout, "#undef GAIN\n"); 28 | 29 | -------------------------------------------------------------------------------- /filter/print_ppf_filter_coef.m: -------------------------------------------------------------------------------- 1 | function [] = print_ppf_filter_coef(b, phase_channels); 2 | 3 | b_poly = buffer(b, phase_channels); 4 | 5 | [R, C] = size(b_poly); 6 | 7 | fprintf(stdout, "#define PHASES %u\n", R); 8 | fprintf(stdout, "#define COEFFS %u\n", C); 9 | 10 | fprintf(stdout, "static const float b[PHASES][COEFFS] = {"); 11 | for r = 1:R, 12 | fprintf("\n\t{"); 13 | for c = 1:C, 14 | fprintf(stdout, "%.10g, ", b_poly(r,c)); 15 | end 16 | fprintf("},"); 17 | end 18 | fprintf(stdout, "};\n"); 19 | 20 | fprintf(stdout, "#undef COEFFS\n"); 21 | fprintf(stdout, "#undef PHASES\n"); 22 | -------------------------------------------------------------------------------- /filter/rtl_wmbus.m: -------------------------------------------------------------------------------- 1 | clear all; 2 | 3 | Fs = 1600e3; % samplerate 4 | t = 0:Fs-1; % seconds 5 | t = t./Fs; 6 | f = -Fs/2:Fs/2-1; 7 | 8 | fid = fopen("../samples.bin"); 9 | samples = fread(fid, size=Fs*2, precision="uint8"); % *2: i,q - samples interleaved 10 | fclose(fid); 11 | 12 | samples = samples .- 127; 13 | signal = samples(1:2:end) .+ samples(2:2:end) .* j; 14 | 15 | Wp1 = 160e3/(Fs/2); 16 | Ws1 = 200e3/(Fs/2); 17 | Rp = 1; 18 | Rs = 40; 19 | 20 | [n, Wc] = buttord(Wp1, Ws1, Rp, Rs); 21 | [b] = fir1(n, Wc); 22 | 23 | b_notch = [1,-1]; 24 | a_notch = [1 , -0.98]; 25 | %freqz(b_notch, a_notch); 26 | %signal = filtfilt(b_notch, a_notch, signal); % filter dc offset 27 | 28 | filtered_signal = filter(b, 1, signal); % low-pass 29 | 30 | conj_filtered_signal = [conj(filtered_signal(2:end)); 0]; 31 | demodulated_signal = arg(filtered_signal .* conj_filtered_signal)/pi; 32 | demodulated_signal2 = demodulated_signal.^2; 33 | 34 | 35 | fid = fopen("../demod.bin", "w"); 36 | samples = fwrite(fid, demodulated_signal./max(abs(demodulated_signal)).*32767, precision="int16"); 37 | fclose(fid); 38 | 39 | 40 | %%%%%%%%% 41 | 42 | 43 | Ws1 = 90e3/(Fs/2); 44 | Wp1 = 98e3/(Fs/2); 45 | Wp2 = 102e3/(Fs/2); 46 | Ws2 = 110e3/(Fs/2); 47 | Rp = 1; 48 | Rs = 40; 49 | 50 | [n, Wc] = cheb1ord([Wp1, Wp2], [Ws1, Ws2], Rp, Rs); 51 | [b, a] = cheby1(n, Rp, Wc); 52 | 53 | takt = filter(b,a,demodulated_signal2); % band-pass 54 | 55 | fft_signal = fft(signal); 56 | fft_filtered_signal = fft(filtered_signal); 57 | fft_demodulated_signal = fft(demodulated_signal); 58 | fft_demodulated_signal2 = fft(demodulated_signal2); 59 | fft_takt = fft(takt); 60 | 61 | u = abs(fftshift(fft_signal)); 62 | v = abs(fftshift(fft_filtered_signal)); 63 | w = abs(fftshift(fft_demodulated_signal)); 64 | x = abs(fftshift(fft_demodulated_signal2)); 65 | y = abs(fftshift(fft_takt)); 66 | 67 | plot(f, [u, v]); 68 | title('Amplitude Spectrum') 69 | xlabel('f (Hz)') 70 | ylabel('|signal|') 71 | grid on; 72 | 73 | -------------------------------------------------------------------------------- /fir.h: -------------------------------------------------------------------------------- 1 | #ifndef FIR_H 2 | #define FIR_H 3 | 4 | /*- 5 | * Copyright (c) 2017 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | /* 30 | * Floating and fixed point implementations of Finite Response Filter. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | 37 | typedef struct 38 | { 39 | const size_t length; 40 | float *const b; 41 | 42 | size_t i; 43 | float *hist; 44 | } FIRF_FILTER; 45 | 46 | float firf(float sample, FIRF_FILTER *filter); 47 | 48 | float firf(float sample, FIRF_FILTER *filter) 49 | { 50 | const float *b = filter->b; 51 | float *hist = &filter->hist[filter->i++]; 52 | size_t i; 53 | 54 | *hist = sample; 55 | 56 | sample = 0; // will be "y" 57 | 58 | for (i = filter->i; i--;) 59 | { 60 | sample += *b++ * *hist--; 61 | } 62 | 63 | hist = &filter->hist[filter->length-1]; 64 | for (i = filter->length; i-- > filter->i;) 65 | { 66 | sample += *b++ * *hist--; 67 | } 68 | 69 | if (filter->i >= filter->length) filter->i = 0; 70 | 71 | return sample; 72 | } 73 | 74 | void firf_lms(float mu_e, FIRF_FILTER *filter) 75 | { 76 | float *b = &filter->b[filter->length-1]; 77 | size_t i; 78 | float *hist; 79 | 80 | i = filter->i; 81 | hist = &filter->hist[i]; 82 | while (i++ < filter->length) 83 | { 84 | *b-- += mu_e * *hist++; 85 | } 86 | 87 | i = 0; 88 | hist = &filter->hist[i]; 89 | while (i++ < filter->i) 90 | { 91 | *b-- += mu_e * *hist++; 92 | } 93 | } 94 | 95 | typedef struct 96 | { 97 | const size_t length; 98 | const fixedpt *const b; 99 | 100 | size_t i; 101 | fixedpt *hist; 102 | } FIRFP_FILTER; 103 | 104 | fixedpt firfp(fixedpt sample, FIRFP_FILTER *filter); 105 | 106 | fixedpt firfp(fixedpt sample, FIRFP_FILTER *filter) 107 | { 108 | const fixedpt *b = filter->b; 109 | fixedpt *hist = &filter->hist[filter->i++]; 110 | size_t i; 111 | 112 | *hist = sample; 113 | 114 | sample = 0; // will be "y" 115 | 116 | for (i = filter->i; i--;) 117 | { 118 | sample = fixedpt_add(sample, fixedpt_mul((*b++), (*hist--))); 119 | } 120 | 121 | hist = &filter->hist[filter->length-1]; 122 | for (i = filter->length; i-- > filter->i;) 123 | { 124 | sample = fixedpt_add(sample, fixedpt_mul((*b++), (*hist--))); 125 | } 126 | 127 | if (filter->i >= filter->length) filter->i = 0; 128 | 129 | return sample; 130 | } 131 | 132 | #endif /* FIR_H */ 133 | 134 | -------------------------------------------------------------------------------- /iir.h: -------------------------------------------------------------------------------- 1 | #ifndef IIR_H 2 | #define IIR_H 3 | 4 | /*- 5 | * Copyright (c) 2017 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | /* 30 | * Implementation of Infinite Response Filter. 31 | */ 32 | 33 | #include 34 | #include 35 | 36 | typedef struct 37 | { 38 | const size_t sections; 39 | 40 | const float gain; 41 | const float *const b; // 3 coeff 42 | const float *const a; // 3 coeff, first of these is assumed to be 1 43 | 44 | float *hist; // 3 taps 45 | } IIRF_FILTER; 46 | 47 | float iirf(float sample, IIRF_FILTER *filter); 48 | 49 | float iirf(float sample, IIRF_FILTER *filter) 50 | { 51 | float *hist; 52 | const float *a, *b; 53 | size_t i; 54 | 55 | // sample will be "y" 56 | 57 | for (i = 0; i < filter->sections; i++) 58 | { 59 | a = filter->a + 3 * i; 60 | b = filter->b + 3 * i; 61 | hist = filter->hist + 3 * i; 62 | 63 | #if 0 64 | hist[0] = -sample; 65 | hist[0] = -(a[0]*hist[0] + a[1]*hist[1] + a[2]*hist[2]); 66 | #else 67 | hist[0] = sample - (a[1]*hist[1] + a[2]*hist[2]); 68 | #endif 69 | sample = b[0]*hist[0] + b[1]*hist[1] + b[2]*hist[2]; 70 | hist[2] = hist[1]; 71 | hist[1] = hist[0]; 72 | } 73 | 74 | sample *= filter->gain; 75 | 76 | return sample; 77 | } 78 | 79 | #endif /* IIR_H */ 80 | 81 | -------------------------------------------------------------------------------- /include/fixedptc/.hgtags: -------------------------------------------------------------------------------- 1 | 354515b14610968c922f4812eaf42c2218329a67 fixedptc-andromeda 2 | -------------------------------------------------------------------------------- /include/fixedptc/Makefile: -------------------------------------------------------------------------------- 1 | all: test verify_32 verify_64 2 | true 3 | 4 | test: test.c fixedptc.h 5 | gcc -o test -O3 -Wall test.c 6 | 7 | verify_32: verify.c fixedptc.h 8 | gcc -o verify_32 -O3 -Wall -DFIXEDPT_BITS=32 -lm verify.c 9 | 10 | verify_64: verify.c fixedptc.h 11 | gcc -o verify_64 -O3 -Wall -DFIXEDPT_BITS=64 -lm verify.c 12 | -------------------------------------------------------------------------------- /include/fixedptc/README.txt: -------------------------------------------------------------------------------- 1 | fixedptc library - a simple fixed point math header library for C. 2 | Copyright (c) 2010-2012. Ivan Voras 3 | Released under the BSDL. 4 | 5 | fixedptc is intended to be simple to use and integrate in other simple 6 | programs, thus is it implemented as a C header library. However, as 7 | functions in this mode of operation are all inlined, it can result in a 8 | significant increase in code size for the final executable. If the complex 9 | functions are used often in the end-program, the library should be 10 | refactored into a "normal" linkable object library. 11 | -------------------------------------------------------------------------------- /include/fixedptc/fixedptc.h: -------------------------------------------------------------------------------- 1 | #ifndef _FIXEDPTC_H_ 2 | #define _FIXEDPTC_H_ 3 | 4 | /* 5 | * fixedptc.h is a 32-bit or 64-bit fixed point numeric library. 6 | * 7 | * The symbol FIXEDPT_BITS, if defined before this library header file 8 | * is included, determines the number of bits in the data type (its "width"). 9 | * The default width is 32-bit (FIXEDPT_BITS=32) and it can be used 10 | * on any recent C99 compiler. The 64-bit precision (FIXEDPT_BITS=64) is 11 | * available on compilers which implement 128-bit "long long" types. This 12 | * precision has been tested on GCC 4.2+. 13 | * 14 | * The FIXEDPT_WBITS symbols governs how many bits are dedicated to the 15 | * "whole" part of the number (to the left of the decimal point). The larger 16 | * this width is, the larger the numbers which can be stored in the fixedpt 17 | * number. The rest of the bits (available in the FIXEDPT_FBITS symbol) are 18 | * dedicated to the fraction part of the number (to the right of the decimal 19 | * point). 20 | * 21 | * Since the number of bits in both cases is relatively low, many complex 22 | * functions (more complex than div & mul) take a large hit on the precision 23 | * of the end result because errors in precision accumulate. 24 | * This loss of precision can be lessened by increasing the number of 25 | * bits dedicated to the fraction part, but at the loss of range. 26 | * 27 | * Adventurous users might utilize this library to build two data types: 28 | * one which has the range, and one which has the precision, and carefully 29 | * convert between them (including adding two number of each type to produce 30 | * a simulated type with a larger range and precision). 31 | * 32 | * The ideas and algorithms have been cherry-picked from a large number 33 | * of previous implementations available on the Internet. 34 | * Tim Hartrick has contributed cleanup and 64-bit support patches. 35 | * 36 | * == Special notes for the 32-bit precision == 37 | * Signed 32-bit fixed point numeric library for the 24.8 format. 38 | * The specific limits are -8388608.999... to 8388607.999... and the 39 | * most precise number is 0.00390625. In practice, you should not count 40 | * on working with numbers larger than a million or to the precision 41 | * of more than 2 decimal places. Make peace with the fact that PI 42 | * is 3.14 here. :) 43 | */ 44 | 45 | /*- 46 | * Copyright (c) 2010-2012 Ivan Voras 47 | * Copyright (c) 2012 Tim Hartrick 48 | * 49 | * Redistribution and use in source and binary forms, with or without 50 | * modification, are permitted provided that the following conditions 51 | * are met: 52 | * 1. Redistributions of source code must retain the above copyright 53 | * notice, this list of conditions and the following disclaimer. 54 | * 2. Redistributions in binary form must reproduce the above copyright 55 | * notice, this list of conditions and the following disclaimer in the 56 | * documentation and/or other materials provided with the distribution. 57 | * 58 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 59 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 60 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 61 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 62 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 63 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 64 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 65 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 66 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 67 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 68 | * SUCH DAMAGE. 69 | */ 70 | 71 | #ifndef FIXEDPT_BITS 72 | #define FIXEDPT_BITS 32 73 | #endif 74 | 75 | #include 76 | 77 | #if FIXEDPT_BITS == 32 78 | typedef int32_t fixedpt; 79 | typedef int64_t fixedptd; 80 | typedef uint32_t fixedptu; 81 | typedef uint64_t fixedptud; 82 | #elif FIXEDPT_BITS == 64 83 | typedef int64_t fixedpt; 84 | typedef __int128_t fixedptd; 85 | typedef uint64_t fixedptu; 86 | typedef __uint128_t fixedptud; 87 | #else 88 | #error "FIXEDPT_BITS must be equal to 32 or 64" 89 | #endif 90 | 91 | #ifndef FIXEDPT_WBITS 92 | #define FIXEDPT_WBITS 24 93 | #endif 94 | 95 | #if FIXEDPT_WBITS >= FIXEDPT_BITS 96 | #error "FIXEDPT_WBITS must be less than or equal to FIXEDPT_BITS" 97 | #endif 98 | 99 | #define FIXEDPT_VCSID "$Id$" 100 | 101 | #define FIXEDPT_FBITS (FIXEDPT_BITS - FIXEDPT_WBITS) 102 | #define FIXEDPT_FMASK (((fixedpt)1 << FIXEDPT_FBITS) - 1) 103 | 104 | #define fixedpt_rconst(R) ((fixedpt)((R) * FIXEDPT_ONE + ((R) >= 0 ? 0.5 : -0.5))) 105 | #define fixedpt_fromint(I) ((fixedptd)(I) << FIXEDPT_FBITS) 106 | #define fixedpt_toint(F) ((F) >> FIXEDPT_FBITS) 107 | #define fixedpt_add(A,B) ((A) + (B)) 108 | #define fixedpt_sub(A,B) ((A) - (B)) 109 | #define fixedpt_xmul(A,B) \ 110 | ((fixedpt)(((fixedptd)(A) * (fixedptd)(B)) >> FIXEDPT_FBITS)) 111 | #define fixedpt_xdiv(A,B) \ 112 | ((fixedpt)(((fixedptd)(A) << FIXEDPT_FBITS) / (fixedptd)(B))) 113 | #define fixedpt_fracpart(A) ((fixedpt)(A) & FIXEDPT_FMASK) 114 | 115 | #define FIXEDPT_ONE ((fixedpt)((fixedpt)1 << FIXEDPT_FBITS)) 116 | #define FIXEDPT_ONE_HALF (FIXEDPT_ONE >> 1) 117 | #define FIXEDPT_TWO (FIXEDPT_ONE + FIXEDPT_ONE) 118 | #define FIXEDPT_PI fixedpt_rconst(3.14159265358979323846) 119 | #define FIXEDPT_TWO_PI fixedpt_rconst(2 * 3.14159265358979323846) 120 | #define FIXEDPT_HALF_PI fixedpt_rconst(3.14159265358979323846 / 2) 121 | #define FIXEDPT_E fixedpt_rconst(2.7182818284590452354) 122 | 123 | #define fixedpt_abs(A) ((A) < 0 ? -(A) : (A)) 124 | 125 | /* fixedpt is meant to be usable in environments without floating point support 126 | * (e.g. microcontrollers, kernels), so we can't use floating point types directly. 127 | * Putting them only in macros will effectively make them optional. */ 128 | #define fixedpt_tofloat(T) ((float) ((T)*((float)(1)/(float)(1 << FIXEDPT_FBITS)))) 129 | 130 | 131 | /* Multiplies two fixedpt numbers, returns the result. */ 132 | static inline fixedpt 133 | fixedpt_mul(fixedpt A, fixedpt B) 134 | { 135 | return (((fixedptd)A * (fixedptd)B) >> FIXEDPT_FBITS); 136 | } 137 | 138 | 139 | /* Divides two fixedpt numbers, returns the result. */ 140 | static inline fixedpt 141 | fixedpt_div(fixedpt A, fixedpt B) 142 | { 143 | return (((fixedptd)A << FIXEDPT_FBITS) / (fixedptd)B); 144 | } 145 | 146 | /* 147 | * Note: adding and substracting fixedpt numbers can be done by using 148 | * the regular integer operators + and -. 149 | */ 150 | 151 | /** 152 | * Convert the given fixedpt number to a decimal string. 153 | * The max_dec argument specifies how many decimal digits to the right 154 | * of the decimal point to generate. If set to -1, the "default" number 155 | * of decimal digits will be used (2 for 32-bit fixedpt width, 10 for 156 | * 64-bit fixedpt width); If set to -2, "all" of the digits will 157 | * be returned, meaning there will be invalid, bogus digits outside the 158 | * specified precisions. 159 | */ 160 | static inline void 161 | fixedpt_str(fixedpt A, char *str, int max_dec) 162 | { 163 | int ndec = 0, slen = 0; 164 | char tmp[12] = {0}; 165 | fixedptud fr, ip; 166 | const fixedptud one = (fixedptud)1 << FIXEDPT_BITS; 167 | const fixedptud mask = one - 1; 168 | 169 | if (max_dec == -1) 170 | #if FIXEDPT_BITS == 32 171 | #if FIXEDPT_WBITS > 16 172 | max_dec = 2; 173 | #else 174 | max_dec = 4; 175 | #endif 176 | #elif FIXEDPT_BITS == 64 177 | max_dec = 10; 178 | #else 179 | #error Invalid width 180 | #endif 181 | else if (max_dec == -2) 182 | max_dec = 15; 183 | 184 | if (A < 0) { 185 | str[slen++] = '-'; 186 | A *= -1; 187 | } 188 | 189 | ip = fixedpt_toint(A); 190 | do { 191 | tmp[ndec++] = '0' + ip % 10; 192 | ip /= 10; 193 | } while (ip != 0); 194 | 195 | while (ndec > 0) 196 | str[slen++] = tmp[--ndec]; 197 | str[slen++] = '.'; 198 | 199 | fr = (fixedpt_fracpart(A) << FIXEDPT_WBITS) & mask; 200 | do { 201 | fr = (fr & mask) * 10; 202 | 203 | str[slen++] = '0' + (fr >> FIXEDPT_BITS) % 10; 204 | ndec++; 205 | } while (fr != 0 && ndec < max_dec); 206 | 207 | if (ndec > 1 && str[slen-1] == '0') 208 | str[slen-1] = '\0'; /* cut off trailing 0 */ 209 | else 210 | str[slen] = '\0'; 211 | } 212 | 213 | 214 | /* Converts the given fixedpt number into a string, using a static 215 | * (non-threadsafe) string buffer */ 216 | static inline char* 217 | fixedpt_cstr(const fixedpt A, const int max_dec) 218 | { 219 | static char str[25]; 220 | 221 | fixedpt_str(A, str, max_dec); 222 | return (str); 223 | } 224 | 225 | 226 | /* Returns the square root of the given number, or -1 in case of error */ 227 | static inline fixedpt 228 | fixedpt_sqrt(fixedpt A) 229 | { 230 | int invert = 0; 231 | int iter = FIXEDPT_FBITS; 232 | int l, i; 233 | 234 | if (A < 0) 235 | return (-1); 236 | if (A == 0 || A == FIXEDPT_ONE) 237 | return (A); 238 | if (A < FIXEDPT_ONE && A > 6) { 239 | invert = 1; 240 | A = fixedpt_div(FIXEDPT_ONE, A); 241 | } 242 | if (A > FIXEDPT_ONE) { 243 | int s = A; 244 | 245 | iter = 0; 246 | while (s > 0) { 247 | s >>= 2; 248 | iter++; 249 | } 250 | } 251 | 252 | /* Newton's iterations */ 253 | l = (A >> 1) + 1; 254 | for (i = 0; i < iter; i++) 255 | l = (l + fixedpt_div(A, l)) >> 1; 256 | if (invert) 257 | return (fixedpt_div(FIXEDPT_ONE, l)); 258 | return (l); 259 | } 260 | 261 | 262 | /* Returns the sine of the given fixedpt number. 263 | * Note: the loss of precision is extraordinary! */ 264 | static inline fixedpt 265 | fixedpt_sin(fixedpt fp) 266 | { 267 | int sign = 1; 268 | fixedpt sqr, result; 269 | const fixedpt SK[2] = { 270 | fixedpt_rconst(7.61e-03), 271 | fixedpt_rconst(1.6605e-01) 272 | }; 273 | 274 | fp %= 2 * FIXEDPT_PI; 275 | if (fp < 0) 276 | fp = FIXEDPT_PI * 2 + fp; 277 | if ((fp > FIXEDPT_HALF_PI) && (fp <= FIXEDPT_PI)) 278 | fp = FIXEDPT_PI - fp; 279 | else if ((fp > FIXEDPT_PI) && (fp <= (FIXEDPT_PI + FIXEDPT_HALF_PI))) { 280 | fp = fp - FIXEDPT_PI; 281 | sign = -1; 282 | } else if (fp > (FIXEDPT_PI + FIXEDPT_HALF_PI)) { 283 | fp = (FIXEDPT_PI << 1) - fp; 284 | sign = -1; 285 | } 286 | sqr = fixedpt_mul(fp, fp); 287 | result = SK[0]; 288 | result = fixedpt_mul(result, sqr); 289 | result -= SK[1]; 290 | result = fixedpt_mul(result, sqr); 291 | result += FIXEDPT_ONE; 292 | result = fixedpt_mul(result, fp); 293 | return sign * result; 294 | } 295 | 296 | 297 | /* Returns the cosine of the given fixedpt number */ 298 | static inline fixedpt 299 | fixedpt_cos(fixedpt A) 300 | { 301 | return (fixedpt_sin(FIXEDPT_HALF_PI - A)); 302 | } 303 | 304 | 305 | /* Returns the tangens of the given fixedpt number */ 306 | static inline fixedpt 307 | fixedpt_tan(fixedpt A) 308 | { 309 | return fixedpt_div(fixedpt_sin(A), fixedpt_cos(A)); 310 | } 311 | 312 | 313 | /* Returns the value exp(x), i.e. e^x of the given fixedpt number. */ 314 | static inline fixedpt 315 | fixedpt_exp(fixedpt fp) 316 | { 317 | fixedpt xabs, k, z, R, xp; 318 | const fixedpt LN2 = fixedpt_rconst(0.69314718055994530942); 319 | const fixedpt LN2_INV = fixedpt_rconst(1.4426950408889634074); 320 | const fixedpt EXP_P[5] = { 321 | fixedpt_rconst(1.66666666666666019037e-01), 322 | fixedpt_rconst(-2.77777777770155933842e-03), 323 | fixedpt_rconst(6.61375632143793436117e-05), 324 | fixedpt_rconst(-1.65339022054652515390e-06), 325 | fixedpt_rconst(4.13813679705723846039e-08), 326 | }; 327 | 328 | if (fp == 0) 329 | return (FIXEDPT_ONE); 330 | xabs = fixedpt_abs(fp); 331 | k = fixedpt_mul(xabs, LN2_INV); 332 | k += FIXEDPT_ONE_HALF; 333 | k &= ~FIXEDPT_FMASK; 334 | if (fp < 0) 335 | k = -k; 336 | fp -= fixedpt_mul(k, LN2); 337 | z = fixedpt_mul(fp, fp); 338 | /* Taylor */ 339 | R = FIXEDPT_TWO + 340 | fixedpt_mul(z, EXP_P[0] + fixedpt_mul(z, EXP_P[1] + 341 | fixedpt_mul(z, EXP_P[2] + fixedpt_mul(z, EXP_P[3] + 342 | fixedpt_mul(z, EXP_P[4]))))); 343 | xp = FIXEDPT_ONE + fixedpt_div(fixedpt_mul(fp, FIXEDPT_TWO), R - fp); 344 | if (k < 0) 345 | k = FIXEDPT_ONE >> (-k >> FIXEDPT_FBITS); 346 | else 347 | k = FIXEDPT_ONE << (k >> FIXEDPT_FBITS); 348 | return (fixedpt_mul(k, xp)); 349 | } 350 | 351 | 352 | /* Returns the natural logarithm of the given fixedpt number. */ 353 | static inline fixedpt 354 | fixedpt_ln(fixedpt x) 355 | { 356 | fixedpt log2, xi; 357 | fixedpt f, s, z, w, R; 358 | const fixedpt LN2 = fixedpt_rconst(0.69314718055994530942); 359 | const fixedpt LG[7] = { 360 | fixedpt_rconst(6.666666666666735130e-01), 361 | fixedpt_rconst(3.999999999940941908e-01), 362 | fixedpt_rconst(2.857142874366239149e-01), 363 | fixedpt_rconst(2.222219843214978396e-01), 364 | fixedpt_rconst(1.818357216161805012e-01), 365 | fixedpt_rconst(1.531383769920937332e-01), 366 | fixedpt_rconst(1.479819860511658591e-01) 367 | }; 368 | 369 | if (x < 0) 370 | return (0); 371 | if (x == 0) 372 | return 0xffffffff; 373 | 374 | log2 = 0; 375 | xi = x; 376 | while (xi > FIXEDPT_TWO) { 377 | xi >>= 1; 378 | log2++; 379 | } 380 | f = xi - FIXEDPT_ONE; 381 | s = fixedpt_div(f, FIXEDPT_TWO + f); 382 | z = fixedpt_mul(s, s); 383 | w = fixedpt_mul(z, z); 384 | R = fixedpt_mul(w, LG[1] + fixedpt_mul(w, LG[3] 385 | + fixedpt_mul(w, LG[5]))) + fixedpt_mul(z, LG[0] 386 | + fixedpt_mul(w, LG[2] + fixedpt_mul(w, LG[4] 387 | + fixedpt_mul(w, LG[6])))); 388 | return (fixedpt_mul(LN2, (log2 << FIXEDPT_FBITS)) + f 389 | - fixedpt_mul(s, f - R)); 390 | } 391 | 392 | 393 | /* Returns the logarithm of the given base of the given fixedpt number */ 394 | static inline fixedpt 395 | fixedpt_log(fixedpt x, fixedpt base) 396 | { 397 | return (fixedpt_div(fixedpt_ln(x), fixedpt_ln(base))); 398 | } 399 | 400 | 401 | /* Return the power value (n^exp) of the given fixedpt numbers */ 402 | static inline fixedpt 403 | fixedpt_pow(fixedpt n, fixedpt exp) 404 | { 405 | if (exp == 0) 406 | return (FIXEDPT_ONE); 407 | if (n < 0) 408 | return 0; 409 | return (fixedpt_exp(fixedpt_mul(fixedpt_ln(n), exp))); 410 | } 411 | 412 | #endif 413 | -------------------------------------------------------------------------------- /include/fixedptc/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | /* This is a pure integer-only test program for fixedptc */ 6 | 7 | //#define FIXEDPT_WBITS 16 8 | 9 | 10 | #include "fixedptc.h" 11 | 12 | void 13 | fixedpt_print(fixedpt A) 14 | { 15 | char num[20]; 16 | 17 | fixedpt_str(A, num, -2); 18 | puts(num); 19 | } 20 | 21 | int main() { 22 | 23 | fixedpt A, B, C; 24 | 25 | printf("fixedptc library version: %s\n", FIXEDPT_VCSID); 26 | printf("Using %d-bit precision, %d.%d format\n\n", FIXEDPT_BITS, FIXEDPT_WBITS, FIXEDPT_FBITS); 27 | 28 | printf("The most precise number: "); 29 | fixedpt_print(1); 30 | printf("The biggest number: "); 31 | fixedpt_print(0x7fffff00); 32 | printf("Here are some example numbers:\n"); 33 | 34 | printf("Random number: "); 35 | fixedpt_print(fixedpt_rconst(143.125)); 36 | printf("PI: "); 37 | fixedpt_print(FIXEDPT_PI); 38 | printf("e: "); 39 | fixedpt_print(FIXEDPT_E); 40 | puts(""); 41 | 42 | A = fixedpt_rconst(2.5); 43 | B = fixedpt_fromint(3); 44 | 45 | fixedpt_print(A); 46 | puts("+"); 47 | fixedpt_print(B); 48 | C = fixedpt_add(A, B); 49 | puts("="); 50 | fixedpt_print(C); 51 | puts(""); 52 | 53 | fixedpt_print(A); 54 | puts("*"); 55 | fixedpt_print(B); 56 | puts("="); 57 | C = fixedpt_mul(A, B); 58 | fixedpt_print(C); 59 | puts(""); 60 | 61 | A = fixedpt_rconst(1); 62 | B = fixedpt_rconst(4); 63 | C = fixedpt_div(A, B); 64 | 65 | fixedpt_print(A); 66 | puts("/"); 67 | fixedpt_print(B); 68 | puts("="); 69 | fixedpt_print(C); 70 | 71 | printf("exp(1)="); 72 | fixedpt_print(fixedpt_exp(FIXEDPT_ONE)); 73 | 74 | puts(""); 75 | puts("sqrt(pi)="); 76 | fixedpt_print(fixedpt_sqrt(FIXEDPT_PI)); 77 | 78 | puts(""); 79 | puts("sqrt(25)="); 80 | fixedpt_print(fixedpt_sqrt(fixedpt_rconst(25))); 81 | 82 | puts(""); 83 | puts("sin(pi/2)="); 84 | fixedpt_print(fixedpt_sin(FIXEDPT_HALF_PI)); 85 | 86 | puts(""); 87 | puts("sin(3.5*pi)="); 88 | fixedpt_print(fixedpt_sin(fixedpt_mul(fixedpt_rconst(3.5), FIXEDPT_PI))); 89 | 90 | puts(""); 91 | puts("4^3.5="); 92 | fixedpt_print(fixedpt_pow(fixedpt_rconst(4), fixedpt_rconst(3.5))); 93 | 94 | puts(""); 95 | puts("4^0.5="); 96 | fixedpt_print(fixedpt_pow(fixedpt_rconst(4), fixedpt_rconst(0.5))); 97 | 98 | return (0); 99 | } 100 | -------------------------------------------------------------------------------- /include/fixedptc/verify.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | //#define FIXEDPT_WBITS 16 8 | #include "fixedptc.h" 9 | 10 | /* This test program verifies the fixedpt precision, comparing it to 11 | * float and double precision results. */ 12 | 13 | static const float pi_f = 3.14159265358979323846264338; 14 | static const double pi_d = 3.14159265358979323846264338; 15 | static const fixedpt pi_x = fixedpt_rconst(3.14159265358979323846264338); 16 | 17 | static const float e_f = 2.71828182845904523536028747; 18 | static const double e_d = 2.71828182845904523536028747; 19 | static const fixedpt e_x = fixedpt_rconst(2.71828182845904523536028747); 20 | 21 | void 22 | verify_numbers() 23 | { 24 | printf("pi as string:\t3.14159265358979323846264338\n"); 25 | printf("pi as float:\t%0.6f\n", pi_f); 26 | printf("pi as double:\t%0.15lf\n", pi_d); 27 | printf("pi as fixedpt:\t%s\n", fixedpt_cstr(pi_x, -1)); 28 | printf(" delta fixedpt-double:\t%0.10lf\n", atof(fixedpt_cstr(pi_x, -1)) - pi_d); 29 | printf("pi as fixedpt converted to float: %0.6f\n", fixedpt_tofloat(pi_x)); 30 | 31 | printf("e as string:\t2.71828182845904523536028747\n"); 32 | printf("e as float:\t%0.6f\n", e_f); 33 | printf("e as double:\t%0.15lf\n", e_d); 34 | printf("e as fixedpt:\t%s\n", fixedpt_cstr(e_x, -1)); 35 | printf(" delta fixedpt-double:\t%0.10lf\n", atof(fixedpt_cstr(e_x, -1)) - e_d); 36 | } 37 | 38 | void 39 | verify_trig() 40 | { 41 | printf("sin(pi) as float:\t%0.6f\n", sinf(pi_f)); 42 | printf("sin(pi) as double:\t%0.15lf\n", sin(pi_d)); 43 | printf("sin(pi) as fixedpt:\t%s\n", fixedpt_cstr(fixedpt_sin(pi_x), -1)); 44 | printf(" delta fixedpt-double:\t%0.10lf\n", atof(fixedpt_cstr(fixedpt_sin(pi_x), -1)) - sin(pi_d)); 45 | 46 | printf("sin(e) as float:\t%0.6f\n", sinf(e_f)); 47 | printf("sin(e) as double:\t%0.15lf\n", sin(e_d)); 48 | printf("sin(e) as fixedpt:\t%s\n", fixedpt_cstr(fixedpt_sin(e_x), -1)); 49 | printf(" delta fixedpt-double:\t%0.10lf\n", atof(fixedpt_cstr(fixedpt_sin(e_x), -1)) - sin(e_d)); 50 | 51 | printf("tan(e) as float:\t%0.6f\n", tanf(e_f)); 52 | printf("tan(e) as double:\t%0.15lf\n", tan(e_d)); 53 | printf("tan(e) as fixedpt:\t%s\n", fixedpt_cstr(fixedpt_tan(e_x), -1)); 54 | printf(" delta fixedpt-double:\t%0.10lf\n", atof(fixedpt_cstr(fixedpt_tan(e_x), -1)) - tan(e_d)); 55 | } 56 | 57 | void 58 | verify_powers() 59 | { 60 | printf("pow(pi,3) as float:\t%0.6f\n", powf(pi_f, 3)); 61 | printf("pow(pi,3) as double:\t%0.15f\n", pow(pi_d, 3)); 62 | printf("pow(pi,3) as fixedpt:\t%s\n", fixedpt_cstr(fixedpt_pow(pi_x, fixedpt_rconst(3)), -1)); 63 | printf(" delta fixedpt-double:\t%0.10lf\n", atof(fixedpt_cstr(fixedpt_pow(pi_x, fixedpt_rconst(3)), -1)) - pow(pi_d, 3)); 64 | 65 | printf("exp(3) as float:\t%0.6f\n", expf(3)); 66 | printf("exp(3) as double:\t%0.15f\n", expf(3)); 67 | printf("exp(3) as fixedpt:\t%s\n", fixedpt_cstr(fixedpt_exp(fixedpt_rconst(3)), -1)); 68 | printf(" delta fixedpt-double:\t%0.10lf\n", atof(fixedpt_cstr(fixedpt_exp(fixedpt_rconst(3)), -1)) - exp(3)); 69 | 70 | printf("ln(e) as float:\t%0.6f\n", logf(e_f)); 71 | printf("ln(e) as double:\t%0.15f\n", log(e_d)); 72 | printf("ln(e) as fixedpt:\t%s\n", fixedpt_cstr(fixedpt_ln(e_x), -1)); 73 | printf(" delta fixedpt-double:\t%0.10lf\n", atof(fixedpt_cstr(fixedpt_ln(e_x), -1)) - log(e_d)); 74 | } 75 | 76 | int 77 | main() 78 | { 79 | setlocale(LC_NUMERIC, "C"); 80 | printf("fixedptc library version: %s\n", FIXEDPT_VCSID); 81 | printf("Using %d-bit precision, %d.%d format\n\n", FIXEDPT_BITS, FIXEDPT_WBITS, FIXEDPT_FBITS); 82 | 83 | verify_numbers(); 84 | printf("\n"); 85 | verify_trig(); 86 | printf("\n"); 87 | verify_powers(); 88 | 89 | return (0); 90 | } 91 | 92 | -------------------------------------------------------------------------------- /include/mode_s_util.h: -------------------------------------------------------------------------------- 1 | #ifndef MODE_S_UTIL_H 2 | #define MODE_S_UTIL_H 3 | 4 | /*- 5 | * Copyright (c) 2024 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | #include 30 | #include 31 | 32 | #ifndef SMALL_INV_MACHESTER_ENCODING_TABLE 33 | #define SMALL_INV_MACHESTER_ENCODING_TABLE 1 34 | #endif 35 | 36 | #if SMALL_INV_MACHESTER_ENCODING_TABLE 37 | //#define USE_SMALLEST_INV_MACHESTER_ENCODING_TABLE 38 | #endif 39 | 40 | #if SMALL_INV_MACHESTER_ENCODING_TABLE 41 | #if defined(USE_SMALLEST_INV_MACHESTER_ENCODING_TABLE) 42 | static const uint8_t encoding_tab_inv_machester[2] = { 0b10, 0b01 }; 43 | #else 44 | static const uint8_t encoding_tab_inv_machester[16] = { 45 | 0xaa, 0xa9, 0xa6, 0xa5, 0x9a, 0x99, 0x96, 0x95, 0x6a, 0x69, 0x66, 0x65, 0x5a, 0x59, 0x56, 0x55 46 | }; 47 | #endif 48 | #else 49 | static const uint16_t encoding_tab_inv_machester[256] = { 50 | 0xaaaa, 0xaaa9, 0xaaa6, 0xaaa5, 0xaa9a, 0xaa99, 0xaa96, 0xaa95, 0xaa6a, 0xaa69, 0xaa66, 0xaa65, 0xaa5a, 0xaa59, 0xaa56, 0xaa55, 51 | 0xa9aa, 0xa9a9, 0xa9a6, 0xa9a5, 0xa99a, 0xa999, 0xa996, 0xa995, 0xa96a, 0xa969, 0xa966, 0xa965, 0xa95a, 0xa959, 0xa956, 0xa955, 52 | 0xa6aa, 0xa6a9, 0xa6a6, 0xa6a5, 0xa69a, 0xa699, 0xa696, 0xa695, 0xa66a, 0xa669, 0xa666, 0xa665, 0xa65a, 0xa659, 0xa656, 0xa655, 53 | 0xa5aa, 0xa5a9, 0xa5a6, 0xa5a5, 0xa59a, 0xa599, 0xa596, 0xa595, 0xa56a, 0xa569, 0xa566, 0xa565, 0xa55a, 0xa559, 0xa556, 0xa555, 54 | 0x9aaa, 0x9aa9, 0x9aa6, 0x9aa5, 0x9a9a, 0x9a99, 0x9a96, 0x9a95, 0x9a6a, 0x9a69, 0x9a66, 0x9a65, 0x9a5a, 0x9a59, 0x9a56, 0x9a55, 55 | 0x99aa, 0x99a9, 0x99a6, 0x99a5, 0x999a, 0x9999, 0x9996, 0x9995, 0x996a, 0x9969, 0x9966, 0x9965, 0x995a, 0x9959, 0x9956, 0x9955, 56 | 0x96aa, 0x96a9, 0x96a6, 0x96a5, 0x969a, 0x9699, 0x9696, 0x9695, 0x966a, 0x9669, 0x9666, 0x9665, 0x965a, 0x9659, 0x9656, 0x9655, 57 | 0x95aa, 0x95a9, 0x95a6, 0x95a5, 0x959a, 0x9599, 0x9596, 0x9595, 0x956a, 0x9569, 0x9566, 0x9565, 0x955a, 0x9559, 0x9556, 0x9555, 58 | 0x6aaa, 0x6aa9, 0x6aa6, 0x6aa5, 0x6a9a, 0x6a99, 0x6a96, 0x6a95, 0x6a6a, 0x6a69, 0x6a66, 0x6a65, 0x6a5a, 0x6a59, 0x6a56, 0x6a55, 59 | 0x69aa, 0x69a9, 0x69a6, 0x69a5, 0x699a, 0x6999, 0x6996, 0x6995, 0x696a, 0x6969, 0x6966, 0x6965, 0x695a, 0x6959, 0x6956, 0x6955, 60 | 0x66aa, 0x66a9, 0x66a6, 0x66a5, 0x669a, 0x6699, 0x6696, 0x6695, 0x666a, 0x6669, 0x6666, 0x6665, 0x665a, 0x6659, 0x6656, 0x6655, 61 | 0x65aa, 0x65a9, 0x65a6, 0x65a5, 0x659a, 0x6599, 0x6596, 0x6595, 0x656a, 0x6569, 0x6566, 0x6565, 0x655a, 0x6559, 0x6556, 0x6555, 62 | 0x5aaa, 0x5aa9, 0x5aa6, 0x5aa5, 0x5a9a, 0x5a99, 0x5a96, 0x5a95, 0x5a6a, 0x5a69, 0x5a66, 0x5a65, 0x5a5a, 0x5a59, 0x5a56, 0x5a55, 63 | 0x59aa, 0x59a9, 0x59a6, 0x59a5, 0x599a, 0x5999, 0x5996, 0x5995, 0x596a, 0x5969, 0x5966, 0x5965, 0x595a, 0x5959, 0x5956, 0x5955, 64 | 0x56aa, 0x56a9, 0x56a6, 0x56a5, 0x569a, 0x5699, 0x5696, 0x5695, 0x566a, 0x5669, 0x5666, 0x5665, 0x565a, 0x5659, 0x5656, 0x5655, 65 | 0x55aa, 0x55a9, 0x55a6, 0x55a5, 0x559a, 0x5599, 0x5596, 0x5595, 0x556a, 0x5569, 0x5566, 0x5565, 0x555a, 0x5559, 0x5556, 0x5555, 66 | }; 67 | #endif 68 | 69 | size_t encode_channel_inv_manchester(uint8_t* encoded_payload, 70 | const uint8_t* payload, 71 | size_t payload_size) { 72 | const uint8_t* const encoded_payload_begin = encoded_payload; 73 | 74 | for (size_t i = 0; i < payload_size; i++) { 75 | #if SMALL_INV_MACHESTER_ENCODING_TABLE 76 | #if defined(USE_SMALLEST_INV_MACHESTER_ENCODING_TABLE) 77 | const unsigned tmp = payload[i]; 78 | 79 | *encoded_payload++ = 80 | (encoding_tab_inv_machester[(tmp >> 7) & 1] << 6) | 81 | (encoding_tab_inv_machester[(tmp >> 6) & 1] << 4) | 82 | (encoding_tab_inv_machester[(tmp >> 5) & 1] << 2) | 83 | (encoding_tab_inv_machester[(tmp >> 4) & 1] << 0); 84 | 85 | *encoded_payload++ = 86 | (encoding_tab_inv_machester[(tmp >> 3) & 1] << 6) | 87 | (encoding_tab_inv_machester[(tmp >> 2) & 1] << 4) | 88 | (encoding_tab_inv_machester[(tmp >> 1) & 1] << 2) | 89 | (encoding_tab_inv_machester[(tmp >> 0) & 1] << 0); 90 | #else 91 | const unsigned tmp = payload[i]; 92 | 93 | *encoded_payload++ = encoding_tab_inv_machester[tmp >> 4]; 94 | *encoded_payload++ = encoding_tab_inv_machester[tmp & 0x0F]; 95 | #endif 96 | #else 97 | const unsigned tmp = encoding_tab_inv_machester[payload[i]]; 98 | 99 | *encoded_payload++ = tmp >> 8; 100 | *encoded_payload++ = tmp >> 0; 101 | #endif 102 | } 103 | 104 | return encoded_payload - encoded_payload_begin; 105 | } 106 | 107 | #endif /* MODE_S_UTIL_H */ 108 | -------------------------------------------------------------------------------- /include/mode_t_util.h: -------------------------------------------------------------------------------- 1 | #ifndef MODE_T_UTIL_H 2 | #define MODE_T_UTIL_H 3 | 4 | /*- 5 | * Copyright (c) 2024 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | #include 30 | #include 31 | 32 | #ifndef SMALL_3OUTOF6_ENCODING_TABLE 33 | #define SMALL_3OUTOF6_ENCODING_TABLE 0 34 | #endif 35 | 36 | 37 | #if SMALL_3OUTOF6_ENCODING_TABLE 38 | static const uint8_t encoding_tab_3oufof6[16] = { 39 | 0x16, 0x0D, 0x0E, 0x0B, 0x1C, 0x19, 0x1A, 0x13, 0x2C, 0x25, 0x26, 0x23, 0x34, 0x31, 0x32, 0x29 40 | }; 41 | #else 42 | static const uint16_t encoding_tab_3oufof6[256] = { 43 | 0x0596, 0x058d, 0x058e, 0x058b, 0x059c, 0x0599, 0x059a, 0x0593, 0x05ac, 0x05a5, 0x05a6, 0x05a3, 0x05b4, 0x05b1, 0x05b2, 0x05a9, 44 | 0x0356, 0x034d, 0x034e, 0x034b, 0x035c, 0x0359, 0x035a, 0x0353, 0x036c, 0x0365, 0x0366, 0x0363, 0x0374, 0x0371, 0x0372, 0x0369, 45 | 0x0396, 0x038d, 0x038e, 0x038b, 0x039c, 0x0399, 0x039a, 0x0393, 0x03ac, 0x03a5, 0x03a6, 0x03a3, 0x03b4, 0x03b1, 0x03b2, 0x03a9, 46 | 0x02d6, 0x02cd, 0x02ce, 0x02cb, 0x02dc, 0x02d9, 0x02da, 0x02d3, 0x02ec, 0x02e5, 0x02e6, 0x02e3, 0x02f4, 0x02f1, 0x02f2, 0x02e9, 47 | 0x0716, 0x070d, 0x070e, 0x070b, 0x071c, 0x0719, 0x071a, 0x0713, 0x072c, 0x0725, 0x0726, 0x0723, 0x0734, 0x0731, 0x0732, 0x0729, 48 | 0x0656, 0x064d, 0x064e, 0x064b, 0x065c, 0x0659, 0x065a, 0x0653, 0x066c, 0x0665, 0x0666, 0x0663, 0x0674, 0x0671, 0x0672, 0x0669, 49 | 0x0696, 0x068d, 0x068e, 0x068b, 0x069c, 0x0699, 0x069a, 0x0693, 0x06ac, 0x06a5, 0x06a6, 0x06a3, 0x06b4, 0x06b1, 0x06b2, 0x06a9, 50 | 0x04d6, 0x04cd, 0x04ce, 0x04cb, 0x04dc, 0x04d9, 0x04da, 0x04d3, 0x04ec, 0x04e5, 0x04e6, 0x04e3, 0x04f4, 0x04f1, 0x04f2, 0x04e9, 51 | 0x0b16, 0x0b0d, 0x0b0e, 0x0b0b, 0x0b1c, 0x0b19, 0x0b1a, 0x0b13, 0x0b2c, 0x0b25, 0x0b26, 0x0b23, 0x0b34, 0x0b31, 0x0b32, 0x0b29, 52 | 0x0956, 0x094d, 0x094e, 0x094b, 0x095c, 0x0959, 0x095a, 0x0953, 0x096c, 0x0965, 0x0966, 0x0963, 0x0974, 0x0971, 0x0972, 0x0969, 53 | 0x0996, 0x098d, 0x098e, 0x098b, 0x099c, 0x0999, 0x099a, 0x0993, 0x09ac, 0x09a5, 0x09a6, 0x09a3, 0x09b4, 0x09b1, 0x09b2, 0x09a9, 54 | 0x08d6, 0x08cd, 0x08ce, 0x08cb, 0x08dc, 0x08d9, 0x08da, 0x08d3, 0x08ec, 0x08e5, 0x08e6, 0x08e3, 0x08f4, 0x08f1, 0x08f2, 0x08e9, 55 | 0x0d16, 0x0d0d, 0x0d0e, 0x0d0b, 0x0d1c, 0x0d19, 0x0d1a, 0x0d13, 0x0d2c, 0x0d25, 0x0d26, 0x0d23, 0x0d34, 0x0d31, 0x0d32, 0x0d29, 56 | 0x0c56, 0x0c4d, 0x0c4e, 0x0c4b, 0x0c5c, 0x0c59, 0x0c5a, 0x0c53, 0x0c6c, 0x0c65, 0x0c66, 0x0c63, 0x0c74, 0x0c71, 0x0c72, 0x0c69, 57 | 0x0c96, 0x0c8d, 0x0c8e, 0x0c8b, 0x0c9c, 0x0c99, 0x0c9a, 0x0c93, 0x0cac, 0x0ca5, 0x0ca6, 0x0ca3, 0x0cb4, 0x0cb1, 0x0cb2, 0x0ca9, 58 | 0x0a56, 0x0a4d, 0x0a4e, 0x0a4b, 0x0a5c, 0x0a59, 0x0a5a, 0x0a53, 0x0a6c, 0x0a65, 0x0a66, 0x0a63, 0x0a74, 0x0a71, 0x0a72, 0x0a69, 59 | }; 60 | #endif 61 | 62 | size_t encode_channel_3outof6(uint8_t* encoded_payload, 63 | const uint8_t* payload, 64 | size_t payload_size) { 65 | const uint8_t* const encoded_payload_begin = encoded_payload; 66 | 67 | /* Clear least significant bit. */ 68 | const size_t N_2_bytes = payload_size & ~(1u << 0); 69 | 70 | size_t i; 71 | for (i = 0; i < N_2_bytes; i += 2) { 72 | const unsigned tmp = 73 | #if SMALL_3OUTOF6_ENCODING_TABLE 74 | ((unsigned)encoding_tab_3oufof6[payload[i + 0] >> 4] << 18) | 75 | ((unsigned)encoding_tab_3oufof6[payload[i + 0] & 0x0F] << 12) | 76 | ((unsigned)encoding_tab_3oufof6[payload[i + 1] >> 4] << 6) | 77 | ((unsigned)encoding_tab_3oufof6[payload[i + 1] & 0x0F] << 0); 78 | #else 79 | ((unsigned)encoding_tab_3oufof6[payload[i + 0]] << 12) | 80 | ((unsigned)encoding_tab_3oufof6[payload[i + 1]] << 0); 81 | #endif 82 | 83 | *encoded_payload++ = tmp >> 16; 84 | *encoded_payload++ = tmp >> 8; 85 | *encoded_payload++ = tmp >> 0; 86 | } 87 | 88 | if (payload_size & 1) { 89 | /* Payload size is odd. */ 90 | unsigned tmp = 91 | #if SMALL_3OUTOF6_ENCODING_TABLE 92 | ((unsigned)encoding_tab_3oufof6[payload[i + 0] >> 4] << 18) | 93 | ((unsigned)encoding_tab_3oufof6[payload[i + 0] & 0x0F] << 12); 94 | #else 95 | ((unsigned)encoding_tab_3oufof6[payload[i + 0]] << 12); 96 | #endif 97 | 98 | if (payload[i + 0] & 1) /* Least significant bit is one? */ 99 | tmp |= 0x14 << 6; 100 | else 101 | tmp |= 0x28 << 6; 102 | 103 | *encoded_payload++ = tmp >> 16; 104 | *encoded_payload++ = tmp >> 8; 105 | } 106 | else { 107 | /* Payload size is even. */ 108 | if (payload[i + 0] & 1) /* Least significant bit is one? */ 109 | *encoded_payload++ = 0x55; 110 | else 111 | *encoded_payload++ = 0xAA; 112 | } 113 | 114 | return encoded_payload - encoded_payload_begin; 115 | } 116 | 117 | #endif /* MODE_T_UTIL_H */ 118 | -------------------------------------------------------------------------------- /moving_average_filter.h: -------------------------------------------------------------------------------- 1 | #ifndef MOVING_AVERAGE_FILTER_H 2 | #define MOVING_AVERAGE_FILTER_H 3 | 4 | /*- 5 | * Copyright (c) 2017 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | /* 30 | * Moving average filter implementation. 31 | */ 32 | 33 | #include 34 | #include 35 | 36 | typedef struct 37 | { 38 | size_t i; 39 | int *hist; 40 | 41 | const size_t length; 42 | int sum; 43 | } MAVGI_FILTER; 44 | 45 | float mavgi(int sample, MAVGI_FILTER *filter); 46 | 47 | float mavgi(int sample, MAVGI_FILTER *filter) 48 | { 49 | filter->sum = filter->sum - filter->hist[filter->i] + sample; 50 | filter->hist[filter->i++] = sample; 51 | if (filter->i >= filter->length) filter->i = 0; 52 | 53 | return (float)filter->sum / filter->length; 54 | } 55 | 56 | #if 0 57 | static int test_mavgi(void) 58 | { 59 | #define COEFFS 5 60 | static int hist[COEFFS]; 61 | 62 | static MAVGI_FILTER filter = { .length = COEFFS, .hist = hist }; 63 | #undef COEFFS 64 | 65 | for (int sample = 0; sample < 20; sample++) 66 | { 67 | printf("%f, ", mavgi(sample, &filter)); 68 | } 69 | printf("\n"); 70 | 71 | return 0; 72 | } 73 | #endif 74 | 75 | #endif /* MOVING_AVERAGE_FILTER_H */ 76 | 77 | -------------------------------------------------------------------------------- /net_support.h: -------------------------------------------------------------------------------- 1 | #ifndef NET_SUPPORT_H 2 | #define NET_SUPPORT_H 3 | 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | FILE * get_net(const char *hostname, int port); 13 | 14 | 15 | FILE * get_net(const char *hostname, int port) 16 | { 17 | struct sockaddr_in address = {.sin_family = AF_INET, .sin_port = htons(port), .sin_addr = {} }; 18 | if (-1 == inet_aton(hostname, &address.sin_addr)) 19 | { 20 | fprintf(stderr, "inet_aton() error\n"); 21 | return NULL; 22 | } 23 | 24 | int fd = socket(AF_INET, SOCK_STREAM, 0); 25 | if (fd == -1) 26 | { 27 | fprintf(stderr, "socket() error\n"); 28 | return NULL; 29 | } 30 | 31 | if (connect(fd, (struct sockaddr *) &address, sizeof (address)) == -1) 32 | { 33 | fprintf(stderr, "connect() error: %s:%d\n", hostname, port); 34 | return NULL; 35 | } 36 | 37 | FILE *input = fdopen(fd, "rb"); 38 | if (input == NULL) 39 | { 40 | fprintf(stderr, "fdopen() error\n"); 41 | } 42 | 43 | return input; 44 | } 45 | 46 | 47 | #endif /* NET_SUPPORT_H */ 48 | -------------------------------------------------------------------------------- /pics/1. open_demod_bin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xaelsouth/rtl-wmbus/34684e655df70867ac86e799561ec6512c45be0a/pics/1. open_demod_bin.png -------------------------------------------------------------------------------- /pics/2. demod_bin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xaelsouth/rtl-wmbus/34684e655df70867ac86e799561ec6512c45be0a/pics/2. demod_bin.png -------------------------------------------------------------------------------- /pics/3. zoom_into_datgram1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xaelsouth/rtl-wmbus/34684e655df70867ac86e799561ec6512c45be0a/pics/3. zoom_into_datgram1.png -------------------------------------------------------------------------------- /pics/demod.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xaelsouth/rtl-wmbus/34684e655df70867ac86e799561ec6512c45be0a/pics/demod.bin -------------------------------------------------------------------------------- /ppf.h: -------------------------------------------------------------------------------- 1 | #ifndef PPF_H 2 | #define PPF_H 3 | 4 | /*- 5 | * Copyright (c) 2017 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | /* 30 | * Floating and fixed point implementations of Polyphase Filter. 31 | */ 32 | 33 | #include "fir.h" 34 | #include 35 | 36 | typedef struct 37 | { 38 | float sum; 39 | size_t phase; 40 | const size_t max_phase; 41 | FIRF_FILTER *fir; 42 | } PPF_FILTER; 43 | 44 | float ppf(float sample, PPF_FILTER *filter); 45 | 46 | float ppf(float sample, PPF_FILTER *filter) 47 | { 48 | if (filter->phase == filter->max_phase) 49 | { 50 | filter->phase = 0; 51 | filter->sum = 0; 52 | } 53 | 54 | filter->sum += firf(sample, filter->fir + filter->phase); 55 | 56 | filter->phase++; 57 | 58 | return filter->sum; 59 | } 60 | 61 | 62 | typedef struct 63 | { 64 | fixedpt sum; 65 | size_t phase; 66 | const size_t max_phase; 67 | FIRFP_FILTER *fir; 68 | } PPFFP_FILTER; 69 | 70 | fixedpt ppffp(fixedpt sample, PPFFP_FILTER *filter); 71 | 72 | fixedpt ppffp(fixedpt sample, PPFFP_FILTER *filter) 73 | { 74 | if (filter->phase == filter->max_phase) 75 | { 76 | filter->phase = 0; 77 | filter->sum = 0; 78 | } 79 | 80 | filter->sum = fixedpt_add(filter->sum, firfp(sample, filter->fir + filter->phase)); 81 | 82 | filter->phase++; 83 | 84 | return filter->sum; 85 | } 86 | 87 | #if 0 88 | static int test_ppf(void) 89 | { 90 | #define PHASES 2 91 | #define COEFFS 5 92 | static const float b[PHASES][COEFFS] = 93 | { 94 | {0.01208900045, 0.1180545517, 0.2457748215, 0.1180545517, 0.01208900045, }, 95 | {0.04038886734, 0.2065801697, 0.2065801697, 0.04038886734, 0, }, 96 | }; 97 | 98 | static float hist[PHASES][COEFFS] = {}; 99 | 100 | static FIRF_FILTER fir[PHASES] = 101 | { 102 | {.length = COEFFS, .b = b[1], .hist = hist[0]}, // !inverted indexing of fir! 103 | {.length = COEFFS, .b = b[0], .hist = hist[1]}, // !inverted indexing of fir! 104 | }; 105 | 106 | static PPF_FILTER filter = 107 | { 108 | .sum = 0, .phase = 0, .max_phase = PHASES, .fir = fir, 109 | }; 110 | #undef COEFFS 111 | #undef PHASES 112 | 113 | 114 | for (int sample = 1, phase = 0; sample <= 22; sample++, phase ^= 1) 115 | { 116 | float x = ppf(sample, &filter); 117 | if (phase == 1) printf("%f, ", x); 118 | } 119 | 120 | return 0; 121 | } 122 | #endif 123 | 124 | #endif /* PPF_H */ 125 | 126 | -------------------------------------------------------------------------------- /readme.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xaelsouth/rtl-wmbus/34684e655df70867ac86e799561ec6512c45be0a/readme.pdf -------------------------------------------------------------------------------- /rtl_wmbus.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2024 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 17 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23 | * SUCH DAMAGE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64) 39 | #define WINDOWS_BUILD 1 40 | #else 41 | #define WINDOWS_BUILD 0 42 | #endif 43 | 44 | #include "build/version.h" 45 | #include "fir.h" 46 | #include "iir.h" 47 | #include "ppf.h" 48 | #include "moving_average_filter.h" 49 | #include "atan2.h" 50 | #include "rtl_wmbus_util.h" 51 | #include "t1_c1_packet_decoder.h" 52 | #include "s1_packet_decoder.h" 53 | 54 | #if WINDOWS_BUILD == 1 55 | #define CHECK_FLOW 0 56 | 57 | #include 58 | 59 | #warning "Compiling for Win discludes network support." 60 | 61 | static inline void START_ALARM(void) {} 62 | static inline void STOP_ALARM(void) {} 63 | 64 | #else 65 | #define CHECK_FLOW 1 66 | 67 | #include 68 | #include 69 | #include "net_support.h" 70 | 71 | static inline void START_ALARM(void) { alarm(5); } 72 | static inline void STOP_ALARM(void) { alarm(0); } 73 | 74 | static void sig_alarm_handler(int signo) 75 | { 76 | fprintf(stderr, "rtl_wmbus: exiting since incoming data stopped flowing!\n"); 77 | exit(EXIT_FAILURE); 78 | } 79 | #endif 80 | 81 | #ifndef TIME2_ALGORITHM_ENABLED 82 | #define TIME2_ALGORITHM_ENABLED 1 83 | #endif 84 | 85 | #ifndef RUN_LENGTH_ALGORITHM_ENABLED 86 | #define RUN_LENGTH_ALGORITHM_ENABLED 1 87 | #endif 88 | 89 | #ifndef T1_C1_DC_OFFSET_ALPHA 90 | #define T1_C1_DC_OFFSET_ALPHA 0.999f 91 | #endif 92 | 93 | #ifndef S1_DC_OFFSET_ALPHA 94 | #define S1_DC_OFFSET_ALPHA 0.999f 95 | #endif 96 | 97 | static const uint32_t ACCESS_CODE_T1_C1 = 0x543d; 98 | static const uint32_t ACCESS_CODE_T1_C1_BITMASK = 0xFFFFu; 99 | static const unsigned ACCESS_CODE_T1_C1_ERRORS = 0u; // 0 if no errors allowed 100 | 101 | static const uint32_t ACCESS_CODE_S1 = 0x547696; 102 | static const uint32_t ACCESS_CODE_S1_BITMASK = 0xFFFFFFu; 103 | static const unsigned ACCESS_CODE_S1_ERRORS = 0u; // 0 if no errors allowed 104 | 105 | 106 | /* deglitch_filter_t1_c1 has been calculated by a Python script as follows. 107 | The filter is counting "1" among 7 bits and saying "1" if count("1") >= 3 else "0". 108 | Notice here count("1") >= 3. (More intuitive in that case would be count("1") >= 3.5.) 109 | That forces the filter to put more "1" than "0" on the output, because RTL-SDR streams 110 | more "0" than "1" - i don't know why RTL-SDR do this. 111 | x = 'static const uint8_t deglitch_filter_t1_c1[128] = {' 112 | mod8 = 8 113 | 114 | for i in range(2**7): 115 | s = '{0:07b};'.format(i) 116 | val = '1' if bin(i).count("1") >= 3 else '0' 117 | print(s[0] + ";" + s[1] + ";" + s[2] + ";" + s[3] + ";" + s[4] + ";" + s[5] + ";" + s[6] + ";;%d;;%s" % (bin(i).count("1"), val)) 118 | 119 | if i % 8 == 0: x += '\n\t' 120 | x += val + ',' 121 | 122 | x += '};\n' 123 | 124 | print(x) 125 | */ 126 | static const uint8_t deglitch_filter_t1_c1[128] = 127 | { 128 | 0,0,0,0,0,0,0,1, 129 | 0,0,0,1,0,1,1,1, 130 | 0,0,0,1,0,1,1,1, 131 | 0,1,1,1,1,1,1,1, 132 | 0,0,0,1,0,1,1,1, 133 | 0,1,1,1,1,1,1,1, 134 | 0,1,1,1,1,1,1,1, 135 | 1,1,1,1,1,1,1,1, 136 | 0,0,0,1,0,1,1,1, 137 | 0,1,1,1,1,1,1,1, 138 | 0,1,1,1,1,1,1,1, 139 | 1,1,1,1,1,1,1,1, 140 | 0,1,1,1,1,1,1,1, 141 | 1,1,1,1,1,1,1,1, 142 | 1,1,1,1,1,1,1,1, 143 | 1,1,1,1,1,1,1,1 144 | }; 145 | 146 | /* 1) Force the filter to put more ones than zeros on the output. 147 | 2) Zeros surrounded by ones are ones and vice versa. 148 | */ 149 | static const uint8_t deglitch_filter_s1[16] = { 150 | // 0000 0001 0010 0011 0100 0101 0110 0111 151 | 0, 1, 0, 1, 0, 1, 1, 1, 152 | // 1000 1001 1010 1011 1100 1101 1110 1111 153 | 0, 1, 1, 1, 1, 1, 1, 1 154 | }; 155 | 156 | 157 | //static FILE *demod_out = NULL; 158 | static FILE *demod_out2_t1_c1 = NULL; 159 | static FILE *demod_out2_s1 = NULL; 160 | //static FILE *clock_out = NULL; 161 | //static FILE *bits_out = NULL; 162 | //static FILE *rawbits_out = NULL; 163 | 164 | 165 | static inline float moving_average_t1_c1(float sample, size_t i_or_q) 166 | { 167 | #define COEFFS 8 168 | static int i_hist[COEFFS]; 169 | static int q_hist[COEFFS]; 170 | 171 | static MAVGI_FILTER filter[2] = // i/q 172 | { 173 | {.length = COEFFS, .hist = i_hist}, // 0 174 | {.length = COEFFS, .hist = q_hist} // 1 175 | }; 176 | #undef COEFFS 177 | 178 | return mavgi(sample, &filter[i_or_q]); 179 | } 180 | 181 | static inline float moving_average_s1(float sample, size_t i_or_q) 182 | { 183 | #define COEFFS 16 184 | static int i_hist[COEFFS]; 185 | static int q_hist[COEFFS]; 186 | 187 | static MAVGI_FILTER filter[2] = // i/q 188 | { 189 | {.length = COEFFS, .hist = i_hist}, // 0 190 | {.length = COEFFS, .hist = q_hist} // 1 191 | }; 192 | #undef COEFFS 193 | 194 | return mavgi(sample, &filter[i_or_q]); 195 | } 196 | 197 | static inline float lp_fir_butter_1600kHz_160kHz_200kHz_t1_c1(float sample, size_t i_or_q) 198 | { 199 | #define COEFFS 23 200 | static float b[COEFFS] = {0.000140535927, 1.102280392e-05, 0.0001309279731, 0.001356012537, 0.00551787474, 0.01499414005, 0.03160167988, 0.05525973093, 0.08315031015, 0.1099887688, 0.1295143636, 0.1366692652, 0.1295143636, 0.1099887688, 0.08315031015, 0.05525973093, 0.03160167988, 0.01499414005, 0.00551787474, 0.001356012537, 0.0001309279731, 1.102280392e-05, 0.000140535927, }; 201 | //static float b[COEFFS] = {0.001645672124, 0.0004733757463, -0.002542116469, -0.008572441674, -0.01545406295, -0.01651661113, -0.002914917097, 0.03113207374, 0.08317149659, 0.1410058012, 0.1866042197, 0.2039350204, 0.1866042197, 0.1410058012, 0.08317149659, 0.03113207374, -0.002914917097, -0.01651661113, -0.01545406295, -0.008572441674, -0.002542116469, 0.0004733757463, 0.001645672124, }; 202 | 203 | static float i_hist[COEFFS] = {}; 204 | static float q_hist[COEFFS] = {}; 205 | 206 | static FIRF_FILTER filter[2] = // i/q 207 | { 208 | {.length = COEFFS, .b = b, .hist = i_hist}, // 0 209 | {.length = COEFFS, .b = b, .hist = q_hist} // 1 210 | }; 211 | #undef COEFFS 212 | 213 | return firf(sample, &filter[i_or_q]); 214 | } 215 | 216 | static inline float lp_fir_butter_1600kHz_160kHz_200kHz_s1(float sample, size_t i_or_q) 217 | { 218 | #define COEFFS 23 219 | static float b[COEFFS] = {0.000140535927, 1.102280392e-05, 0.0001309279731, 0.001356012537, 0.00551787474, 0.01499414005, 0.03160167988, 0.05525973093, 0.08315031015, 0.1099887688, 0.1295143636, 0.1366692652, 0.1295143636, 0.1099887688, 0.08315031015, 0.05525973093, 0.03160167988, 0.01499414005, 0.00551787474, 0.001356012537, 0.0001309279731, 1.102280392e-05, 0.000140535927, }; 220 | //static float b[COEFFS] = {0.001645672124, 0.0004733757463, -0.002542116469, -0.008572441674, -0.01545406295, -0.01651661113, -0.002914917097, 0.03113207374, 0.08317149659, 0.1410058012, 0.1866042197, 0.2039350204, 0.1866042197, 0.1410058012, 0.08317149659, 0.03113207374, -0.002914917097, -0.01651661113, -0.01545406295, -0.008572441674, -0.002542116469, 0.0004733757463, 0.001645672124, }; 221 | 222 | static float i_hist[COEFFS] = {}; 223 | static float q_hist[COEFFS] = {}; 224 | 225 | static FIRF_FILTER filter[2] = // i/q 226 | { 227 | {.length = COEFFS, .b = b, .hist = i_hist}, // 0 228 | {.length = COEFFS, .b = b, .hist = q_hist} // 1 229 | }; 230 | #undef COEFFS 231 | 232 | return firf(sample, &filter[i_or_q]); 233 | } 234 | 235 | static inline float lp_firfp_butter_1600kHz_160kHz_200kHz(float sample, size_t i_or_q) 236 | { 237 | #define COEFFS 23 238 | static const fixedpt b[COEFFS] = {fixedpt_rconst(0.000140535927), fixedpt_rconst(1.102280392e-05), fixedpt_rconst(0.0001309279731), fixedpt_rconst(0.001356012537), fixedpt_rconst(0.00551787474), 239 | fixedpt_rconst(0.01499414005), fixedpt_rconst(0.03160167988), fixedpt_rconst(0.05525973093), fixedpt_rconst(0.08315031015), fixedpt_rconst(0.1099887688), 240 | fixedpt_rconst(0.1295143636), fixedpt_rconst(0.1366692652), fixedpt_rconst(0.1295143636), fixedpt_rconst(0.1099887688), fixedpt_rconst(0.08315031015), 241 | fixedpt_rconst(0.05525973093), fixedpt_rconst(0.03160167988), fixedpt_rconst(0.01499414005), fixedpt_rconst(0.00551787474), fixedpt_rconst(0.001356012537), 242 | fixedpt_rconst(0.0001309279731), fixedpt_rconst(1.102280392e-05), fixedpt_rconst(0.000140535927), 243 | }; 244 | static fixedpt i_hist[COEFFS] = {}; 245 | static fixedpt q_hist[COEFFS] = {}; 246 | 247 | static FIRFP_FILTER filter[2] = // i/q 248 | { 249 | {.length = COEFFS, .b = b, .hist = i_hist}, // 0 250 | {.length = COEFFS, .b = b, .hist = q_hist} // 1 251 | }; 252 | #undef COEFFS 253 | 254 | return fixedpt_tofloat(firfp(fixedpt_fromint(sample), &filter[i_or_q])); 255 | } 256 | 257 | 258 | static inline float lp_ppf_butter_1600kHz_160kHz_200kHz(float sample, size_t i_or_q) 259 | { 260 | #define PHASES 2 261 | #define COEFFS 12 262 | static float b[PHASES][COEFFS] = 263 | { 264 | {0.000140535927, 0.0001309279731, 0.00551787474, 0.03160167988, 0.08315031015, 0.1295143636, 0.1295143636, 0.08315031015, 0.03160167988, 0.00551787474, 0.0001309279731, 0.000140535927, }, 265 | {1.102280392e-05, 0.001356012537, 0.01499414005, 0.05525973093, 0.1099887688, 0.1366692652, 0.1099887688, 0.05525973093, 0.01499414005, 0.001356012537, 1.102280392e-05, 0, }, 266 | }; 267 | 268 | static float i_hist[PHASES][COEFFS] = {}; 269 | static float q_hist[PHASES][COEFFS] = {}; 270 | 271 | static FIRF_FILTER fir[2][PHASES] = 272 | { 273 | { 274 | // i/q phase 275 | {.length = COEFFS, .b = b[1], .hist = i_hist[0]}, // 0 0 276 | {.length = COEFFS, .b = b[0], .hist = i_hist[1]} // 0 1 277 | }, 278 | { 279 | // i/q phase 280 | {.length = COEFFS, .b = b[1], .hist = q_hist[0]}, // 1 0 281 | {.length = COEFFS, .b = b[0], .hist = q_hist[1]} // 1 1 282 | }, 283 | }; 284 | 285 | static PPF_FILTER filter[2] = 286 | { 287 | {.sum = 0, .phase = 0, .max_phase = PHASES, .fir = fir[0]}, // 0 =: i 288 | {.sum = 0, .phase = 0, .max_phase = PHASES, .fir = fir[1]}, // 1 =: q 289 | }; 290 | #undef COEFFS 291 | #undef PHASES 292 | 293 | return ppf(sample, &filter[i_or_q]); 294 | } 295 | 296 | 297 | static inline float lp_ppffp_butter_1600kHz_160kHz_200kHz(float sample, size_t i_or_q) 298 | { 299 | #define PHASES 2 300 | #define COEFFS 12 301 | static const fixedpt b[PHASES][COEFFS] = 302 | { 303 | {fixedpt_rconst(0.000140535927), fixedpt_rconst(0.0001309279731), fixedpt_rconst(0.00551787474), fixedpt_rconst(0.03160167988), fixedpt_rconst(0.08315031015), fixedpt_rconst(0.1295143636), fixedpt_rconst(0.1295143636), fixedpt_rconst(0.08315031015), fixedpt_rconst(0.03160167988), fixedpt_rconst(0.00551787474), fixedpt_rconst(0.0001309279731), fixedpt_rconst(0.000140535927), }, 304 | {fixedpt_rconst(1.102280392e-05), fixedpt_rconst(0.001356012537), fixedpt_rconst(0.01499414005), fixedpt_rconst(0.05525973093), fixedpt_rconst(0.1099887688), fixedpt_rconst(0.1366692652), fixedpt_rconst(0.1099887688), fixedpt_rconst(0.05525973093), fixedpt_rconst(0.01499414005), fixedpt_rconst(0.001356012537), fixedpt_rconst(1.102280392e-05), fixedpt_rconst(0), }, 305 | }; 306 | 307 | static fixedpt i_hist[PHASES][COEFFS] = {}; 308 | static fixedpt q_hist[PHASES][COEFFS] = {}; 309 | 310 | static FIRFP_FILTER fir[2][PHASES] = 311 | { 312 | { 313 | // i/q phase 314 | {.length = COEFFS, .b = b[1], .hist = i_hist[0]}, // 0 0 315 | {.length = COEFFS, .b = b[0], .hist = i_hist[1]} // 0 1 316 | }, 317 | { 318 | // i/q phase 319 | {.length = COEFFS, .b = b[1], .hist = q_hist[0]}, // 1 0 320 | {.length = COEFFS, .b = b[0], .hist = q_hist[1]} // 1 1 321 | }, 322 | }; 323 | 324 | static PPFFP_FILTER filter[2] = 325 | { 326 | {.sum = fixedpt_rconst(0), .phase = 0, .max_phase = PHASES, .fir = fir[0]}, // 0 =: i 327 | {.sum = fixedpt_rconst(0), .phase = 0, .max_phase = PHASES, .fir = fir[1]}, // 1 =: q 328 | }; 329 | #undef COEFFS 330 | #undef PHASES 331 | 332 | return fixedpt_tofloat(ppffp(fixedpt_fromint(sample), &filter[i_or_q])); 333 | } 334 | 335 | 336 | static inline float bp_iir_cheb1_800kHz_90kHz_98kHz_102kHz_110kHz(float sample) 337 | { 338 | #define GAIN 1.874981046e-06 339 | #define SECTIONS 3 340 | static const float b[3*SECTIONS] = {1, 1.999994649, 0.9999946492, 1, -1.99999482, 0.9999948196, 1, 1.703868036e-07, -1.000010531, }; 341 | static const float a[3*SECTIONS] = {1, -1.387139203, 0.9921518712, 1, -1.403492665, 0.9845934971, 1, -1.430055639, 0.9923856172, }; 342 | static float hist[3*SECTIONS] = {}; 343 | 344 | static IIRF_FILTER filter = {.sections = SECTIONS, .b = b, .a = a, .gain = GAIN, .hist = hist}; 345 | #undef SECTIONS 346 | #undef GAIN 347 | 348 | return iirf(sample, &filter); 349 | } 350 | 351 | static inline float bp_iir_cheb1_800kHz_22kHz_30kHz_34kHz_42kHz(float sample) 352 | { 353 | #define GAIN 1.874981046e-06 354 | #define SECTIONS 3 355 | static const float b[3*SECTIONS] = {1, 1.999994187, 0.9999941867, 1, -1.999994026,0.9999940262, 1, -1.605750097e-07, -1.000011787, }; 356 | static const float a[3*SECTIONS] = {1, -1.92151475, 0.9918135499, 1, -1.922481015,0.984593497, 1, -1.937432099, 0.9927241336, }; 357 | static float hist[3*SECTIONS] = {}; 358 | 359 | static IIRF_FILTER filter = {.sections = SECTIONS, .b = b, .a = a, .gain = GAIN, .hist = hist}; 360 | 361 | #undef SECTIONS 362 | #undef GAIN 363 | 364 | return iirf(sample, &filter); 365 | } 366 | 367 | 368 | 369 | static inline float lp_fir_butter_800kHz_100kHz_160kHz(float sample) 370 | { 371 | #define COEFFS 11 372 | static float b[COEFFS] = {-0.00456638213, -0.002571450348, 0.02689425925, 0.1141330398, 0.2264456422, 0.2793297826, 0.2264456422, 0.1141330398, 0.02689425925, -0.002571450348, -0.00456638213, }; 373 | static float hist[COEFFS]; 374 | 375 | static FIRF_FILTER filter = {.length = COEFFS, .b = b, .hist = hist}; 376 | #undef COEFFS 377 | 378 | return firf(sample, &filter); 379 | } 380 | 381 | static inline float lp_fir_butter_800kHz_32kHz_36kHz(float sample) 382 | { 383 | #define COEFFS 46 384 | static float b[COEFFS] = {-0.000649081282, -0.0009491938209, -0.001361601657, -0.001910785234, -0.002570133495, -0.003251218426, -0.003801634695, -0.004012672882, -0.003636803575, -0.002413585945, -0.0001013597693, 0.003488892085, 0.008461671287, 0.01481127545, 0.02240598045, 0.03098477999, 0.0401679839, 0.04948137286, 0.05839197924, 0.06635211627, 0.07284719662, 0.07744230649, 0.07982251613, 0.07982251613, 0.07744230649, 0.07284719662, 0.06635211627, 0.05839197924, 0.04948137286, 0.0401679839, 0.03098477999, 0.02240598045, 0.01481127545, 0.008461671287, 0.003488892085, -0.0001013597693, -0.002413585945, -0.003636803575, -0.004012672882, -0.003801634695, -0.003251218426, -0.002570133495, -0.001910785234, -0.001361601657, -0.0009491938209, -0.000649081282, }; 385 | 386 | static float hist[COEFFS]; 387 | 388 | static FIRF_FILTER filter = {.length = COEFFS, .b = b, .hist = hist}; 389 | #undef COEFFS 390 | 391 | return firf(sample, &filter); 392 | } 393 | 394 | /* https://liquidsdr.org/blog/lms-equalizer/ */ 395 | static inline void equalizer_complex_t1_c1(float *i, float *q) 396 | { 397 | static const float mu = 0.05f; 398 | static size_t buf_index = 0; 399 | 400 | #define COEFFS 21 401 | static float complex w[COEFFS] = { 402 | 0.f, 0.f, 0.f, 0.f, 0.f, 403 | 0.f, 0.f, 0.f, 0.f, 0.f, 404 | 1.f, 405 | 0.f, 0.f, 0.f, 0.f, 0.f, 406 | 0.f, 0.f, 0.f, 0.f, 0.f, 407 | }; 408 | 409 | static float complex b[COEFFS]; 410 | 411 | b[buf_index] = *i + *q * _Complex_I; 412 | buf_index = (buf_index + 1) % COEFFS; 413 | 414 | float complex r = 0.f; 415 | for (int k = 0; k < COEFFS; k++) 416 | { 417 | r += b[(buf_index+k)%COEFFS] * conjf(w[k]); 418 | } 419 | 420 | const float complex e = (*q >= 0.f) ? (127.5f * _Complex_I) : (-127.5f * _Complex_I); 421 | for (int k = 0; k < COEFFS; k++) 422 | { 423 | w[k] = w[k] - mu * conjf(e)*b[(buf_index+k)%COEFFS]; 424 | } 425 | #undef COEFFS 426 | 427 | //fprintf(stdout, "%8.3f, %8.3f, %8.3f, %8.3f\n", *i, crealf(r), *q, cimagf(r)); 428 | 429 | *i = crealf(r); 430 | *q = cimagf(r); 431 | } 432 | 433 | static inline float equalizer_t1_c1(const float sample, const float d) 434 | { 435 | static const float mu = 0.05f; 436 | 437 | #define COEFFS 9 438 | static float b[COEFFS] = { [COEFFS/2 + 1] = 1.f, }; 439 | 440 | static float hist[COEFFS]; 441 | 442 | static FIRF_FILTER filter = {.length = COEFFS, .b = b, .hist = hist}; 443 | #undef COEFFS 444 | 445 | const float r = firf(sample, &filter); 446 | 447 | const float e = d - r; 448 | const float mu_e = mu * e; 449 | firf_lms(mu_e, &filter); 450 | 451 | return r; 452 | } 453 | 454 | static inline float equalizer_s1(const float sample, const float d) 455 | { 456 | static const float mu = 0.05f; 457 | 458 | #define COEFFS 19 459 | static float b[COEFFS] = { [COEFFS/2 + 1] = 1.f, }; 460 | 461 | static float hist[COEFFS]; 462 | 463 | static FIRF_FILTER filter = {.length = COEFFS, .b = b, .hist = hist}; 464 | #undef COEFFS 465 | 466 | const float r = firf(sample, &filter); 467 | 468 | const float e = d - r; 469 | const float mu_e = mu * e; 470 | firf_lms(mu_e, &filter); 471 | 472 | return r; 473 | } 474 | 475 | static float rssi_filter_t1_c1(float sample) 476 | { 477 | static float old_sample; 478 | 479 | #define ALPHA 0.6789f 480 | old_sample = ALPHA*sample + (1.0f - ALPHA)*old_sample; 481 | #undef ALPHA 482 | 483 | return old_sample; 484 | } 485 | 486 | static float rssi_filter_s1(float sample) 487 | { 488 | static float old_sample; 489 | 490 | #define ALPHA 0.6789f 491 | old_sample = ALPHA*sample + (1.0f - ALPHA)*old_sample; 492 | #undef ALPHA 493 | 494 | return old_sample; 495 | } 496 | 497 | static float s1_remove_dc_offset_demod(float x) 498 | { 499 | static float x_old, y_old; 500 | 501 | y_old = (1.f + S1_DC_OFFSET_ALPHA)/2.f * (x - x_old) + S1_DC_OFFSET_ALPHA * y_old; 502 | x_old = x; 503 | 504 | return y_old; 505 | } 506 | 507 | static float t1_c1_remove_dc_offset_demod(float x) 508 | { 509 | static float x_old, y_old; 510 | 511 | y_old = (1.f + T1_C1_DC_OFFSET_ALPHA)/2.f * (x - x_old) + T1_C1_DC_OFFSET_ALPHA * y_old; 512 | x_old = x; 513 | 514 | return y_old; 515 | } 516 | 517 | static inline float polar_discriminator_t1_c1(float i, float q) 518 | { 519 | static float complex s_last; 520 | const float complex s = i + q * _Complex_I; 521 | const float complex y = s * conjf(s_last); 522 | 523 | #if 1 524 | const float delta_phi = atan2_libm(y); 525 | #elif 0 526 | const float delta_phi = atan2_approximation(y); 527 | #else 528 | const float delta_phi = atan2_approximation2(y); 529 | #endif 530 | 531 | s_last = s; 532 | 533 | return delta_phi; 534 | } 535 | 536 | static inline float polar_discriminator_t1_c1_inaccurate(float i, float q) 537 | { 538 | // We are going to use only complex part of the phase difference 539 | // so avoid unnecesary computation of real part. The math behind: 540 | // cargf = atan (delta_phi_imag / delta_phi_real) / pi; 541 | // In the formula only the sign is of interest - we compute delta_phi_imag only. 542 | 543 | static float i_last, q_last; 544 | 545 | const float delta_phi_imag = i_last*q - i*q_last; 546 | 547 | i_last = i; 548 | q_last = q; 549 | 550 | return delta_phi_imag; 551 | } 552 | 553 | static inline float polar_discriminator_s1(float i, float q) 554 | { 555 | static float complex s_last; 556 | const float complex s = i + q * _Complex_I; 557 | const float complex y = s * conjf(s_last); 558 | 559 | #if 1 560 | const float delta_phi = atan2_libm(y); 561 | #elif 0 562 | const float delta_phi = atan2_approximation(y); 563 | #else 564 | const float delta_phi = atan2_approximation2(y); 565 | #endif 566 | 567 | s_last = s; 568 | 569 | return delta_phi; 570 | } 571 | 572 | static inline float polar_discriminator_s1_inaccurate(float i, float q) 573 | { 574 | // We are going to use only complex part of the phase difference 575 | // so avoid unnecesary computation of real part. The math behind: 576 | // cargf = atan (delta_phi_imag / delta_phi_real) / pi; 577 | // In the formula only the sign is of interest - we compute delta_phi_imag only. 578 | 579 | static float i_last, q_last; 580 | const float delta_phi_imag = i_last*q - i*q_last; 581 | 582 | i_last = i; 583 | q_last = q; 584 | 585 | return delta_phi_imag; 586 | } 587 | 588 | /** @brief Sparse Ones runs in time proportional to the number 589 | * of 1 bits. 590 | * 591 | * From: http://gurmeet.net/puzzles/fast-bit-counting-routines 592 | */ 593 | static inline unsigned count_set_bits_sparse_one(uint32_t n) 594 | { 595 | unsigned count = 0; 596 | 597 | while (n) 598 | { 599 | count++; 600 | n &= (n - 1) ; // set rightmost 1 bit in n to 0 601 | } 602 | 603 | return count; 604 | } 605 | 606 | 607 | static inline unsigned count_set_bits(uint32_t n) 608 | { 609 | #if defined(__i386__) || defined(__arm__) 610 | return __builtin_popcount(n); 611 | #else 612 | return count_set_bits_sparse_one(n); 613 | #endif 614 | } 615 | 616 | 617 | struct runlength_algorithm_s1 618 | { 619 | int run_length; 620 | unsigned state; 621 | uint32_t raw_bitstream; 622 | uint32_t bitstream; 623 | int samples_per_bit[2]; 624 | struct s1_packet_decoder_work decoder; 625 | }; 626 | 627 | 628 | static void runlength_algorithm_reset_s1(struct runlength_algorithm_s1 *algo) 629 | { 630 | algo->run_length = 0; 631 | algo->state = 0u; 632 | algo->raw_bitstream = 0; 633 | algo->bitstream = 0; 634 | algo->samples_per_bit[0] = 24; // Data rate is 32768 bps which gives us approx. 24 samples 635 | algo->samples_per_bit[1] = 24; // at a sample rate of 800kHz (800kHz / 32768bps = 24.41 ~= 24 samples). 636 | reset_s1_packet_decoder(&algo->decoder); 637 | } 638 | 639 | 640 | static void runlength_algorithm_s1(unsigned raw_bit, unsigned rssi, struct runlength_algorithm_s1 *algo) 641 | { 642 | algo->raw_bitstream = (algo->raw_bitstream << 1) | raw_bit; 643 | 644 | const unsigned state = deglitch_filter_s1[algo->raw_bitstream & 0xFu]; 645 | 646 | // Edge detector. 647 | if (algo->state == state) 648 | { 649 | algo->run_length++; 650 | } 651 | else 652 | { 653 | // Get the current bit length expressed in samples as an 654 | // average of two preceeding symbols. 655 | const int samples_per_bit = (algo->samples_per_bit[0] + algo->samples_per_bit[1]) / 2; 656 | 657 | // Reset the state machine if the current bit length (in samples) 658 | // is less than 0.5 or more than 1.5 of the ideal symbol length. 659 | if (samples_per_bit <= 24/2 || samples_per_bit >= (24+24/2)) 660 | { 661 | runlength_algorithm_reset_s1(algo); 662 | algo->state = state; 663 | algo->run_length = 1; 664 | return; 665 | } 666 | 667 | // Reset the state machine if the sequence of ones (or zeros) 668 | // is less than 0.5 symbol length that we assume. 669 | const int half_bit_length = samples_per_bit/2; 670 | const int run_length = algo->run_length; 671 | if (run_length <= half_bit_length) 672 | { 673 | runlength_algorithm_reset_s1(algo); 674 | algo->state = state; 675 | algo->run_length = 1; 676 | return; 677 | } 678 | 679 | int num_of_bits_rx; 680 | for (num_of_bits_rx = 0; algo->run_length > half_bit_length; num_of_bits_rx++) 681 | { 682 | algo->run_length -= samples_per_bit; 683 | 684 | unsigned bit = algo->state; 685 | 686 | algo->bitstream = (algo->bitstream << 1) | bit; 687 | 688 | if (count_set_bits((algo->bitstream & ACCESS_CODE_S1_BITMASK) ^ ACCESS_CODE_S1) <= ACCESS_CODE_S1_ERRORS) 689 | { 690 | bit |= (1u<decoder, "rla;"); 694 | } 695 | 696 | //fprintf(stdout, "%u, %d, bits: %d, 0: %u, 1: %u\n", algo->state, run_length, num_of_bits_rx, algo->samples_per_bit[0], algo->samples_per_bit[1]); 697 | 698 | algo->samples_per_bit[algo->state] = run_length / num_of_bits_rx; 699 | algo->state = state; 700 | algo->run_length = 1; 701 | } 702 | } 703 | 704 | 705 | struct runlength_algorithm_t1_c1 706 | { 707 | int run_length; 708 | int bit_length; 709 | int cum_run_length_error; 710 | unsigned state; 711 | uint32_t raw_bitstream; 712 | uint32_t bitstream; 713 | struct t1_c1_packet_decoder_work decoder; 714 | }; 715 | 716 | 717 | static void runlength_algorithm_reset_t1_c1(struct runlength_algorithm_t1_c1 *algo) 718 | { 719 | algo->run_length = 0; 720 | algo->bit_length = 8 * 256; 721 | algo->cum_run_length_error = 0; 722 | algo->state = 0u; 723 | algo->raw_bitstream = 0; 724 | algo->bitstream = 0; 725 | reset_t1_c1_packet_decoder(&algo->decoder); 726 | } 727 | 728 | 729 | static void runlength_algorithm_t1_c1(unsigned raw_bit, unsigned rssi, struct runlength_algorithm_t1_c1 *algo) 730 | { 731 | algo->raw_bitstream = (algo->raw_bitstream << 1) | raw_bit; 732 | 733 | const unsigned state = deglitch_filter_t1_c1[algo->raw_bitstream & 0x3Fu]; 734 | 735 | // Edge detector. 736 | if (algo->state == state) 737 | { 738 | algo->run_length++; 739 | } 740 | else 741 | { 742 | if (algo->run_length < 5) 743 | { 744 | runlength_algorithm_reset_t1_c1(algo); 745 | algo->state = state; 746 | algo->run_length = 1; 747 | return; 748 | } 749 | 750 | //const int unscaled_run_length = algo->run_length; 751 | 752 | algo->run_length *= 256; // resolution scaling up for fixed point calculation 753 | 754 | const int half_bit_length = algo->bit_length / 2; 755 | 756 | if (algo->run_length <= half_bit_length) 757 | { 758 | runlength_algorithm_reset_t1_c1(algo); 759 | algo->state = state; 760 | algo->run_length = 1; 761 | return; 762 | } 763 | 764 | int num_of_bits_rx; 765 | for (num_of_bits_rx = 0; algo->run_length > half_bit_length; num_of_bits_rx++) 766 | { 767 | algo->run_length -= algo->bit_length; 768 | 769 | unsigned bit = algo->state; 770 | 771 | algo->bitstream = (algo->bitstream << 1) | bit; 772 | 773 | if (count_set_bits((algo->bitstream & ACCESS_CODE_T1_C1_BITMASK) ^ ACCESS_CODE_T1_C1) <= ACCESS_CODE_T1_C1_ERRORS) 774 | { 775 | bit |= (1u<decoder, "rla;"); 779 | } 780 | 781 | #if 0 782 | const int bit_error_length = algo->run_length / num_of_bits_rx; 783 | if (in_rx_t1_c1_packet_decoder(&algo->decoder)) 784 | { 785 | fprintf(stdout, "rl = %d, num_of_bits_rx = %d, bit_length = %d, old_bit_error_length = %d, new_bit_error_length = %d\n", 786 | unscaled_run_length, num_of_bits_rx, algo->bit_length, algo->bit_error_length, bit_error_length); 787 | } 788 | #endif 789 | 790 | // Some kind of PI controller is implemented below: u[n] = u[n-1] + Kp * e[n] + Ki * sum(e[0..n]). 791 | // Kp and Ki were found by experiment; e[n] := algo->run_length; u[[n] is the new bit length; u[n-1] is the last known bit length 792 | algo->cum_run_length_error += algo->run_length; // sum(e[0..n]) 793 | #define PI_KP 32 794 | #define PI_KI 16 795 | //algo->bit_length += (algo->run_length / PI_KP + algo->cum_run_length_error / PI_KI) / num_of_bits_rx; 796 | algo->bit_length += (algo->run_length + algo->cum_run_length_error / PI_KI) / (PI_KP * num_of_bits_rx); 797 | #undef PI_KI 798 | #undef PI_KP 799 | 800 | algo->state = state; 801 | algo->run_length = 1; 802 | } 803 | } 804 | 805 | 806 | struct time2_algorithm_t1_c1 807 | { 808 | uint32_t bitstream; 809 | struct t1_c1_packet_decoder_work t1_c1_decoder; 810 | }; 811 | 812 | static void time2_algorithm_t1_c1_reset(struct time2_algorithm_t1_c1 *algo) 813 | { 814 | algo->bitstream = 0; 815 | reset_t1_c1_packet_decoder(&algo->t1_c1_decoder); 816 | } 817 | 818 | static void time2_algorithm_t1_c1(unsigned bit, unsigned rssi, struct time2_algorithm_t1_c1 *algo) 819 | { 820 | algo->bitstream = (algo->bitstream << 1) | bit; 821 | 822 | if (count_set_bits((algo->bitstream & ACCESS_CODE_T1_C1_BITMASK) ^ ACCESS_CODE_T1_C1) <= ACCESS_CODE_T1_C1_ERRORS) 823 | { 824 | bit |= (1u<t1_c1_decoder, "t2a;"); 828 | } 829 | 830 | struct time2_algorithm_s1 831 | { 832 | uint32_t bitstream; 833 | struct s1_packet_decoder_work s1_decoder; 834 | }; 835 | 836 | static void time2_algorithm_s1_reset(struct time2_algorithm_s1 *algo) 837 | { 838 | algo->bitstream = 0; 839 | reset_s1_packet_decoder(&algo->s1_decoder); 840 | } 841 | 842 | static void time2_algorithm_s1(unsigned bit, unsigned rssi, struct time2_algorithm_s1 *algo) 843 | { 844 | algo->bitstream = (algo->bitstream << 1) | bit; 845 | 846 | if (count_set_bits((algo->bitstream & ACCESS_CODE_S1_BITMASK) ^ ACCESS_CODE_S1) <= ACCESS_CODE_S1_ERRORS) 847 | { 848 | bit |= (1u<s1_decoder, "t2a;"); 852 | } 853 | 854 | 855 | static int opts_run_length_algorithm_enabled = 1; 856 | static int opts_time2_algorithm_enabled = TIME2_ALGORITHM_ENABLED; 857 | static unsigned opts_decimation_rate = 2u; 858 | static int opts_s1_t1_c1_simultaneously = 0; 859 | static int opts_accurate_atan = 1; 860 | static int opts_remove_dc_offset = 0; 861 | int opts_show_used_algorithm = 0; 862 | static int opts_t1_c1_processing_enabled = 1; 863 | static int opts_s1_processing_enabled = 1; 864 | static int opts_check_flow = 0; 865 | static const unsigned opts_CLOCK_LOCK_THRESHOLD_T1_C1 = 2; // Is not implemented as option yet. 866 | static const unsigned opts_CLOCK_LOCK_THRESHOLD_S1 = 2; // Is not implemented as option yet. 867 | 868 | 869 | static void print_usage(const char *program_name) 870 | { 871 | fprintf(stdout, "rtl_wmbus: " VERSION "\n\n"); 872 | fprintf(stdout, "Usage %s:\n", program_name); 873 | fprintf(stdout, "\t-o remove DC offset\n"); 874 | fprintf(stdout, "\t-a accelerate (use an inaccurate atan version)\n"); 875 | fprintf(stdout, "\t-r 0 to disable run length algorithm\n"); 876 | fprintf(stdout, "\t-t 0 to disable time2 algorithm\n"); 877 | fprintf(stdout, "\t-d 2 set decimation rate to 2 (defaults to 2 if omitted)\n"); 878 | fprintf(stdout, "\t-v show used algorithm in the output\n"); 879 | fprintf(stdout, "\t-V show version\n"); 880 | fprintf(stdout, "\t-s receive S1 and T1/C1 datagrams simultaneously. rtl_sdr _MUST_ be set to 868.625MHz (-f 868.625M)\n"); 881 | fprintf(stdout, "\t-p [T,S] to disable processing T1/C1 or S1 mode\n"); 882 | fprintf(stdout, "\t-f exit if incoming data stalled for 5 seconds\n"); 883 | fprintf(stdout, "\t-h print this help\n"); 884 | } 885 | 886 | static void print_version(void) 887 | { 888 | fprintf(stdout, "rtl_wmbus: " VERSION "\n"); 889 | fprintf(stdout, COMMIT "\n"); 890 | } 891 | 892 | static void process_options(int argc, char *argv[]) 893 | { 894 | int option; 895 | 896 | while ((option = getopt(argc, argv, "ofad:p:r:vVst:")) != -1) 897 | { 898 | switch (option) 899 | { 900 | case 'o': 901 | opts_remove_dc_offset = 1; 902 | break; 903 | case 'f': 904 | opts_check_flow = 1; 905 | #if CHECK_FLOW == 0 906 | fprintf(stderr, "rtl_wmbus: Warning! You supplied the option -f but this build of rtl_wmbus cannot check flow of incoming data!\n"); 907 | #endif 908 | break; 909 | case 'a': 910 | opts_accurate_atan = 0; 911 | break; 912 | case 'p': 913 | if (strcmp(optarg, "T") == 0 || strcmp(optarg, "t") == 0) 914 | { 915 | opts_t1_c1_processing_enabled = 0; 916 | } 917 | else if (strcmp(optarg, "S") == 0 || strcmp(optarg, "s") == 0) 918 | { 919 | opts_s1_processing_enabled = 0; 920 | } 921 | else 922 | { 923 | print_usage(argv[0]); 924 | exit(EXIT_FAILURE); 925 | } 926 | break; 927 | case 'r': 928 | if (strcmp(optarg, "0") == 0) 929 | { 930 | opts_run_length_algorithm_enabled = 0; 931 | } 932 | else 933 | { 934 | print_usage(argv[0]); 935 | exit(EXIT_FAILURE); 936 | } 937 | break; 938 | case 't': 939 | if (strcmp(optarg, "0") == 0) 940 | { 941 | opts_time2_algorithm_enabled = 0; 942 | } 943 | else 944 | { 945 | print_usage(argv[0]); 946 | exit(EXIT_FAILURE); 947 | } 948 | break; 949 | case 'd': 950 | opts_decimation_rate = strtoul(optarg, NULL, 10); 951 | break; 952 | case 's': 953 | opts_s1_t1_c1_simultaneously = 1; 954 | break; 955 | case 'v': 956 | opts_show_used_algorithm = 1; 957 | break; 958 | case 'V': 959 | print_version(); 960 | exit(EXIT_SUCCESS); 961 | break; 962 | default: 963 | print_usage(argv[0]); 964 | exit(EXIT_FAILURE); 965 | } 966 | } 967 | } 968 | 969 | static float *LUT_FREQUENCY_TRANSLATION_PLUS_COSINE = NULL; 970 | static float *LUT_FREQUENCY_TRANSLATION_PLUS_SINE = NULL; 971 | #define FREQ_STEP_KHZ (25) 972 | 973 | /* fs_kHz is the sample rate in kHz. */ 974 | static void setup_lookup_tables_for_frequency_translation(int fs_kHz) 975 | { 976 | const int ft_kHz = FREQ_STEP_KHZ; 977 | const size_t n_max = fs_kHz/ft_kHz; 978 | 979 | free(LUT_FREQUENCY_TRANSLATION_PLUS_COSINE); 980 | LUT_FREQUENCY_TRANSLATION_PLUS_COSINE = malloc(n_max *sizeof(LUT_FREQUENCY_TRANSLATION_PLUS_COSINE[0])); 981 | if (!LUT_FREQUENCY_TRANSLATION_PLUS_COSINE) exit(EXIT_FAILURE); 982 | 983 | free(LUT_FREQUENCY_TRANSLATION_PLUS_SINE); 984 | LUT_FREQUENCY_TRANSLATION_PLUS_SINE = malloc(n_max *sizeof(LUT_FREQUENCY_TRANSLATION_PLUS_SINE[0])); 985 | if (!LUT_FREQUENCY_TRANSLATION_PLUS_SINE) exit(EXIT_FAILURE); 986 | 987 | for (size_t n = 0; n < n_max; n++) 988 | { 989 | const double phi = (2. * M_PI * (ft_kHz * n)) / fs_kHz; 990 | LUT_FREQUENCY_TRANSLATION_PLUS_COSINE[n] = cosf(phi); 991 | LUT_FREQUENCY_TRANSLATION_PLUS_SINE[n] = -sinf(phi); // Minus sinf! 992 | } 993 | } 994 | 995 | /* Positive frequencies shift: ft = +325kHz the signal will shift from 868.625M right to 868.95M. 996 | Negative frequencies shift: ft = -325kHz the signal will shift from 868.625M right to 868.3M. */ 997 | static void shift_freq_plus_minus325(float *iplus, float *qplus, float *iminus, float *qminus, int fs_kHz) 998 | { 999 | const int ft = 325; 1000 | static size_t n = 0; 1001 | const size_t n_max = fs_kHz/FREQ_STEP_KHZ; 1002 | #if 0 1003 | const float complex freq_shift = cosf(2.*M_PI*(ft*n)/fs_kHz) + sin(2.*M_PI*(ft*n)/fs_kHz) * _Complex_I; 1004 | n++; 1005 | #else 1006 | const float x = LUT_FREQUENCY_TRANSLATION_PLUS_COSINE[n]; 1007 | const float z = LUT_FREQUENCY_TRANSLATION_PLUS_SINE[n]; 1008 | n += ft/FREQ_STEP_KHZ; 1009 | #endif 1010 | if (n >= n_max) n -= n_max; 1011 | float ix, iz, qx, qz; 1012 | 1013 | // (i+Jq)*(x+Jz) =ix-qz + J(qx+iz) positive rotation 1014 | // (i+Jq)*(x-Jz) =ix+qz + J(qx-iz) negative rotation 1015 | // ix, qz, qx, iz are the same for boths shifts so we reuse them 1016 | // totaling 4 mul and 4 sums instead of 8 mul 4 sum. 1017 | // It works because iplus equals to iminus and qplus equals to qminus. 1018 | 1019 | ix = *iplus * x; 1020 | qx = *qplus * x; 1021 | iz = *iplus * z; 1022 | qz = *qplus * z; 1023 | 1024 | // Symmetric positive shift. 1025 | *iplus = ix - qz; 1026 | *qplus = qx + iz; 1027 | 1028 | // Symmetric negative shift . 1029 | *iminus = ix + qz; 1030 | *qminus = qx - iz; 1031 | } 1032 | 1033 | typedef void (*t1_c1_signal_chain_prototype)(float i_t1_c1, float q_t1_c1, 1034 | struct time2_algorithm_t1_c1 *t2_algo_t1_c1, 1035 | struct runlength_algorithm_t1_c1 *rl_algo_t1_c1, 1036 | float (*polar_discriminator_t1_c1_function)(float i, float q)); 1037 | 1038 | void t1_c1_signal_chain(float i_t1_c1, float q_t1_c1, 1039 | struct time2_algorithm_t1_c1 *t2_algo_t1_c1, 1040 | struct runlength_algorithm_t1_c1 *rl_algo_t1_c1, 1041 | float (*polar_discriminator_t1_c1_function)(float i, float q)) 1042 | { 1043 | static int16_t old_clock_t1_c1 = INT16_MIN; 1044 | static unsigned clock_lock_t1_c1 = 0; 1045 | 1046 | // Demodulate. 1047 | const float _delta_phi_t1_c1 = polar_discriminator_t1_c1_function(i_t1_c1, q_t1_c1); 1048 | //int16_t demodulated_signal = (INT16_MAX-1)*delta_phi; 1049 | //fwrite(&demodulated_signal, sizeof(demodulated_signal), 1, demod_out); 1050 | 1051 | // Post-filtering to prevent bit errors because of signal jitter. 1052 | float delta_phi_t1_c1 = lp_fir_butter_800kHz_100kHz_160kHz(_delta_phi_t1_c1); 1053 | //float delta_phi_t1_c1 = equalizer_t1_c1(_delta_phi_t1_c1, _delta_phi_t1_c1 >= 0.f ? 1.f : -1.f); 1054 | if (opts_remove_dc_offset) delta_phi_t1_c1 = t1_c1_remove_dc_offset_demod(delta_phi_t1_c1); 1055 | //int16_t demodulated_signal = (INT16_MAX-1)*delta_phi_t1_c1; 1056 | //fwrite(&demodulated_signal, sizeof(demodulated_signal), 1, demod_out2_t1_c1); 1057 | 1058 | // Get the bit! 1059 | unsigned bit_t1_c1 = (delta_phi_t1_c1 >= 0) ? (1u<= 0) ? INT16_MAX : INT16_MIN; 1090 | //fwrite(&clock_t1_c1, sizeof(clock_t1_c1), 1, clock_out); 1091 | 1092 | if (clock_t1_c1 > old_clock_t1_c1) 1093 | { // Clock signal rising edge detected. 1094 | clock_lock_t1_c1 = 1; 1095 | } 1096 | else if (clock_t1_c1 == INT16_MAX) 1097 | { // Clock signal is still high. 1098 | if (clock_lock_t1_c1 < opts_CLOCK_LOCK_THRESHOLD_T1_C1) 1099 | { // Skip up to (opts_CLOCK_LOCK_THRESHOLD_T1_C1 - 1) clock bits 1100 | // to get closer to the middle of the data bit. 1101 | clock_lock_t1_c1++; 1102 | } 1103 | else if (clock_lock_t1_c1 == opts_CLOCK_LOCK_THRESHOLD_T1_C1) 1104 | { // Sample data bit at CLOCK_LOCK_THRESHOLD_T1_C1 clock bit position. 1105 | clock_lock_t1_c1++; 1106 | time2_algorithm_t1_c1(bit_t1_c1, rssi_t1_c1, t2_algo_t1_c1); 1107 | //int16_t u = bit_t1_c1 ? (INT16_MAX-1) : 0; 1108 | //fwrite(&u, sizeof(u), 1, bits_out); 1109 | } 1110 | } 1111 | old_clock_t1_c1 = clock_t1_c1; 1112 | // --- clock recovery section end --- 1113 | } 1114 | #endif 1115 | // --- time2 algorithm section end --- 1116 | } 1117 | 1118 | void t1_c1_signal_chain_empty(float i_t1_c1, float q_t1_c1, 1119 | struct time2_algorithm_t1_c1 *t2_algo_t1_c1, 1120 | struct runlength_algorithm_t1_c1 *rl_algo_t1_c1, 1121 | float (*polar_discriminator_t1_c1_function)(float i, float q)) 1122 | { 1123 | } 1124 | 1125 | typedef void (*s1_signal_chain_prototype)(float i_s1, float q_s1, 1126 | struct time2_algorithm_s1 *t2_algo_s1, 1127 | struct runlength_algorithm_s1 *rl_algo_s1, 1128 | float (*polar_discriminator_s1_function)(float i, float q)); 1129 | 1130 | void s1_signal_chain(float i_s1, float q_s1, 1131 | struct time2_algorithm_s1 *t2_algo_s1, 1132 | struct runlength_algorithm_s1 *rl_algo_s1, 1133 | float (*polar_discriminator_s1_function)(float i, float q)) 1134 | { 1135 | static int16_t old_clock_s1 = INT16_MIN; 1136 | static unsigned clock_lock_s1 = 0; 1137 | 1138 | // Demodulate. 1139 | const float _delta_phi_s1 = polar_discriminator_s1_function(i_s1, q_s1); 1140 | //int16_t demodulated_signal = (INT16_MAX-1)*delta_phi; 1141 | //fwrite(&demodulated_signal, sizeof(demodulated_signal), 1, demod_out); 1142 | 1143 | // Post-filtering to prevent bit errors because of signal jitter. 1144 | float delta_phi_s1 = lp_fir_butter_800kHz_32kHz_36kHz(_delta_phi_s1); 1145 | //float delta_phi_s1 = equalizer_s1(_delta_phi_s1, _delta_phi_s1 >= 0.f ? 1.f : -1.f); 1146 | if (opts_remove_dc_offset) delta_phi_s1 = s1_remove_dc_offset_demod(delta_phi_s1); 1147 | //int16_t demodulated_signal = (INT16_MAX-1)*delta_phi_s1; 1148 | //fwrite(&demodulated_signal, sizeof(demodulated_signal), 1, demod_out2_s1); 1149 | 1150 | // Get the bit! 1151 | unsigned bit_s1 = (delta_phi_s1 >= 0) ? (1u<= 0) ? INT16_MAX : INT16_MIN; 1182 | //fwrite(&clock_s1, sizeof(clock_s1), 1, clock_out); 1183 | 1184 | if (clock_s1 > old_clock_s1) 1185 | { // Clock signal rising edge detected. 1186 | clock_lock_s1 = 1; 1187 | } 1188 | else if (clock_s1 == INT16_MAX) 1189 | { // Clock signal is still high. 1190 | if (clock_lock_s1 < opts_CLOCK_LOCK_THRESHOLD_S1) 1191 | { // Skip up to (opts_CLOCK_LOCK_THRESHOLD_S1 - 1) clock bits 1192 | // to get closer to the middle of the data bit. 1193 | clock_lock_s1++; 1194 | } 1195 | else if (clock_lock_s1 == opts_CLOCK_LOCK_THRESHOLD_S1) 1196 | { // Sample data bit at CLOCK_LOCK_THRESHOLD_S1 clock bit position. 1197 | clock_lock_s1++; 1198 | time2_algorithm_s1(bit_s1, rssi_s1, t2_algo_s1); 1199 | //int16_t u = bit ? (INT16_MAX-1) : 0; 1200 | //fwrite(&u, sizeof(u), 1, bits_out); 1201 | } 1202 | } 1203 | old_clock_s1 = clock_s1; 1204 | // --- clock recovery section end --- 1205 | } 1206 | #endif 1207 | // --- time2 algorithm section end --- 1208 | } 1209 | 1210 | void s1_signal_chain_empty(float i_s1, float q_s1, 1211 | struct time2_algorithm_s1 *t2_algo_s1, 1212 | struct runlength_algorithm_s1 *rl_algo_s1, 1213 | float (*polar_discriminator_s1_function)(float i, float q)) 1214 | { 1215 | } 1216 | 1217 | int main(int argc, char *argv[]) 1218 | { 1219 | #if WINDOWS_BUILD == 1 1220 | _setmode(_fileno(stdin), _O_BINARY); 1221 | #else 1222 | #ifndef DEBUG 1223 | if (argc == 1 && isatty(0)) 1224 | { 1225 | // Standard input is a terminal, print help. 1226 | print_usage(argv[0]); 1227 | exit(0); 1228 | } 1229 | #endif /* DEBUG */ 1230 | #endif /* WINDOWS_BUILD == 1 */ 1231 | 1232 | process_options(argc, argv); 1233 | 1234 | #if CHECK_FLOW == 1 1235 | struct sigaction old_alarm; 1236 | struct sigaction new_alarm; 1237 | 1238 | if (opts_check_flow) 1239 | { 1240 | new_alarm.sa_handler = sig_alarm_handler; 1241 | sigemptyset(&new_alarm.sa_mask); 1242 | new_alarm.sa_flags = 0; 1243 | 1244 | fprintf(stderr, "rtl_wmbus: monitoring flow\n"); 1245 | sigaction(SIGALRM, &new_alarm, &old_alarm); 1246 | } 1247 | #endif 1248 | 1249 | __attribute__((__aligned__(16))) uint8_t samples[4096]; 1250 | const int fs_kHz = opts_decimation_rate*800; // Sample rate [kHz] as a multiple of 800 kHz. 1251 | float i_t1_c1, q_t1_c1; 1252 | float i_s1, q_s1; 1253 | 1254 | 1255 | unsigned decimation_rate_index = 0; 1256 | 1257 | struct time2_algorithm_t1_c1 t2_algo_t1_c1; 1258 | time2_algorithm_t1_c1_reset(&t2_algo_t1_c1); 1259 | 1260 | struct time2_algorithm_s1 t2_algo_s1; 1261 | time2_algorithm_s1_reset(&t2_algo_s1); 1262 | 1263 | struct runlength_algorithm_t1_c1 rl_algo_t1_c1; 1264 | runlength_algorithm_reset_t1_c1(&rl_algo_t1_c1); 1265 | 1266 | struct runlength_algorithm_s1 rl_algo_s1; 1267 | runlength_algorithm_reset_s1(&rl_algo_s1); 1268 | 1269 | t1_c1_signal_chain_prototype process_t1_c1_chain = opts_t1_c1_processing_enabled ? t1_c1_signal_chain: t1_c1_signal_chain_empty; 1270 | s1_signal_chain_prototype process_s1_chain = opts_s1_processing_enabled ? s1_signal_chain : s1_signal_chain_empty; 1271 | 1272 | float (*polar_discriminator_t1_c1_function)(float i, float q) = opts_accurate_atan ? polar_discriminator_t1_c1 : polar_discriminator_t1_c1_inaccurate; 1273 | float (*polar_discriminator_s1_function)(float i, float q) = opts_accurate_atan ? polar_discriminator_s1 : polar_discriminator_s1_inaccurate; 1274 | 1275 | FILE *input = stdin; 1276 | //input = fopen("samples/samples2.bin", "rb"); 1277 | //input = fopen("samples/kamstrup.bin", "rb"); 1278 | //input = fopen("samples/c1_mode_b.bin", "rb"); 1279 | //input = fopen("samples/t1_c1a_mixed.bin", "rb"); 1280 | //input = fopen("rtlsdr_868.95M_1M6_amiplus_notdecoded.bin.002", "rb"); 1281 | //input = get_net("localhost", 14423); 1282 | 1283 | if (input == NULL) 1284 | { 1285 | fprintf(stderr, "File open error.\n"); 1286 | return EXIT_FAILURE; 1287 | } 1288 | 1289 | //demod_out = fopen("demod.bin", "wb"); 1290 | //demod_out2_t1_c1 = fopen("demod2_t1_c1.bin", "wb"); 1291 | //demod_out2_s1 = fopen("demod2_s1.bin", "wb"); 1292 | //clock_out = fopen("clock.bin", "wb"); 1293 | //bits_out = fopen("bits.bin", "wb"); 1294 | //rawbits_out = fopen("rawbits.bin", "wb"); 1295 | 1296 | setup_lookup_tables_for_frequency_translation(fs_kHz); 1297 | 1298 | while (!feof(input)) 1299 | { 1300 | if (opts_check_flow) START_ALARM(); 1301 | size_t read_items = fread(samples, sizeof(samples), 1, input); 1302 | if (opts_check_flow) STOP_ALARM(); 1303 | 1304 | if (1 != read_items) 1305 | { 1306 | // End of file?.. 1307 | break; 1308 | } 1309 | 1310 | for (size_t k = 0; k < sizeof(samples)/sizeof(samples[0]); k += 2) // +2 : i and q interleaved 1311 | { 1312 | const float i_unfilt = ((float)(samples[k]) - 127.5f); 1313 | const float q_unfilt = ((float)(samples[k + 1]) - 127.5f); 1314 | 1315 | // rtl_sdr -f 868.35M -s 2400000 - 2>/dev/null | build/rtl_wmbus -d 3 1316 | //shift_freq(&i_unfilt, &q_unfilt, 600, 2400); 1317 | 1318 | float i_t1_c1_unfilt = i_unfilt; 1319 | float q_t1_c1_unfilt = q_unfilt; 1320 | 1321 | float i_s1_unfilt = i_unfilt; 1322 | float q_s1_unfilt = q_unfilt; 1323 | 1324 | if (opts_s1_t1_c1_simultaneously) 1325 | { 1326 | shift_freq_plus_minus325(&i_t1_c1_unfilt, &q_t1_c1_unfilt, &i_s1_unfilt, &q_s1_unfilt, fs_kHz); 1327 | //shift_freq_plus_minus325(&i_s1_unfilt, &q_s1_unfilt, &i_t1_c1_unfilt, &q_t1_c1_unfilt, fs_kHz); // Just to test T1/C1 at 869.275M. 1328 | } 1329 | 1330 | // Low-Pass-Filtering before decimation is necessary, to ensure 1331 | // that i and q signals don't contain frequencies above new sample 1332 | // rate. Moving average can be viewed as a low pass filter. 1333 | i_t1_c1 = moving_average_t1_c1(i_t1_c1_unfilt, 0); 1334 | q_t1_c1 = moving_average_t1_c1(q_t1_c1_unfilt, 1); 1335 | 1336 | #if 0 1337 | equalizer_complex_t1_c1(&i_t1_c1, &q_t1_c1); 1338 | #endif 1339 | 1340 | // Low-Pass-Filtering before decimation is necessary, to ensure 1341 | // that i and q signals don't contain frequencies above new sample 1342 | // rate. Moving average can be viewed as a low pass filter. 1343 | i_s1 = moving_average_s1(i_s1_unfilt, 0); 1344 | q_s1 = moving_average_s1(q_s1_unfilt, 1); 1345 | 1346 | #if 0 1347 | equalizer_complex_s1(&i_s1, &q_s1); // FIXME: Function does not exist. 1348 | #endif 1349 | 1350 | ++decimation_rate_index; 1351 | if (decimation_rate_index < opts_decimation_rate) continue; 1352 | decimation_rate_index = 0; 1353 | 1354 | process_t1_c1_chain(i_t1_c1, q_t1_c1, &t2_algo_t1_c1, &rl_algo_t1_c1, polar_discriminator_t1_c1_function); 1355 | process_s1_chain(i_s1, q_s1, &t2_algo_s1, &rl_algo_s1, polar_discriminator_s1_function); 1356 | } 1357 | } 1358 | 1359 | if (opts_check_flow) 1360 | { 1361 | #if CHECK_FLOW == 1 1362 | sigaction(SIGALRM, &old_alarm, NULL); 1363 | #endif 1364 | } 1365 | 1366 | if (input != stdin) fclose(input); 1367 | if (demod_out2_t1_c1 != NULL) fclose(demod_out2_t1_c1); 1368 | if (demod_out2_s1 != NULL) fclose(demod_out2_s1); 1369 | free(LUT_FREQUENCY_TRANSLATION_PLUS_COSINE); 1370 | free(LUT_FREQUENCY_TRANSLATION_PLUS_SINE); 1371 | return EXIT_SUCCESS; 1372 | } 1373 | -------------------------------------------------------------------------------- /rtl_wmbus.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import traceback 4 | import subprocess 5 | import psutil 6 | import signal 7 | from time import time 8 | 9 | 10 | __rtl_sdr = None 11 | __rtl_wmbus = None 12 | 13 | 14 | def __signal_handler(signal, frame): 15 | global __rtl_sdr, __rtl_wmbus 16 | 17 | if __rtl_wmbus: 18 | #print("Terminating rtl_wmbus") 19 | __rtl_wmbus.kill() 20 | __rtl_wmbus.wait() 21 | 22 | if __rtl_sdr: 23 | #print("Terminating rtl_sdr") 24 | __rtl_sdr.kill() 25 | __rtl_sdr.wait() 26 | 27 | 28 | def _main(args): 29 | global __rtl_sdr, __rtl_wmbus 30 | 31 | #"/C/Program Files/PothosSDR/bin/rtl_sdr" -f 868.95M -s 1600000 - 2>/dev/null | build/rtl_wmbus_x64.exe -v 32 | #"C:\Program Files\PothosSDR\bin\rtl_sdr" -f 868.95M -s 1600000 - 2>NUL | build\rtl_wmbus_x64.exe -v 33 | 34 | try: 35 | signal.signal(signal.SIGINT, __signal_handler) 36 | 37 | __rtl_sdr = subprocess.Popen([r"c:\Program Files\PothosSDR\bin\rtl_sdr", "-f", "868.95M", "-s", "1600000", "-"], stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) 38 | psutil.Process(__rtl_sdr.pid).nice(psutil.REALTIME_PRIORITY_CLASS) 39 | 40 | __rtl_wmbus = subprocess.Popen([r"d:\rtl-wmbus\build\rtl_wmbus_x64.exe", "-v"], stdin=__rtl_sdr.stdout, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) 41 | psutil.Process(__rtl_wmbus.pid).nice(psutil.REALTIME_PRIORITY_CLASS) 42 | 43 | psutil.Process(os.getpid()).nice(psutil.REALTIME_PRIORITY_CLASS) 44 | 45 | #__rtl_sdr.stdout.close() # Allow rtl_sdr to receive a SIGPIPE if rtl_wmbus exits. 46 | 47 | t1 = time() 48 | rate = 0 49 | while __rtl_sdr.poll() is None and __rtl_wmbus.poll() is None: 50 | s = __rtl_wmbus.stdout.readline().decode('utf-8').rstrip('\n').rstrip('\r') 51 | t2 = time() 52 | 53 | if s.find("T1;1") >= 0 or s.find("C1;1") >= 0 or s.find("S1;1") >= 0: 54 | rate += 1 55 | print(s) 56 | else: 57 | pass 58 | 59 | dt = t2 - t1 60 | if dt >= 10: 61 | #print(rate/dt, file=sys.stderr) 62 | t1 = t2 63 | rate = 0 64 | 65 | return 0 66 | 67 | except Exception as e: 68 | print(traceback.print_exc()) 69 | return 1 70 | 71 | 72 | if __name__ == '__main__': 73 | sys.exit(_main(sys.argv)) 74 | -------------------------------------------------------------------------------- /rtl_wmbus.vpj: -------------------------------------------------------------------------------- 1 | 2 | 7 | 14 | 15 | 24 | 25 | 26 | 35 | 36 | 37 | 44 | 45 | 46 | 53 | 54 | 55 | 64 | 65 | 66 | 75 | 76 | 77 | 81 | 82 | 83 | 89 | 92 | 93 | 94 | 95 | 99 | 100 | 101 | 105 | 106 | 107 | 111 | 112 | 113 | 117 | 118 | 119 | 123 | 124 | 125 | 126 | 127 | 130 | 131 | 132 | 139 | 140 | 149 | 150 | 151 | 160 | 161 | 162 | 169 | 170 | 171 | 178 | 179 | 180 | 189 | 190 | 191 | 200 | 201 | 202 | 206 | 207 | 208 | 214 | 217 | 218 | 219 | 220 | 224 | 225 | 226 | 230 | 231 | 232 | 236 | 237 | 238 | 242 | 243 | 244 | 248 | 249 | 250 | 251 | 252 | 255 | 256 | 257 | 258 | 262 | 266 | 270 | 274 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | -------------------------------------------------------------------------------- /rtl_wmbus.vpw: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /rtl_wmbus_util.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #if WINDOWS_BUILD 5 | #else 6 | #include 7 | #endif 8 | 9 | 10 | inline int make_time_string(char* timestamp, size_t timestamp_size) 11 | { 12 | memset(timestamp, 0, timestamp_size); 13 | 14 | #if WINDOWS_BUILD 15 | time_t now; 16 | if (time(&now) == NULL) 17 | return -1; 18 | 19 | struct tm* timeinfo = gmtime(&now); 20 | if (timeinfo == NULL) 21 | return -1; 22 | 23 | strftime(timestamp, timestamp_size, "%Y-%m-%d %H:%M:%S.000000", timeinfo); 24 | #else 25 | struct timeval tv; 26 | if (gettimeofday(&tv, NULL) != 0) 27 | return -1; 28 | 29 | struct tm timeinfo; 30 | if (localtime_r(&tv.tv_sec, &timeinfo) == NULL) 31 | return -1; 32 | 33 | char fmt[timestamp_size]; 34 | strftime(fmt, sizeof(fmt), "%Y-%m-%d %H:%M:%S.%%06u", &timeinfo); 35 | snprintf(timestamp, timestamp_size, fmt, tv.tv_usec); 36 | #endif 37 | 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /s1_packet_decoder.h: -------------------------------------------------------------------------------- 1 | #ifndef S1_PACKET_DECODER_H 2 | #define S1_PACKET_DECODER_H 3 | 4 | /*- 5 | * Copyright (c) 2021 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | static const uint8_t MANCHESTER_IEEE_802_3[4] = { 36 | 0xFF, 0x01, 0x00, 0xFF // According to wireless MBus spec.: "01b” representing a “one”; "10b" representing a "zero". 37 | }; 38 | 39 | struct s1_packet_decoder_work; 40 | typedef void (*s1_packet_decoder_state)(unsigned bit, struct s1_packet_decoder_work *decoder); 41 | 42 | static void s1_idle(unsigned bit, struct s1_packet_decoder_work *decoder); 43 | static void s1_done(unsigned bit, struct s1_packet_decoder_work *decoder); 44 | static void s1_rx_bit(unsigned bit, struct s1_packet_decoder_work *decoder); 45 | static void s1_rx_bit2(unsigned bit, struct s1_packet_decoder_work *decoder); 46 | 47 | static void s1_rx_first_mode_bit(unsigned bit, struct s1_packet_decoder_work *decoder); 48 | static void s1_rx_last_mode_bit(unsigned bit, struct s1_packet_decoder_work *decoder); 49 | 50 | static void s1_rx_first_lfield_bit(unsigned bit, struct s1_packet_decoder_work *decoder); 51 | static void s1_rx_last_lfield_bit(unsigned bit, struct s1_packet_decoder_work *decoder); 52 | 53 | static void s1_rx_first_data_bit(unsigned bit, struct s1_packet_decoder_work *decoder); 54 | static void s1_rx_last_data_bit(unsigned bit, struct s1_packet_decoder_work *decoder); 55 | 56 | 57 | static const s1_packet_decoder_state s1_decoder_states[] = 58 | { 59 | s1_idle, // 0 60 | 61 | s1_rx_first_lfield_bit, // 1 62 | s1_rx_bit2, // 2 63 | s1_rx_bit, // 3 64 | s1_rx_bit2, // 4 65 | s1_rx_bit, // 5 66 | s1_rx_bit2, // 6 67 | s1_rx_bit, // 7 68 | s1_rx_bit2, // 8 69 | s1_rx_bit, // 9 70 | s1_rx_bit2, // 10 71 | s1_rx_bit, // 11 72 | s1_rx_bit2, // 12 73 | s1_rx_bit, // 13 74 | s1_rx_bit2, // 14 75 | s1_rx_bit, // 15 76 | s1_rx_last_lfield_bit, // 16 77 | 78 | s1_rx_first_data_bit, // 17 79 | s1_rx_bit2, // 18 80 | s1_rx_bit, // 19 81 | s1_rx_bit2, // 20 82 | s1_rx_bit, // 21 83 | s1_rx_bit2, // 22 84 | s1_rx_bit, // 23 85 | s1_rx_bit2, // 24 86 | s1_rx_bit, // 25 87 | s1_rx_bit2, // 26 88 | s1_rx_bit, // 27 89 | s1_rx_bit2, // 28 90 | s1_rx_bit, // 29 91 | s1_rx_bit2, // 30 92 | s1_rx_bit, // 31 93 | s1_rx_last_data_bit, // 32 94 | 95 | s1_done, // 33 96 | }; 97 | 98 | 99 | struct s1_packet_decoder_work 100 | { 101 | const s1_packet_decoder_state *state; 102 | unsigned current_rssi; 103 | unsigned packet_rssi; 104 | union 105 | { 106 | unsigned flags; 107 | struct 108 | { 109 | unsigned unused: 1; 110 | unsigned crc_ok: 1; 111 | }; 112 | }; 113 | unsigned l; 114 | unsigned L; 115 | unsigned mode; 116 | unsigned byte; 117 | __attribute__((__aligned__(16))) uint8_t packet[290]; // max. packet length with L- and all CRC-Fields 118 | char timestamp[64]; 119 | }; 120 | 121 | static int in_rx_s1_packet_decoder(struct s1_packet_decoder_work *decoder) 122 | { 123 | return (decoder->state == &s1_decoder_states[0]) ? 0 : 1; 124 | } 125 | 126 | static void reset_s1_packet_decoder(struct s1_packet_decoder_work *decoder) 127 | { 128 | memset(decoder, 0, sizeof(*decoder)); 129 | decoder->state = &s1_decoder_states[0]; 130 | } 131 | 132 | static void s1_idle(unsigned bit, struct s1_packet_decoder_work *decoder) 133 | { 134 | if (!(bit & PACKET_PREAMBLE_DETECTED_MASK)) 135 | { 136 | reset_s1_packet_decoder(decoder); 137 | } 138 | } 139 | 140 | static void s1_done(unsigned bit, struct s1_packet_decoder_work *decoder) 141 | { 142 | (void)bit; 143 | (void)decoder; 144 | } 145 | 146 | static void s1_rx_bit(unsigned bit, struct s1_packet_decoder_work *decoder) 147 | { 148 | decoder->byte <<= 1; 149 | decoder->byte |= (bit & PACKET_DATABIT_MASK); 150 | } 151 | 152 | static void s1_rx_bit2(unsigned bit, struct s1_packet_decoder_work *decoder) 153 | { 154 | decoder->byte <<= 1; 155 | decoder->byte |= (bit & PACKET_DATABIT_MASK); 156 | 157 | const unsigned b = MANCHESTER_IEEE_802_3[decoder->byte & 0b11]; 158 | 159 | if (b == 0xFFu) 160 | { 161 | reset_s1_packet_decoder(decoder); 162 | return; 163 | } 164 | 165 | decoder->byte >>= 2; 166 | decoder->byte <<= 1; 167 | decoder->byte |= b; 168 | } 169 | 170 | static void s1_rx_first_lfield_bit(unsigned bit, struct s1_packet_decoder_work *decoder) 171 | { 172 | decoder->byte = (bit & PACKET_DATABIT_MASK); 173 | decoder->packet_rssi = decoder->current_rssi; 174 | } 175 | 176 | static void s1_rx_last_lfield_bit(unsigned bit, struct s1_packet_decoder_work *decoder) 177 | { 178 | decoder->byte <<= 1; 179 | decoder->byte |= (bit & PACKET_DATABIT_MASK); 180 | 181 | const unsigned b = MANCHESTER_IEEE_802_3[decoder->byte & 0b11]; 182 | 183 | if (b == 0xFFu) 184 | { 185 | reset_s1_packet_decoder(decoder); 186 | return; 187 | } 188 | 189 | decoder->byte >>= 2; 190 | decoder->byte <<= 1; 191 | decoder->byte |= b; 192 | 193 | decoder->L = decoder->byte; 194 | decoder->l = 0; 195 | decoder->packet[decoder->l++] = decoder->L; 196 | decoder->L = FULL_TLG_LENGTH_FROM_L_FIELD[decoder->L]; 197 | } 198 | 199 | static void s1_rx_first_data_bit(unsigned bit, struct s1_packet_decoder_work *decoder) 200 | { 201 | decoder->byte = (bit & PACKET_DATABIT_MASK); 202 | } 203 | 204 | static void s1_rx_last_data_bit(unsigned bit, struct s1_packet_decoder_work *decoder) 205 | { 206 | decoder->byte <<= 1; 207 | decoder->byte |= (bit & PACKET_DATABIT_MASK); 208 | 209 | const unsigned b = MANCHESTER_IEEE_802_3[decoder->byte & 0b11]; 210 | 211 | if (b == 0xFFu) 212 | { 213 | reset_s1_packet_decoder(decoder); 214 | return; 215 | } 216 | 217 | decoder->byte >>= 2; 218 | decoder->byte <<= 1; 219 | decoder->byte |= b; 220 | 221 | decoder->packet[decoder->l++] = decoder->byte; 222 | 223 | if (decoder->l < decoder->L) 224 | { 225 | decoder->state = &s1_decoder_states[17]; // s1_rx_first_data_bit 226 | } 227 | else 228 | { 229 | make_time_string(decoder->timestamp, sizeof(decoder->timestamp)); 230 | } 231 | } 232 | 233 | static void s1_packet_decoder(unsigned bit, unsigned rssi, struct s1_packet_decoder_work *decoder, const char *algorithm) 234 | { 235 | decoder->current_rssi = rssi; 236 | 237 | (*decoder->state++)(bit, decoder); 238 | 239 | if (*decoder->state == s1_idle) 240 | { 241 | // nothing 242 | } 243 | else if (*decoder->state == s1_done) 244 | { 245 | decoder->crc_ok = check_calc_crc_wmbus(decoder->packet, decoder->L) ? 1 : 0; 246 | 247 | if (!opts_show_used_algorithm) algorithm = ""; 248 | fprintf(stdout, "%s%s;%u;%u;%s;%u;%u;%08X;", algorithm, "S1", 249 | decoder->crc_ok, 250 | 1, 251 | decoder->timestamp, 252 | decoder->packet_rssi, 253 | rssi, 254 | get_serial(decoder->packet)); 255 | 256 | #if 0 257 | fprintf(stdout, "0x"); 258 | for (size_t l = 0; l < decoder->L; l++) fprintf(stdout, "%02x", decoder->packet[l]); 259 | fprintf(stdout, ";"); 260 | #endif 261 | 262 | #if 1 263 | decoder->L = cook_pkt(decoder->packet, decoder->L); 264 | fprintf(stdout, "0x"); 265 | for (size_t l = 0; l < decoder->L; l++) fprintf(stdout, "%02x", decoder->packet[l]); 266 | #endif 267 | 268 | fprintf(stdout, "\n"); 269 | fflush(stdout); 270 | 271 | reset_s1_packet_decoder(decoder); 272 | } 273 | else 274 | { 275 | // Stop receiving packet if current rssi below threshold. 276 | // The current packet seems to be collided with an another one. 277 | if (rssi < PACKET_CAPTURE_THRESHOLD) 278 | { 279 | reset_s1_packet_decoder(decoder); 280 | } 281 | } 282 | } 283 | 284 | #endif /* S1_PACKET_DECODER_H */ 285 | 286 | -------------------------------------------------------------------------------- /samples/rtlsdr_868.625M_2M4_issue48.cu8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xaelsouth/rtl-wmbus/34684e655df70867ac86e799561ec6512c45be0a/samples/rtlsdr_868.625M_2M4_issue48.cu8 -------------------------------------------------------------------------------- /samples/rtlsdr_868.950M_1M6_issue47.cu8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xaelsouth/rtl-wmbus/34684e655df70867ac86e799561ec6512c45be0a/samples/rtlsdr_868.950M_1M6_issue47.cu8 -------------------------------------------------------------------------------- /samples/rtlsdr_868.950M_1M6_issue49.cu8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xaelsouth/rtl-wmbus/34684e655df70867ac86e799561ec6512c45be0a/samples/rtlsdr_868.950M_1M6_issue49.cu8 -------------------------------------------------------------------------------- /samples/rtlsdr_868.950M_1M6_samples2.cu8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xaelsouth/rtl-wmbus/34684e655df70867ac86e799561ec6512c45be0a/samples/rtlsdr_868.950M_1M6_samples2.cu8 -------------------------------------------------------------------------------- /t1_c1_packet_decoder.h: -------------------------------------------------------------------------------- 1 | #ifndef T1_C1_PACKET_DECODER_H 2 | #define T1_C1_PACKET_DECODER_H 3 | 4 | /*- 5 | * Copyright (c) 2021 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #if !defined(PACKET_CAPTURE_THRESHOLD) 36 | #define PACKET_CAPTURE_THRESHOLD 5u 37 | #endif 38 | 39 | #define C1_MODE_A 0b010101001100 40 | #define C1_MODE_B 0b010101000011 41 | #define C1_MODE_AB_TRAILER 0b1101 42 | 43 | #define PACKET_DATABIT_SHIFT (0u) 44 | #define PACKET_PREAMBLE_DETECTED_SHIFT (1u) 45 | 46 | #define PACKET_DATABIT_MASK (1u<state == &t1_c1_decoder_states[0]) ? 0 : 1; 264 | } 265 | 266 | static void reset_t1_c1_packet_decoder(struct t1_c1_packet_decoder_work *decoder) 267 | { 268 | memset(decoder, 0, sizeof(*decoder)); 269 | decoder->state = &t1_c1_decoder_states[0]; 270 | } 271 | 272 | static void t1_c1_idle(unsigned bit, struct t1_c1_packet_decoder_work *decoder) 273 | { 274 | if (!(bit & PACKET_PREAMBLE_DETECTED_MASK)) 275 | { 276 | reset_t1_c1_packet_decoder(decoder); 277 | } 278 | } 279 | 280 | static void t1_c1_done(unsigned bit, struct t1_c1_packet_decoder_work *decoder) 281 | { 282 | (void)bit; 283 | (void)decoder; 284 | } 285 | 286 | static void t1_c1_rx_bit(unsigned bit, struct t1_c1_packet_decoder_work *decoder) 287 | { 288 | decoder->byte <<= 1; 289 | decoder->byte |= (bit & PACKET_DATABIT_MASK); 290 | } 291 | 292 | static void t1_rx_high_nibble_first_lfield_bit(unsigned bit, struct t1_c1_packet_decoder_work *decoder) 293 | { 294 | decoder->byte = (bit & PACKET_DATABIT_MASK); 295 | decoder->packet_rssi = decoder->current_rssi; 296 | } 297 | 298 | static void t1_rx_high_nibble_last_lfield_bit(unsigned bit, struct t1_c1_packet_decoder_work *decoder) 299 | { 300 | decoder->byte <<= 1; 301 | decoder->byte |= (bit & PACKET_DATABIT_MASK); 302 | decoder->mode = decoder->byte; 303 | 304 | decoder->L = HIGH_NIBBLE_3OUTOF6[decoder->byte]; 305 | decoder->flags = 0; 306 | } 307 | 308 | static void t1_rx_low_nibble_first_lfield_bit(unsigned bit, struct t1_c1_packet_decoder_work *decoder) 309 | { 310 | decoder->byte = (bit & PACKET_DATABIT_MASK); 311 | } 312 | 313 | static void t1_rx_low_nibble_last_lfield_bit(unsigned bit, struct t1_c1_packet_decoder_work *decoder) 314 | { 315 | decoder->byte <<= 1; 316 | decoder->byte |= (bit & PACKET_DATABIT_MASK); 317 | decoder->mode <<= 6; 318 | decoder->mode |= decoder->byte; 319 | 320 | const unsigned byte = LOW_NIBBLE_3OUTOF6[decoder->byte]; 321 | 322 | if (decoder->L == 0xFFu || byte == 0xFFu) 323 | { 324 | if (decoder->mode == C1_MODE_A) 325 | { 326 | decoder->b_frame_type = 0; 327 | decoder->state = &t1_c1_decoder_states[26]; // c1_rx_first_mode_bit 328 | } 329 | else if (decoder->mode == C1_MODE_B) 330 | { 331 | decoder->b_frame_type = 1; 332 | decoder->state = &t1_c1_decoder_states[26]; // c1_rx_first_mode_bit 333 | } 334 | else 335 | { 336 | reset_t1_c1_packet_decoder(decoder); 337 | } 338 | } 339 | else 340 | { 341 | decoder->b_frame_type = 0; 342 | decoder->c1_packet = 0; 343 | 344 | decoder->L |= byte; 345 | decoder->l = 0; 346 | decoder->packet[decoder->l++] = decoder->L; 347 | decoder->L = FULL_TLG_LENGTH_FROM_L_FIELD[decoder->L]; 348 | } 349 | } 350 | 351 | static void t1_rx_high_nibble_first_data_bit(unsigned bit, struct t1_c1_packet_decoder_work *decoder) 352 | { 353 | decoder->byte = (bit & PACKET_DATABIT_MASK); 354 | } 355 | 356 | static void t1_rx_high_nibble_last_data_bit(unsigned bit, struct t1_c1_packet_decoder_work *decoder) 357 | { 358 | decoder->byte <<= 1; 359 | decoder->byte |= (bit & PACKET_DATABIT_MASK); 360 | 361 | const unsigned byte = HIGH_NIBBLE_3OUTOF6[decoder->byte]; 362 | 363 | if (byte == 0xFFu) decoder->err_3outof = 1; 364 | 365 | decoder->packet[decoder->l] = byte; 366 | } 367 | 368 | static void t1_rx_low_nibble_first_data_bit(unsigned bit, struct t1_c1_packet_decoder_work *decoder) 369 | { 370 | decoder->byte = (bit & PACKET_DATABIT_MASK); 371 | } 372 | 373 | static void t1_rx_low_nibble_last_data_bit(unsigned bit, struct t1_c1_packet_decoder_work *decoder) 374 | { 375 | decoder->byte <<= 1; 376 | decoder->byte |= (bit & PACKET_DATABIT_MASK); 377 | 378 | const unsigned byte = LOW_NIBBLE_3OUTOF6[decoder->byte]; 379 | 380 | if (byte == 0xFFu) decoder->err_3outof = 1; 381 | 382 | decoder->packet[decoder->l++] |= byte; 383 | 384 | if (decoder->l < decoder->L) 385 | { 386 | decoder->state = &t1_c1_decoder_states[13]; // rx_high_nibble_first_data_bit 387 | } 388 | else 389 | { 390 | make_time_string(decoder->timestamp, sizeof(decoder->timestamp)); 391 | } 392 | } 393 | 394 | static void c1_rx_first_mode_bit(unsigned bit, struct t1_c1_packet_decoder_work *decoder) 395 | { 396 | decoder->byte = (bit & PACKET_DATABIT_MASK); 397 | } 398 | 399 | static void c1_rx_last_mode_bit(unsigned bit, struct t1_c1_packet_decoder_work *decoder) 400 | { 401 | decoder->byte <<= 1; 402 | decoder->byte |= (bit & PACKET_DATABIT_MASK); 403 | 404 | decoder->mode <<= 4; 405 | decoder->mode |= decoder->byte; 406 | 407 | if (decoder->byte == C1_MODE_AB_TRAILER) 408 | { 409 | decoder->c1_packet = 1; 410 | } 411 | else 412 | { 413 | reset_t1_c1_packet_decoder(decoder); 414 | } 415 | } 416 | 417 | static void c1_rx_first_lfield_bit(unsigned bit, struct t1_c1_packet_decoder_work *decoder) 418 | { 419 | decoder->byte = (bit & PACKET_DATABIT_MASK); 420 | } 421 | 422 | static void c1_rx_last_lfield_bit(unsigned bit, struct t1_c1_packet_decoder_work *decoder) 423 | { 424 | decoder->byte <<= 1; 425 | decoder->byte |= (bit & PACKET_DATABIT_MASK); 426 | 427 | decoder->L = decoder->byte; 428 | decoder->l = 0; 429 | decoder->packet[decoder->l++] = decoder->L; 430 | if (decoder->b_frame_type) 431 | { 432 | decoder->L = get_mode_b_tlg_length(decoder->L); 433 | } 434 | else 435 | { 436 | decoder->L = FULL_TLG_LENGTH_FROM_L_FIELD[decoder->L]; 437 | } 438 | } 439 | 440 | static void c1_rx_first_data_bit(unsigned bit, struct t1_c1_packet_decoder_work *decoder) 441 | { 442 | decoder->byte = (bit & PACKET_DATABIT_MASK); 443 | } 444 | 445 | static void c1_rx_last_data_bit(unsigned bit, struct t1_c1_packet_decoder_work *decoder) 446 | { 447 | decoder->byte <<= 1; 448 | decoder->byte |= (bit & PACKET_DATABIT_MASK); 449 | 450 | decoder->packet[decoder->l++] = decoder->byte; 451 | 452 | if (decoder->l < decoder->L) 453 | { 454 | decoder->state = &t1_c1_decoder_states[38]; // c1_rx_first_data_bit 455 | } 456 | else 457 | { 458 | make_time_string(decoder->timestamp, sizeof(decoder->timestamp)); 459 | } 460 | } 461 | 462 | 463 | static uint16_t calc_crc_wmbus(const uint8_t *data, size_t datalen) 464 | { 465 | uint16_t crc = 0; 466 | while (datalen--) crc = CRC16_DNP_TABLE[*data++ ^ (crc >> 8)] ^ (crc << 8); 467 | crc = ~crc; 468 | return crc; 469 | } 470 | 471 | static bool check_calc_crc_wmbus(const uint8_t *data, size_t datalen) 472 | { 473 | bool crc_ok = false; 474 | uint16_t crc1, crc2; 475 | 476 | if (datalen >= 12) 477 | { 478 | crc1 = calc_crc_wmbus(data, 10); 479 | crc2 = (data[10] << 8) | (data[11]); 480 | data += 12; 481 | datalen -= 12; 482 | crc_ok = (crc1 == crc2); 483 | 484 | while (crc_ok && datalen) 485 | { 486 | if (datalen >= 18) 487 | { 488 | crc1 = calc_crc_wmbus(data, 16); 489 | crc2 = (data[16] << 8) | (data[17]); 490 | data += 18; 491 | datalen -= 18; 492 | crc_ok = (crc1 == crc2); 493 | } 494 | else 495 | { 496 | crc1 = calc_crc_wmbus(data, datalen-2); 497 | crc2 = (data[datalen-2] << 8) | (data[datalen-1]); 498 | data += datalen; 499 | datalen -= datalen; 500 | crc_ok = (crc1 == crc2); 501 | } 502 | } 503 | } 504 | 505 | return crc_ok; 506 | } 507 | 508 | static bool check_calc_crc_wmbus_b_frame_type(const uint8_t *data, size_t datalen) 509 | { 510 | bool crc_ok = (datalen >= 12); 511 | uint16_t crc1, crc2; 512 | 513 | /* The CRC field of Block 2 is calculated on the _concatenation_ 514 | of Block 1 and Block 2 data. */ 515 | while (crc_ok && datalen) 516 | { 517 | if (datalen >= 128) 518 | { 519 | crc1 = calc_crc_wmbus(data, 126); 520 | crc2 = (data[126] << 8) | (data[127]); 521 | data += 128; 522 | datalen -= 128; 523 | crc_ok = (crc1 == crc2); 524 | } 525 | else 526 | { 527 | crc1 = calc_crc_wmbus(data, datalen-2); 528 | crc2 = (data[datalen-2] << 8) | (data[datalen-1]); 529 | data += datalen; 530 | datalen -= datalen; 531 | crc_ok = (crc1 == crc2); 532 | } 533 | } 534 | 535 | return crc_ok; 536 | } 537 | 538 | __attribute__((__packed__)) 539 | struct wmmbus_header_with_crc 540 | { 541 | uint8_t L; // 1 542 | uint8_t C; // 1 543 | uint16_t manufacturer; // 2 544 | uint32_t ident_no; // 4 545 | uint8_t version; // 1 546 | uint8_t device_type; // 1 547 | uint16_t crc; // 2 548 | }; // 12 bytes in total. 549 | 550 | /** @brief Strip CRCs in place. */ 551 | static unsigned cook_pkt(uint8_t *data, unsigned datalen) 552 | { 553 | const uint8_t *const L = data; // Valid for T1 and C1 mode A: L = len(data). (Without CRC bytes included). 554 | uint8_t *dst = data; 555 | unsigned dstlen = 0; 556 | 557 | if (*L > 0 && // L should be greater than 0 bytes. 558 | datalen >= sizeof(struct wmmbus_header_with_crc)) 559 | { 560 | dst += 10; 561 | dstlen += 10; 562 | 563 | data += 12; 564 | datalen -= 12; 565 | 566 | while (datalen) 567 | { 568 | if (datalen >= 18) 569 | { 570 | memmove(dst, data, 16); 571 | 572 | dst += 16; 573 | dstlen += 16; 574 | 575 | data += 18; 576 | datalen -= 18; 577 | } 578 | else 579 | { 580 | memmove(dst, data, datalen-2); 581 | 582 | dst += (datalen-2); 583 | dstlen += (datalen-2); 584 | 585 | data += datalen; 586 | datalen -= datalen; 587 | } 588 | } 589 | } 590 | 591 | return dstlen; 592 | } 593 | 594 | /** @brief Strip CRCs in place. */ 595 | static unsigned cook_pkt_b_frame_type(uint8_t *data, unsigned datalen) 596 | { 597 | uint8_t *const L = data; // Valid for C1 mode B: L = len(data) + num of CRC bytes. (With CRC bytes included.) 598 | uint8_t *dst = data; 599 | unsigned dstlen = 0; 600 | 601 | if (*L >= 2 && // L should be at least 2 bytes long - means only CRC was received :). 602 | datalen >= sizeof(struct wmmbus_header_with_crc)) 603 | { 604 | while (datalen) 605 | { 606 | /* The CRC field of Block 2 is calculated on the _concatenation_ 607 | of Block 1 and Block 2 data. */ 608 | if (datalen >= 128) 609 | { 610 | memmove(dst, data, 126); 611 | 612 | dst += 126; 613 | dstlen += 126; 614 | 615 | data += 128; 616 | datalen -= 128; 617 | 618 | *L = *L - 2; 619 | } 620 | else 621 | { 622 | memmove(dst, data, datalen-2); 623 | 624 | dst += (datalen-2); 625 | dstlen += (datalen-2); 626 | 627 | data += datalen; 628 | datalen -= datalen; 629 | 630 | *L = *L - 2; 631 | } 632 | } 633 | } 634 | 635 | return dstlen; 636 | } 637 | 638 | static inline uint32_t get_serial(const uint8_t *const packet) 639 | { 640 | uint32_t serial; 641 | 642 | memcpy(&serial, &packet[4], sizeof(serial)); 643 | 644 | return serial; 645 | } 646 | 647 | extern int opts_show_used_algorithm; 648 | 649 | static void t1_c1_packet_decoder(unsigned bit, unsigned rssi, struct t1_c1_packet_decoder_work *decoder, const char *algorithm) 650 | { 651 | decoder->current_rssi = rssi; 652 | 653 | (*decoder->state++)(bit, decoder); 654 | 655 | if (*decoder->state == t1_c1_idle) 656 | { 657 | // nothing 658 | } 659 | else if (*decoder->state == t1_c1_done) 660 | { 661 | if (decoder->b_frame_type) 662 | { 663 | decoder->crc_ok = check_calc_crc_wmbus_b_frame_type(decoder->packet, decoder->L) ? 1 : 0; 664 | } 665 | else 666 | { 667 | decoder->crc_ok = check_calc_crc_wmbus(decoder->packet, decoder->L) ? 1 : 0; 668 | } 669 | 670 | if (!opts_show_used_algorithm) algorithm = ""; 671 | fprintf(stdout, "%s%s;%u;%u;%s;%u;%u;%08X;", algorithm, decoder->c1_packet ? "C1": "T1", 672 | decoder->crc_ok, 673 | decoder->err_3outof^1, 674 | decoder->timestamp, 675 | decoder->packet_rssi, 676 | rssi, 677 | get_serial(decoder->packet)); 678 | 679 | #if 0 680 | fprintf(stdout, "0x"); 681 | for (size_t l = 0; l < decoder->L; l++) fprintf(stdout, "%02x", decoder->packet[l]); 682 | fprintf(stdout, ";"); 683 | #endif 684 | 685 | #if 1 686 | if (decoder->b_frame_type) 687 | { 688 | decoder->L = cook_pkt_b_frame_type(decoder->packet, decoder->L); 689 | } 690 | else 691 | { 692 | decoder->L = cook_pkt(decoder->packet, decoder->L); 693 | } 694 | fprintf(stdout, "0x"); 695 | for (size_t l = 0; l < decoder->L; l++) fprintf(stdout, "%02x", decoder->packet[l]); 696 | #endif 697 | 698 | fprintf(stdout, "\n"); 699 | fflush(stdout); 700 | 701 | reset_t1_c1_packet_decoder(decoder); 702 | } 703 | else 704 | { 705 | // Stop receiving packet if current rssi below threshold. 706 | // The current packet seems to be collided with an another one. 707 | if (rssi < PACKET_CAPTURE_THRESHOLD) 708 | { 709 | reset_t1_c1_packet_decoder(decoder); 710 | } 711 | } 712 | } 713 | 714 | #endif /* T1_C1_PACKET_DECODER_H */ 715 | 716 | --------------------------------------------------------------------------------