├── docs ├── screenshot.png ├── .gitignore ├── .vuepress │ ├── styles │ │ └── index.scss │ └── config.js ├── build-docs.sh ├── package.json ├── HARDWARE.md └── LINKS.md ├── examples ├── sigrok-conv.sh ├── sigrok-open.sh ├── rtl_433_statsd_pipe.py ├── rtl_433_json_to_rtlwmbus.py ├── rtl_433_collectd_pipe.py ├── rtl_433_statsd_relay.py ├── rtl_433_custom.py ├── rtl_433_custom.php ├── rtl_433_http_stream.py ├── rtl_433_http_events.py ├── rtl_433_http_ws.py └── rtl_433_influxdb_relay.py ├── .github ├── actions │ └── style-check │ │ └── action.yml └── workflows │ └── check.yml ├── tests ├── exitcode-for-output.sh ├── data-test.c └── CMakeLists.txt ├── rtl433.pc.in ├── include ├── CMakeLists.txt ├── decoder.h ├── compat_paths.h ├── output_udp.h ├── pulse_analyzer.h ├── output_mqtt.h ├── http_server.h ├── output_influx.h ├── rfraw.h ├── compat_alarm.h ├── raw_output.h ├── compat_time.h ├── output_file.h ├── output_rtltcp.h ├── data_tag.h ├── samp_grab.h ├── output_trigger.h ├── write_sigrok.h ├── confparse.h ├── abuf.h ├── fatal.h ├── am_analyze.h ├── list.h ├── compat_pthread.h ├── jsmn.h ├── r_private.h ├── pulse_detect.h └── pulse_detect_fsk.h ├── conf ├── CMakeLists.txt ├── valeo_car_key.conf ├── car_fob.conf ├── Skylink_HA-434TL.conf ├── atc-technology_lmt-430.conf ├── sonoff_rm433.conf ├── steffen_switch.conf ├── dooya_curtain.conf ├── energy_count_3000.conf ├── LeakDetector.conf ├── led-light-remote.conf ├── EV1527-PIR-Sgooway.conf ├── tesla_charge-port-opener.conf ├── EV1527-DDS-Sgooway.conf ├── DrivewayAlert.conf ├── GhostControls.conf ├── MightyMule-FM231.conf ├── adlm_fprf.conf ├── verisure_alarm.conf ├── friedlandevo.conf ├── chungear_bcf-0019x2.conf ├── pir-ef4.conf ├── SMC5326-Remote.conf ├── SalusRT300RF.conf ├── EV1527-4Button-Universal-Remote.conf ├── CAME-TOP432.conf ├── silverline_doorbell.conf ├── FAN-53T.conf ├── MondeoRemote.conf ├── ContinentalRemote.conf ├── fan-11t.conf ├── heatilator.conf └── HeatmiserPRT-W.conf ├── src ├── mongoose_bool.patch ├── mongoose_lenchk.patch ├── mongoose_warn.patch ├── raw_output.c ├── mongoose_ipv6.patch ├── abuf.c ├── output_trigger.c ├── compat_time.c ├── devices │ ├── silvercrest.c │ ├── elro_db286a.c │ ├── quhwa.c │ ├── intertechno.c │ ├── generic_temperature_sensor.c │ ├── hondaremote.c │ ├── akhan_100F14.c │ ├── blyss.c │ ├── fordremote.c │ ├── ft004b.c │ ├── jasco.c │ ├── thermopro_tp11.c │ ├── rftech.c │ ├── generic_motion.c │ ├── generic_remote.c │ ├── abmt.c │ ├── nexa.c │ ├── nice_flor_s.c │ ├── mebus.c │ ├── newkaku.c │ ├── eurochron.c │ ├── tfa_pool_thermometer.c │ └── ibis_beacon.c ├── mongoose_msvcdbg.patch ├── list.c ├── compat_alarm.c └── compat_paths.c ├── .clang-format ├── .deploy ├── gen_release_info.py └── WINDOWS-MSVC.txt ├── .gitignore ├── AUTHORS ├── do_build.sh └── cmake ├── Modules ├── GetGitRevisionDescription.cmake.in ├── FindLibRTLSDR.cmake ├── FindGperftools.cmake └── FindLibUSB.cmake ├── Toolchain-gcc-mingw-w64-i686.cmake ├── Toolchain-gcc-mingw-w64-x86-64.cmake └── cmake_uninstall.cmake.in /docs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3gg0/rtl_433/master/docs/screenshot.png -------------------------------------------------------------------------------- /examples/sigrok-conv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo 'Please use "rtl_433 [-s ] -w .sr -r .cu8"' 4 | -------------------------------------------------------------------------------- /examples/sigrok-open.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo 'Please use "rtl_433 [-s ] -W .sr -r .cu8"' 4 | -------------------------------------------------------------------------------- /.github/actions/style-check/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Style Check' 2 | description: 'Check for common code style warnings' 3 | runs: 4 | using: 'node12' 5 | main: 'index.js' 6 | -------------------------------------------------------------------------------- /tests/exitcode-for-output.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # execute command and set exit code 1 if there is output on stdout or stderr 4 | out=$($@ 2>&1) 5 | echo "$out" 6 | [ -z "$out" ] 7 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # ignore VuePress build files 2 | .vuepress/dist 3 | .temp 4 | .cache 5 | node_modules 6 | yarn.lock 7 | # ignore copied files 8 | README.md 9 | CHANGELOG.md 10 | EXAMPLES.md 11 | TESTS.md 12 | -------------------------------------------------------------------------------- /docs/.vuepress/styles/index.scss: -------------------------------------------------------------------------------- 1 | // Missing background color on bare code sections 2 | .theme-default-content pre, .theme-default-content pre[class*="language-"] { 3 | background-color: var(--code-bg-color); 4 | } 5 | -------------------------------------------------------------------------------- /rtl433.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@prefix@ 2 | exec_prefix=@exec_prefix@ 3 | libdir=@libdir@ 4 | includedir=@includedir@ 5 | 6 | Name: RTL-433 Utility 7 | Description: C Utility 8 | Version: @VERSION@ 9 | Cflags: -I${includedir}/ @RTL433_PC_CFLAGS@ 10 | -------------------------------------------------------------------------------- /include/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ######################################################################## 2 | # Install public header files 3 | ######################################################################## 4 | install(FILES 5 | rtl_433.h 6 | rtl_433_devices.h 7 | DESTINATION include 8 | ) 9 | -------------------------------------------------------------------------------- /docs/build-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # abort on errors 4 | set -e 5 | 6 | # navigate to the docs directory 7 | cd ${0%/*} 8 | 9 | # copy other docs 10 | sed 's/docs\///' ../README.md >README.md 11 | cp ../CHANGELOG.md . 12 | cp ../rtl_433_tests/README.md TESTS.md 13 | 14 | # build 15 | yarn install 16 | yarn docs:build 17 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rtl_433-docs", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docs:dev": "vuepress dev", 7 | "docs:build": "vuepress build" 8 | }, 9 | "devDependencies": { 10 | "@vuepress/plugin-search": "^2.0.0-beta.49", 11 | "vuepress": "^2.0.0-beta.49" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /conf/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ######################################################################## 2 | # Install example configuration files 3 | ######################################################################## 4 | file(GLOB RTL433_CONF_FILES "*.conf") 5 | 6 | install(FILES 7 | ${RTL433_CONF_FILES} 8 | DESTINATION etc/rtl_433 9 | ) 10 | -------------------------------------------------------------------------------- /include/decoder.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | Meta include for all decoders. 3 | */ 4 | 5 | #ifndef INCLUDE_DECODER_H_ 6 | #define INCLUDE_DECODER_H_ 7 | 8 | #include 9 | #include 10 | #include "r_device.h" 11 | #include "bitbuffer.h" 12 | #include "data.h" 13 | #include "util.h" 14 | #include "decoder_util.h" 15 | 16 | #endif /* INCLUDE_DECODER_H_ */ 17 | -------------------------------------------------------------------------------- /src/mongoose_bool.patch: -------------------------------------------------------------------------------- 1 | diff --git a/include/mongoose.h b/include/mongoose.h 2 | index b885270..5166d1a 100644 3 | --- a/include/mongoose.h 4 | +++ b/include/mongoose.h 5 | @@ -231,7 +231,7 @@ 6 | #include 7 | #include 8 | 9 | -#if _MSC_VER < 1700 10 | +#if defined(_MSC_VER) && (_MSC_VER < 1700) 11 | typedef int bool; 12 | #else 13 | #include 14 | -------------------------------------------------------------------------------- /conf/valeo_car_key.conf: -------------------------------------------------------------------------------- 1 | # Valeo Car Key 2 | # Identifies event, but does not attempt to decrypt rolling code... 3 | # Copyright (C) 2015 Tommy Vestermark 4 | 5 | # preamble is actually repeated e8e8e8... (with ZEROBIT removed: d1d1d1...) 6 | 7 | decoder { 8 | name=Valeo-Car-Key, 9 | modulation=OOK_MC_ZEROBIT, 10 | short=106, 11 | reset=400, 12 | bits=461, 13 | preamble=d1d1d0 14 | } 15 | -------------------------------------------------------------------------------- /conf/car_fob.conf: -------------------------------------------------------------------------------- 1 | # Unknown carfob with rolling code 2 | # Copyright (C) 2020 Benjamin Larsson 3 | # 4 | 5 | 6 | decoder { 7 | name=Car fob, 8 | modulation=OOK_PWM, 9 | short=428, 10 | long=872, 11 | reset=4284, 12 | gap=872, 13 | tolerance=176, 14 | bits=66, 15 | get=rolling_code:@0:{32}:, 16 | get=id:@32:{24}, 17 | get=@56:button:{8}:[02:button_1 04:button_2], 18 | } 19 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | UseTab: Never 2 | IndentWidth: 4 3 | ContinuationIndentWidth: 8 4 | BreakBeforeBraces: Stroustrup 5 | AlignAfterOpenBracket: DontAlign 6 | AlignEscapedNewlines: DontAlign 7 | AlignConsecutiveAssignments: true 8 | AlignConsecutiveMacros: AcrossEmptyLines 9 | AllowShortIfStatementsOnASingleLine: false 10 | AllowShortCaseLabelsOnASingleLine: true 11 | IndentCaseLabels: false 12 | ColumnLimit: 0 13 | SortIncludes: false 14 | -------------------------------------------------------------------------------- /.deploy/gen_release_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | with open('CHANGELOG.md') as r, open('RELEASEINFO.md', 'w') as w: 4 | # skip over possible 'Unreleased' 5 | for line in r: 6 | if line.startswith('## Release'): 7 | # w.write(line[1:]) 8 | break 9 | for line in r: 10 | if line.startswith('## '): 11 | break 12 | # if line.startswith('#'): 13 | # line = line[1:] 14 | w.write(line) 15 | -------------------------------------------------------------------------------- /conf/Skylink_HA-434TL.conf: -------------------------------------------------------------------------------- 1 | # Skylink HA-434TL motion sensor 2 | # 3 | # This decoder reads detections of the Skylink HA-434TL PIR motion sensor 4 | # 5 | # s.a. https://github.com/merbanan/rtl_433/pull/814 6 | 7 | decoder { 8 | n=Skylink-HA-434TL, 9 | m=OOK_PPM, 10 | s=500, 11 | l=1500, 12 | y=2000, 13 | g=1800, 14 | r=10000, 15 | bits=17, 16 | get={3}:motion:[5:motion 2:alive], 17 | get=@3:{14}:id, 18 | unique 19 | } 20 | -------------------------------------------------------------------------------- /conf/atc-technology_lmt-430.conf: -------------------------------------------------------------------------------- 1 | # ATC Technology LMT-430 2 | # 3 | # This config represents the decoding settings of a handheld dimming remote for my room lamp 4 | 5 | decoder { 6 | name = ATC Technology LMT-430, 7 | modulation = OOK_PPM, 8 | short = 420, 9 | long = 1080, 10 | gap = 1100, 11 | reset = 8060, 12 | bits = 25, 13 | preamble = 0x2cad, 14 | repeats >= 3, 15 | get = button:@16:{12}:[0x4c8:1 0x2a8:2 0x550:3 0x330:4] 16 | } 17 | -------------------------------------------------------------------------------- /conf/sonoff_rm433.conf: -------------------------------------------------------------------------------- 1 | # Sonoff RM433 remote controller 2 | # https://www.itead.cc/sonoff-rm433-remote-controller-base.html 3 | 4 | # map the 8 buttons to A->H 5 | # Each remote controller has its own ID (2 first bytes) 6 | 7 | decoder { 8 | name=Sonoff-RM433, 9 | modulation=OOK_PWM, 10 | short=260, 11 | long=744, 12 | reset=8000, 13 | gap=800, 14 | tolerance=50, 15 | bits>=24, 16 | invert, 17 | get=@0:{20}:id, 18 | get=@20:{4}:button:[8:A 12:B 4:C 9:D 2:E 5:F 1:G 3:H], 19 | unique 20 | } 21 | -------------------------------------------------------------------------------- /include/compat_paths.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | compat_paths addresses compatibility with default OS path names. 3 | 4 | topic: default search paths for config file 5 | issue: Linux and Windows use different common paths for config files 6 | solution: provide specific default paths for each system 7 | */ 8 | 9 | #ifndef INCLUDE_COMPAT_PATHS_H_ 10 | #define INCLUDE_COMPAT_PATHS_H_ 11 | 12 | /// Get default search paths for rtl_433 config file. 13 | char **compat_get_default_conf_paths(void); 14 | 15 | #endif /* INCLUDE_COMPAT_PATHS_H_ */ 16 | -------------------------------------------------------------------------------- /conf/steffen_switch.conf: -------------------------------------------------------------------------------- 1 | # Steffen Switch Transmitter, HS1527 based remote button 2 | 3 | # should abort if (bb[0][0]!=0x00 || (bb[1][0]&0x07)!=0x07 || bb[1][0]!=bb[2][0] || bb[2][0]==bb[3][0]) 4 | 5 | decoder { 6 | name = Steffen-Switch, 7 | modulation = OOK_PPM, 8 | short = 370, 9 | long = 750, 10 | gap = 1080, 11 | reset = 6000, 12 | bits = 25, 13 | get = @0:{5}:code, 14 | get = @20:{4}:button:[14:A 13:B 11:C 7:D 15:ALL], 15 | get = @16:{4}:state:[15:OFF 0:ON], 16 | } 17 | -------------------------------------------------------------------------------- /conf/dooya_curtain.conf: -------------------------------------------------------------------------------- 1 | # Dooya Curtain Remote (DC1602) 2 | # e.g. https://www.aliexpress.com/i/32954197821.html 3 | # see https://github.com/merbanan/rtl_433/issues/1545 4 | # 5 | # A 15 channel remote, with 3 functions/commands: Open/Close/Stop 6 | 7 | decoder { 8 | name=Dooya-Curtain, 9 | modulation=OOK_PWM, 10 | short=350, 11 | long=750, 12 | sync=4900, 13 | gap=990, 14 | reset=9900, 15 | bits>=40, 16 | invert, 17 | get=@0:{24}:id, 18 | get=@24:{8}:channel, 19 | get=@32:{4}:button:[1:open 3:close 5:stop], 20 | get=@36:{4}:check, 21 | unique 22 | } 23 | -------------------------------------------------------------------------------- /conf/energy_count_3000.conf: -------------------------------------------------------------------------------- 1 | # EC3k Energy Count Control 2 | # 3 | # "Voltcraft Energy Count 3000" (868.3 MHz) sensor sold by Conrad 4 | # aka “Velleman NETBSEM4” 5 | # aka “La Crosse Technology Remote Cost Control Monitor – RS3620”. 6 | # aka "ELV Cost Control" 7 | # 8 | # Stub driver 9 | # FSK PCM NRZ 50 us bit width, up to 12 zeros seen, package should be around 578 bits. 10 | # 11 | # Copyright (C) 2015 Tommy Vestermark 12 | 13 | decoder { 14 | name=Energy-Count-3000, 15 | modulation=FSK_PCM, 16 | short=50, 17 | long=50, 18 | reset=800, 19 | bits>=550, 20 | bits<=590 21 | } 22 | -------------------------------------------------------------------------------- /include/output_udp.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | UDP syslog output for rtl_433 events. 3 | 4 | Copyright (C) 2021 Christian Zuckschwerdt 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | #ifndef INCLUDE_OUTPUT_UDP_H_ 13 | #define INCLUDE_OUTPUT_UDP_H_ 14 | 15 | #include "data.h" 16 | 17 | struct data_output *data_output_syslog_create(const char *host, const char *port); 18 | 19 | #endif /* INCLUDE_OUTPUT_UDP_H_ */ 20 | -------------------------------------------------------------------------------- /include/pulse_analyzer.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | Pulse analyzer functions. 3 | 4 | Copyright (C) 2015 Tommy Vestermark 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | #ifndef INCLUDE_PULSE_ANALYZER_H_ 13 | #define INCLUDE_PULSE_ANALYZER_H_ 14 | 15 | #include "pulse_detect.h" 16 | 17 | /// Analyze and print result. 18 | void pulse_analyzer(pulse_data_t *data, int package_type); 19 | 20 | #endif /* INCLUDE_PULSE_ANALYZER_H_ */ 21 | -------------------------------------------------------------------------------- /conf/LeakDetector.conf: -------------------------------------------------------------------------------- 1 | # Decoder for the WaterLeak Detector: 2 | # https://www.banggood.com/DY-SQ100B-Water-Leakage-Detector-Rustproof-Sensor-Alarm-433MHz-for-Security-Home-Alarm-System-p-1266537.html 3 | # by Kevin Saye 4 | # 5 | # Operates on 433.920MHz. 6 | # All models emit identical id's. 7 | 8 | decoder { 9 | name=LeakDetector, 10 | modulation=OOK_PWM, 11 | short=316, 12 | long=968, 13 | reset=916, 14 | gap=0, 15 | tolerance=261, 16 | bits=33, 17 | unique, 18 | get=Location:@0:{16}:[42460:HotWaterHeater 5853:KevinSink 7133:KitchenSink], 19 | get=Message:@16:{8}:[250:Alarm], 20 | get=Battery:@24:{4}:[14:Ok 12:Low] 21 | } 22 | 23 | -------------------------------------------------------------------------------- /conf/led-light-remote.conf: -------------------------------------------------------------------------------- 1 | # Generic Remote Controller for LED Strip Lights 2 | # Remote decoder from issue #1112 by chaos511 3 | 4 | # first 16 bit seem to be a fixed remote id 5 | # last 8 bit encode the button press: 6 | # (the keypad has up to 7 rows of 3 columns, 7 | # usually some keys are missing) 8 | # 1 2 3 9 | # 4 5 6 10 | # 7 8 9 11 | # 10 11 12 12 | # 13 14 15 13 | # 16 17 18 14 | # 19 20 21 15 | 16 | decoder { 17 | name=LED-Light-Remote, 18 | modulation=OOK_PWM, 19 | short=264, 20 | long=1060, 21 | reset=2000, 22 | gap=0, 23 | tolerance=316, 24 | bits=25, 25 | invert, 26 | get=@0:{16}:id, 27 | get=@16:{8}:button 28 | } 29 | -------------------------------------------------------------------------------- /include/output_mqtt.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | MQTT output for rtl_433 events 3 | 4 | Copyright (C) 2019 Christian Zuckschwerdt 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | #ifndef INCLUDE_OUTPUT_MQTT_H_ 13 | #define INCLUDE_OUTPUT_MQTT_H_ 14 | 15 | #include "data.h" 16 | 17 | struct mg_mgr; 18 | 19 | struct data_output *data_output_mqtt_create(struct mg_mgr *mgr, char *param, char const *dev_hint); 20 | 21 | #endif /* INCLUDE_OUTPUT_MQTT_H_ */ 22 | -------------------------------------------------------------------------------- /include/http_server.h: -------------------------------------------------------------------------------- 1 | /** 2 | * RESTful HTTP control and WS interface 3 | * 4 | * Copyright (C) 2018 Christian Zuckschwerdt 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | */ 11 | 12 | #ifndef INCLUDE_HTTP_SERVER_H_ 13 | #define INCLUDE_HTTP_SERVER_H_ 14 | 15 | #include "data.h" 16 | 17 | struct mg_mgr; 18 | struct r_cfg; 19 | 20 | struct data_output *data_output_http_create(struct mg_mgr *mgr, const char *host, const char *port, struct r_cfg *cfg); 21 | 22 | #endif /* INCLUDE_HTTP_SERVER_H_ */ 23 | -------------------------------------------------------------------------------- /src/mongoose_lenchk.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/common/str_util.c b/src/common/str_util.c 2 | index cc825cb..5a0b8b1 100644 3 | --- a/src/common/str_util.c 4 | +++ b/src/common/str_util.c 5 | @@ -507,7 +507,7 @@ size_t mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str) { 6 | const struct mg_str pstr = {pattern.p + i, pattern.len - i}; 7 | const struct mg_str sstr = {str.p + j + len, str.len - j - len}; 8 | res = mg_match_prefix_n(pstr, sstr); 9 | - } while (res == 0 && len != 0 && len-- > 0); 10 | + } while (res == 0 && len != 0 && --len > 0); 11 | return res == 0 ? 0 : j + res + len; 12 | } else if (str_util_lowercase(&pattern.p[i]) != 13 | str_util_lowercase(&str.p[j])) { 14 | -------------------------------------------------------------------------------- /include/output_influx.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | InfluxDB output for rtl_433 events 3 | 4 | Copyright (C) 2019 Daniel Krueger 5 | based on output_mqtt.c 6 | Copyright (C) 2019 Christian Zuckschwerdt 7 | 8 | This program is free software; you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation; either version 2 of the License, or 11 | (at your option) any later version. 12 | */ 13 | 14 | #ifndef INCLUDE_OUTPUT_INFLUX_H_ 15 | #define INCLUDE_OUTPUT_INFLUX_H_ 16 | 17 | #include "data.h" 18 | 19 | struct mg_mgr; 20 | 21 | struct data_output *data_output_influx_create(struct mg_mgr *mgr, char *opts); 22 | 23 | #endif /* INCLUDE_OUTPUT_INFLUX_H_ */ 24 | -------------------------------------------------------------------------------- /include/rfraw.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | RfRaw format functions. 3 | 4 | Copyright (C) 2020 Christian W. Zuckschwerdt 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | #ifndef INCLUDE_RFRAW_H_ 13 | #define INCLUDE_RFRAW_H_ 14 | 15 | #include "pulse_detect.h" 16 | #include 17 | 18 | /// Check if a given string is in RfRaw format. 19 | bool rfraw_check(char const *p); 20 | 21 | /// Decode RfRaw string to pulse data. 22 | bool rfraw_parse(pulse_data_t *data, char const *p); 23 | 24 | #endif /* INCLUDE_RFRAW_H_ */ 25 | -------------------------------------------------------------------------------- /conf/EV1527-PIR-Sgooway.conf: -------------------------------------------------------------------------------- 1 | # EV1527 based passive infrared sensor 2 | # 3 | # This decoder reads detections of the Sgooway EV1527 based PIR 4 | # 5 | # The shown example can distinguish between multiple of those PIR's (two in this case) 6 | # and present the different PIR's as "channel : x" in the output. Adopt this line 7 | # to your EV1527 unique codes. Put the decimal representation of the in here. 8 | # 9 | # If this is not desired, a simple "match=abcde" can limit the detection of 10 | # one unique EV1527 code. 11 | 12 | decoder { 13 | n=EV1527-PIR, 14 | m=OOK_PWM, 15 | s=400, 16 | l=1200, 17 | g=1500, 18 | r=12000, 19 | repeats>=4, 20 | bits=25, 21 | get=@0:{25}:channel:[12345678:1 98765432:2], 22 | unique 23 | } 24 | -------------------------------------------------------------------------------- /conf/tesla_charge-port-opener.conf: -------------------------------------------------------------------------------- 1 | # Tesla charge port opener 2 | # 3 | # * When the button on the charge handle is pressed the signal is repeated at 0.15s intervals for 10 times. 4 | # * There are no unique codes or transmissions variants. The code is the same for all Tesla charge port 5 | # handles and there is a single button. 6 | # * The transmitter hardware is reported to be using a Si4010. 7 | # * tolerance=20 worked in the initial test, larger value in the configuration for additional tolerance 8 | 9 | decoder { 10 | name = Tesla charge port opener, 11 | modulation = OOK_MC_ZEROBIT, 12 | short = 400, 13 | reset = 1200, 14 | tolerance = 50, 15 | match = 094aa9b38da19, 16 | rows = 5 17 | repeats = 2, 18 | countonly, 19 | } 20 | -------------------------------------------------------------------------------- /src/mongoose_warn.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/mongoose.c b/src/mongoose.c 2 | index 6db561bc..2a284ee8 100644 3 | --- a/src/mongoose.c 4 | +++ b/src/mongoose.c 5 | @@ -1221,7 +1221,7 @@ void cs_md5_update(cs_md5_ctx *ctx, const unsigned char *buf, size_t len) { 6 | memcpy(ctx->in, buf, len); 7 | } 8 | 9 | -void cs_md5_final(unsigned char digest[16], cs_md5_ctx *ctx) { 10 | +void cs_md5_final(unsigned char *digest, cs_md5_ctx *ctx) { 11 | unsigned count; 12 | unsigned char *p; 13 | uint32_t *a; 14 | @@ -4965,7 +4965,7 @@ static enum mg_ssl_if_result mg_use_cert(SSL_CTX *ctx, const char *cert, 15 | DH_free(dh); 16 | } 17 | #if OPENSSL_VERSION_NUMBER > 0x10002000L 18 | - SSL_CTX_set_ecdh_auto(ctx, 1); 19 | + (void) SSL_CTX_set_ecdh_auto(ctx, 1); 20 | #endif 21 | #endif 22 | } 23 | -------------------------------------------------------------------------------- /conf/EV1527-DDS-Sgooway.conf: -------------------------------------------------------------------------------- 1 | # EV1527 based Wireless Door Sensor 2 | # 3 | # This decoder reads detections of the Sgooway EV1527 based Door sensor 4 | # 5 | # Link to this product can be found https://www.aliexpress.com/item/399798633.html?spm=a2g0s.9042311.0.0.6bf54c4dDH7uSk 6 | # 7 | # The shown example will display all sensors detected. Each device will have a separate code that can be used to differentiate between the 8 | # different devices. 9 | # 10 | # One thing of note is this device does not trigger a closed even, only when the device is opened is an rf signal sent. 11 | 12 | decoder { 13 | n=EV1527-DDS, 14 | m=OOK_PWM, 15 | s=280, 16 | l=990, 17 | r=2000, 18 | bits>=24, 19 | bits<=25, 20 | get=@0:{24}:code, 21 | unique 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile 2 | Makefile.in 3 | .deps 4 | .libs 5 | *.o 6 | *.lo 7 | *.la 8 | *.pc 9 | aclocal.m4 10 | acinclude.m4 11 | aminclude.am 12 | m4/*.m4 13 | autom4te.cache 14 | config.h* 15 | config.sub 16 | config.log 17 | config.status 18 | config.guess 19 | configure 20 | compile 21 | depcomp 22 | missing 23 | ltmain.sh 24 | install-sh 25 | stamp-h1 26 | libtool 27 | Doxyfile 28 | .dirstamp 29 | tags 30 | 31 | .tarball-version 32 | .version 33 | 34 | .*.swp 35 | 36 | doc/ 37 | 38 | src/rtl_433 39 | 40 | CMakeCache.txt 41 | 42 | build*/ 43 | .cproject 44 | .settings 45 | .project 46 | 47 | *.orig 48 | *~ 49 | *.tlog 50 | *.ipdb 51 | *.iobj 52 | *.idb 53 | *.lastbuildstate 54 | *.db 55 | *.opendb 56 | 57 | _*/ 58 | 59 | rtl_433_tests 60 | 61 | .vs 62 | .idea 63 | cmake-build-debug/ 64 | 65 | .DS_Store 66 | -------------------------------------------------------------------------------- /src/raw_output.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Raw I/Q data output handler. 3 | 4 | Copyright (C) 2022 Christian Zuckschwerdt 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | #include "raw_output.h" 13 | 14 | #include 15 | 16 | /* generic raw_output */ 17 | 18 | void raw_output_frame(struct raw_output *output, uint8_t const *data, uint32_t len) 19 | { 20 | if (!output) 21 | return; 22 | output->output_frame(output, data, len); 23 | } 24 | 25 | void raw_output_free(struct raw_output *output) 26 | { 27 | if (!output) 28 | return; 29 | output->output_free(output); 30 | } 31 | -------------------------------------------------------------------------------- /conf/DrivewayAlert.conf: -------------------------------------------------------------------------------- 1 | # Decoder for the Harbor Freight / Bunker Hill Wireless / Ironton 2 | # Wireless Security Alert System / Wireless Driveway Alert System 3 | # Passive Infrared Sensor by ZHUJI JIARONG ELECTRICAL APPLIANCE CO.,LTD. 4 | # 5 | # Operates on 433.920MHz. 6 | # All models emit identical id's. 7 | # 8 | # Reference: 9 | # https://fccid.io/2AA9QJR-178 10 | # https://triq.org/pdv/#AAB01A04010160043C15B831DC8180918180918091818180909090909255+AAB01D04190160043C15B831DC8181818180918180918091818180909090909255+AAB01D04010160043C15B831DC8181818180918180918091818180909090909355 11 | 12 | decoder { 13 | name=DrivewayAlert, 14 | modulation=OOK_PWM, 15 | short=368, 16 | long=1108, 17 | reset=5572, 18 | gap=1076, 19 | tolerance=296, 20 | sync=0, 21 | match={19}0xfb5c00, 22 | bits=19, 23 | unique, 24 | } 25 | -------------------------------------------------------------------------------- /include/compat_alarm.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | @brief compat_alarm adds an alarm() function for Windows. 3 | 4 | Except for MinGW-w64 when `_POSIX` and/or `__USE_MINGW_ALARM` 5 | is defined 6 | */ 7 | 8 | #ifndef INCLUDE_COMPAT_ALARM_H_ 9 | #define INCLUDE_COMPAT_ALARM_H_ 10 | 11 | #ifdef _WIN32 12 | #include 13 | #include 14 | #include /* alarm() for MinGW is possibly here */ 15 | 16 | #if !defined(_POSIX) && !defined(__USE_MINGW_ALARM) 17 | int win_alarm(unsigned seconds); 18 | #define alarm(sec) win_alarm(sec) 19 | #define HAVE_win_alarm 20 | #endif 21 | 22 | /* No SIGUSRx on Windows. Use this unless MinGW-w64 23 | * has support for it (untested by me). 24 | */ 25 | #if !defined(__USE_MINGW_ALARM) 26 | #define SIGALRM SIGBREAK 27 | #endif 28 | 29 | #endif /* _WIN32 */ 30 | #endif /* INCLUDE_COMPAT_ALARM_H_ */ 31 | -------------------------------------------------------------------------------- /conf/GhostControls.conf: -------------------------------------------------------------------------------- 1 | # Decoder for the GhostControls family of automated gate controls that 2 | # operate on 433.920MHz. 3 | # 4 | # Tested with: 5 | # AXS1 3-Button Remote 6 | # AXP1 5-Button Premium Remote 7 | # AXR1 Water-Resistant Remote 8 | # AXWK Premium Wireless Keypad 9 | # 10 | # Reference: 11 | # https://fccid.io/2AGMZGC433TX1-5 12 | # https://fccid.io/2AGMZGC433WK1 13 | 14 | decoder { 15 | name=GhostControls, 16 | modulation=OOK_PWM, 17 | short=248, 18 | long=776, 19 | sync=0, 20 | reset=780, 21 | tolerance=211, 22 | rows=1, 23 | bits=42, 24 | invert, 25 | get=unit:@0:{4}:[1:remote 2:keypad], 26 | get=options:@4:{4}:[0:none 8:party 9:vacation 15:test], 27 | get=command:@8:{4}:[0:none 3:toggle], 28 | get=button:@12:{8}:[0:none 1:secondary 2:primary], 29 | get=id:@20:{22}, 30 | } 31 | -------------------------------------------------------------------------------- /conf/MightyMule-FM231.conf: -------------------------------------------------------------------------------- 1 | # Decoder for the Mighty Mule FM231 Driveway alarm from GTO Inc 2 | # FCC Test report, including RF waveforms is here: 3 | # https://fccid.io/I6HGTOFM231/Test-Report/Test-Report-1214140.pdf 4 | 5 | # Use the Accurite and similar convention for reporting battery. 6 | # The name is 'battery_ok' with values 1 (ok) and 0. (which are 7 | # numerically reversed from what the FM231 reports) 8 | 9 | # The DIP switches for setting a unique device ID are labeled 1-4 10 | # from left to right, but appear in the # data stream in reverse 11 | # order. 12 | 13 | decoder { 14 | name=MightyMule-FM231, 15 | modulation=OOK_PWM, 16 | short=650, 17 | long=1200, 18 | sync=3800, 19 | reset=1100, 20 | tolerance=200, 21 | rows=1, 22 | bits=9, 23 | get=@4:{1}:battery_ok:[0:1 1:0], 24 | get=@5:{4}:id, 25 | unique 26 | } 27 | -------------------------------------------------------------------------------- /conf/adlm_fprf.conf: -------------------------------------------------------------------------------- 1 | # Equation/Siemens ADLM FPRF on 433.863MHz 2 | # 3 zones heater programer 3 | # 4 | # A 50ms wakeup pulse followed by a 5ms gap, 5 | # then a start pulse 5ms gap + 3ms pulse followed by 41 data pulses. 6 | # This is repeated 3 times with the next wakeup directly following 7 | # the preceding stop pulses. 8 | # 9 | # Bit width is 2000 us with 10 | # Short pulse: ___- 1500us gap + 500 us pulse 11 | # Long pulse: _--- 500us gap + 1500 us pulse 12 | # 13 | # This is a electric heater programmer sold in France by Leroy Merlin on the brand Equation. 14 | # It is manufactured by Siemens, and also has the mark RDE100.1 FPRF on the PCB 15 | 16 | decoder { 17 | name=ADLM FPRF, 18 | modulation=OOK_PWM, 19 | short=500, 20 | long=1500, 21 | reset=7000, 22 | gap=2000, 23 | bits>=40, 24 | get=@8:{16}:id, 25 | get=@28:{4}:zone, 26 | get=@32:{4}:mode:[9:ECO 10:COMFORT 8:OFF], 27 | unique 28 | } 29 | -------------------------------------------------------------------------------- /include/raw_output.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | Raw I/Q data output handler. 3 | 4 | Copyright (C) 2022 Christian Zuckschwerdt 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | #ifndef INCLUDE_RAW_OUTPUT_H_ 13 | #define INCLUDE_RAW_OUTPUT_H_ 14 | 15 | #include 16 | 17 | struct raw_output; 18 | 19 | typedef struct raw_output { 20 | void (*output_frame)(struct raw_output *output, uint8_t const *data, uint32_t len); 21 | void (*output_free)(struct raw_output *output); 22 | } raw_output_t; 23 | 24 | void raw_output_frame(struct raw_output *output, uint8_t const *data, uint32_t len); 25 | 26 | void raw_output_free(struct raw_output *output); 27 | 28 | #endif /* INCLUDE_RAW_OUTPUT_H_ */ 29 | -------------------------------------------------------------------------------- /.deploy/WINDOWS-MSVC.txt: -------------------------------------------------------------------------------- 1 | # rtl_433 Windows MSVC build 2 | 3 | For the SoapySDR builds you need PothosSDR installed https://downloads.myriadrf.org/builds/PothosSDR/ 4 | Any recent version should work, currently built with 2021.07.25-vc16: 5 | https://downloads.myriadrf.org/builds/PothosSDR/PothosSDR-2021.07.25-vc16-x64.exe 6 | When installing choose "Add PothosSDR to the system PATH for the current user" 7 | 8 | For the TLS builds (mqtts and influxs) you need OpenSSL installed. 9 | E.g. install Chocolatey https://chocolatey.org/install 10 | then run `choco install openssl.light` 11 | 12 | An alternative to installing SoapySDR from PothosSDR is to extract the installer 13 | and copy the builds (.exe) from this release to the `bin` directory in PothosSDR. 14 | 15 | This release includes the Microsoft Visual C++ Redistributable for Visual Studio 2015, 2017 and 2019. 16 | S.a. https://support.microsoft.com/en-us/topic/the-latest-supported-visual-c-downloads-2647da03-1eea-4433-9aff-95f26a218cc0 17 | -------------------------------------------------------------------------------- /include/compat_time.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | compat_time addresses compatibility time functions. 3 | 4 | topic: high-resolution timestamps 5 | issue: is not available on Windows systems 6 | solution: provide a compatible version for Windows systems 7 | */ 8 | 9 | #ifndef INCLUDE_COMPAT_TIME_H_ 10 | #define INCLUDE_COMPAT_TIME_H_ 11 | 12 | // ensure struct timeval is known 13 | #ifdef _WIN32 14 | #include 15 | #else 16 | #include 17 | #endif 18 | 19 | /** Subtract `struct timeval` values. 20 | 21 | @param[out] result time difference result 22 | @param x first time value 23 | @param y second time value 24 | @return 1 if the difference is negative, otherwise 0. 25 | */ 26 | int timeval_subtract(struct timeval *result, struct timeval *x, struct timeval *y); 27 | 28 | // platform-specific functions 29 | 30 | #ifdef _WIN32 31 | int gettimeofday(struct timeval *tv, void *tz); 32 | #endif 33 | 34 | #endif /* INCLUDE_COMPAT_TIME_H_ */ 35 | -------------------------------------------------------------------------------- /conf/verisure_alarm.conf: -------------------------------------------------------------------------------- 1 | # Verisure Alarm, contact switch made by Honeywell 2 | # Copyright (C) 2020 Benjamin Larsson 3 | # 4 | # The payload is most likely encrypted, but you can still use the id 5 | # and state to figure out if the contact switch is opened or closed. 6 | # 7 | # This decoder needs the classic FSK demodulator 8 | # 9 | #55fff d87e08023c005e1b3e2ed 54000 387e17fdc3ffbe04c1d13 10 | #55fff d87e08023c005e1b3e2ed 54000 387e17fdc3ffbe04c1d13 11 | #55fff d87e08023c005e1b3e2ed 54000 387e17fdc3ffbe04c1d13 12 | #55fff d87e08023c005e1b3e2ed 54000 387e17fdc3ffbe04c1d13 13 | #55fff d87e08023c005e1b3e2ed 54000 387e17fdc3ffbe04c1d13 14 | # 15 | # The bit stream might be 2 different packets of ca 10 bytes that 16 | # are repeated several times. 17 | 18 | decoder { 19 | name=Verisure Alarm, 20 | modulation=FSK_PCM, 21 | short=208, 22 | long=208, 23 | reset=4025, 24 | gap=2500, 25 | tolerance=10, 26 | match={20}0xafffe, 27 | get=@24:{24}:id, 28 | get=@52:{12}:state, 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Benjamin Larsson 2 | Christian W. Zuckschwerdt 3 | 4 | Sven Killig 5 | rct 6 | johan 7 | Thomas Kerpe 8 | Jens Jensen 9 | Sven 10 | Martin Hauke 11 | magellannh 12 | jules69350 13 | arantius 14 | andreaaizza 15 | Trueffelwurm 16 | Tomasz Brzezina 17 | Paul F-Y 18 | Corné van Strien 19 | Baruch Even 20 | Andrea 21 | Helge Weissig 22 | Robert Fraczkiewicz 23 | Nicola Quiriti 24 | Petr Konecny 25 | Tom Felker 26 | Pasquale 'sid' Fiorillo 27 | Tommy Vestermark 28 | Aaron Spangler 29 | -------------------------------------------------------------------------------- /include/output_file.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | File outputs for rtl_433 events. 3 | 4 | Copyright (C) 2021 Christian Zuckschwerdt 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | #ifndef INCLUDE_OUTPUT_FILE_H_ 13 | #define INCLUDE_OUTPUT_FILE_H_ 14 | 15 | #include "data.h" 16 | #include 17 | 18 | /** Construct data output for CSV printer. 19 | 20 | @param file the output stream 21 | @return The auxiliary data to pass along with data_csv_printer to data_print. 22 | You must release this object with data_output_free once you're done with it. 23 | */ 24 | struct data_output *data_output_csv_create(FILE *file); 25 | 26 | struct data_output *data_output_json_create(FILE *file); 27 | 28 | struct data_output *data_output_kv_create(FILE *file); 29 | 30 | #endif /* INCLUDE_OUTPUT_FILE_H_ */ 31 | -------------------------------------------------------------------------------- /conf/friedlandevo.conf: -------------------------------------------------------------------------------- 1 | # Friedland EVO door bell 2 | # 3 | # This decodes the transmissions from the Friedland EVO wireless door bell buttons. The 4 | # Friedland doorbell system allows multiple buttons and multiple bells and will make a 5 | # different sound depending on which door button was pressed. This has only been tested with 6 | # 2 different buttons. 7 | # 8 | # In order to eliminate other transmissions the match function has been used with part of 9 | # the data that was the same between the two tested variants. This may not hold true for 10 | # different devices so be prepared to remove/adjust as needed. 11 | # 12 | # The frequency is around 433.8Mhz 13 | # 14 | # Note: The buttons send out the same data 12 times so you may need to write a filter 15 | # to pipe the output through to remove them 16 | # 17 | 18 | decoder { 19 | name = FriedlandEvo, 20 | modulation = OOK_PCM, 21 | short = 750, 22 | long = 750, 23 | reset = 9000, 24 | preamble = AA, 25 | match = 0x410408210400, 26 | unique 27 | } 28 | -------------------------------------------------------------------------------- /include/output_rtltcp.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | rtl_tcp output for rtl_433 raw data. 3 | 4 | Copyright (C) 2022 Christian Zuckschwerdt 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | #ifndef INCLUDE_OUTPUT_RTLTCP_H_ 13 | #define INCLUDE_OUTPUT_RTLTCP_H_ 14 | 15 | #include "raw_output.h" 16 | 17 | #include 18 | 19 | struct r_cfg; 20 | 21 | /** Construct rtl_tcp data output. 22 | 23 | @param host the server host to bind 24 | @param port the server port to bind 25 | @param cfg the r_api config to use 26 | @return The initialized rtltcp output instance. 27 | You must release this object with raw_output_free once you're done with it. 28 | */ 29 | struct raw_output *raw_output_rtltcp_create(char const *host, char const *port, struct r_cfg *cfg); 30 | 31 | #endif /* INCLUDE_OUTPUT_RTLTCP_H_ */ 32 | -------------------------------------------------------------------------------- /conf/chungear_bcf-0019x2.conf: -------------------------------------------------------------------------------- 1 | # Chungear Industrial Co Ltd Fan/Light Remote Controller BCF-0019x2 2 | # 3 | # https://fccid.io/KUJCE9001/User-Manual/User-Manual-171498.pdf 4 | # repeats of 13 short (252 us) or long (484 us) pulses. Packet gap is 8188 us. 5 | 6 | decoder { 7 | name = Chungear_BCF-0019x2, 8 | modulation = OOK_PWM, 9 | short = 252, 10 | long = 484, 11 | gap = 500, 12 | reset = 8500, 13 | tolerance = 100, 14 | get = id:@9:{4}, 15 | get = button:@1:{6}:[47:light_on-off 55:light_dimmer 62:fan_button_0 61:fan_button_1 59:fan_button_2 31:fan_button_3 63:no_button], 16 | } 17 | 18 | # The getters above decode the following bits: 19 | # 20 | # 11011111 10111 light on/off 21 | # 11101111 10111 light dimmer 22 | # 11111101 10111 fan button 0 23 | # 11111011 10111 fan button 1 24 | # 11110111 10111 fan button 2 25 | # 10111111 10111 fan button 3 26 | # 27 | # xxxxxxxx x0xxx dip sw 1 28 | # xxxxxxxx xx0xx dip sw 2 29 | # xxxxxxxx xxx0x dip sw 3 30 | # xxxxxxxx xxxx0 dip sw 4 31 | -------------------------------------------------------------------------------- /include/data_tag.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | Custom data tags for data struct. 3 | 4 | Copyright (C) 2021 Christian Zuckschwerdt 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | #ifndef INCLUDE_TAGS_H_ 13 | #define INCLUDE_TAGS_H_ 14 | 15 | struct gpsd_client; 16 | struct mg_mgr; 17 | struct data; 18 | 19 | typedef struct data_tag { 20 | char const *key; 21 | char const *val; 22 | char const **includes; 23 | struct gpsd_client *gpsd_client; 24 | } data_tag_t; 25 | 26 | /// Create a data tag. Might fail and return NULL. 27 | data_tag_t *data_tag_create(char *params, struct mg_mgr *mgr); 28 | 29 | /// Free a data tag. 30 | void data_tag_free(data_tag_t *tag); 31 | 32 | /// Apply a data tag. 33 | struct data *data_tag_apply(data_tag_t *tag, struct data *data, char const *filename); 34 | 35 | #endif /* INCLUDE_TAGS_H_ */ 36 | -------------------------------------------------------------------------------- /conf/pir-ef4.conf: -------------------------------------------------------------------------------- 1 | # config for PIR-EF4SBT00003. 2 | # Nicolas JOURDEN - 25/04/2019 3 | # 4 | # The sensor is known with the FCC-ID: EF4SBT00003 5 | # 6 | # Some references: 7 | # * https://fcc.report/FCC-ID/EF4SBT00003 8 | # * http://certid.org/fccid/EF4SBT00003 9 | # * https://apps.fcc.gov/eas/GetEas731Report.do?applicationId=XkBhwjLuvh2pXcri3MP%2FWA%3D%3D&fcc_id=EF4SBT00003 10 | # 11 | # 12 | # The PIR-EF4 was produced by Nortek (nortekcontrol.com) 13 | # It was released in 1996 and works with 9 Volts battery. 14 | # The sensor broadcast its ID when it is triggered. 15 | # The ID is defined on 2 bytes. 16 | # There is no CRC or other data transmitted. 17 | # The modulation is OOK with PPM at the frequency of 315MHz. : 18 | # Guessing modulation: Pulse Position Modulation with fixed pulse width 19 | # Attempting demodulation... short_width: 848, long_width: 2132, reset_limit: 8488, sync_width: 0 20 | # Use a flex decoder with -X 'n=name,m=OOK_PPM,s=848,l=2132,g=2136,r=8488' 21 | # 22 | 23 | # PIR-EF4 configuration: 24 | decoder n="PIR-EF4 sensor",m=OOK_PPM,s=848,l=2116,r=8488,rows=1,bits=16,get=@0:{16}:id 25 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | const { defaultTheme } = require('vuepress') 2 | const { searchPlugin } = require('@vuepress/plugin-search') 3 | 4 | module.exports = { 5 | lang: 'en-US', 6 | title: 'rtl_433', 7 | description: 'generic data receiver for ISM/SRD bands.', 8 | 9 | base: '/rtl_433/', 10 | markdown: { 11 | code: { 12 | lineNumbers: false, 13 | }, 14 | }, 15 | 16 | plugins: [ 17 | searchPlugin(), 18 | ], 19 | 20 | theme: defaultTheme({ 21 | repo: 'merbanan/rtl_433', 22 | displayAllHeaders: true, 23 | 24 | editLink: true, 25 | docsBranch: 'master', 26 | docsDir: 'docs', 27 | 28 | navbar: [ 29 | { text: 'Projects', link: 'https://triq.org/' }, 30 | ], 31 | 32 | sidebar: [ 33 | { text: 'Overview', link: '/' }, 34 | 'BUILDING', 35 | 'STARTING', 36 | 'CHANGELOG', 37 | 'CONTRIBUTING', 38 | 'PRIMER', 39 | 'IQ_FORMATS', 40 | 'ANALYZE', 41 | 'OPERATION', 42 | 'DATA_FORMAT', 43 | 'HARDWARE', 44 | 'INTEGRATION', 45 | 'LINKS', 46 | 'TESTS', 47 | ], 48 | }), 49 | }; 50 | -------------------------------------------------------------------------------- /do_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # performs a standard out-of-tree build and transform environment vars to cmake options 5 | # set RTLSDR=ON/OFF/AUTO (default: ON) 6 | # set SOAPYSDR=ON/OFF/AUTO (default: AUTO) 7 | # set OPENSSL=ON/OFF/AUTO (default: AUTO) 8 | # set CMAKE_TOOLCHAIN_FILE=file (default: unset) 9 | # set RUN_RTL_433_TESTS=1 (default: unset) 10 | 11 | RTLSDR="${RTLSDR:-ON}" 12 | RTSA="${RTSA:-OFF}" 13 | SOAPYSDR="${SOAPYSDR:-AUTO}" 14 | OPENSSL="${OPENSSL:-AUTO}" 15 | set -- -DENABLE_RTLSDR=$RTLSDR -DENABLE_SOAPYSDR=$SOAPYSDR -DENABLE_RTSA=$RTSA -DENABLE_OPENSSL=$OPENSSL 16 | 17 | mkdir -p build 18 | cd build 19 | if [ -n "$CMAKE_TOOLCHAIN_FILE" ] ; then 20 | cmake $@ -DCMAKE_TOOLCHAIN_FILE=../$CMAKE_TOOLCHAIN_FILE .. 21 | else 22 | cmake $@ .. 23 | fi 24 | make 25 | # make install 26 | 27 | if [ -n "$RUN_RTL_433_TESTS" ] ; then 28 | 29 | cd .. 30 | set -x 31 | git clone --depth 1 https://github.com/merbanan/rtl_433_tests.git 32 | cd rtl_433_tests 33 | export PATH=../build/src:$PATH 34 | test -f ../build/src/rtl_433 35 | 36 | # virtualenv --system-site-packages .venv 37 | # source .venv/bin/activate 38 | # pip install deepdiff 39 | make test 40 | 41 | fi 42 | -------------------------------------------------------------------------------- /conf/SMC5326-Remote.conf: -------------------------------------------------------------------------------- 1 | # SMC5326 remote control 2 | 3 | # This decoder reads two or four button pressed on the remote control. The 4 | # rubber buttons are directly connected to the SMC5326 data pins. Therefore, 5 | # pressing more than one button at the same time is possible. However this 6 | # flex spec only match a single button press 7 | # 8 | # SMC5326 are usually configured using 8-dip switch. All possible combinations 9 | # of keys are matched. You can simply press any button to 10 | # determine the remote unique keys ;-) 11 | # 12 | # see rtl_433_tests/tests/smc5326 for more information 13 | 14 | decoder { 15 | n=SMC5326-Remote, 16 | m=OOK_PWM, 17 | s=328, 18 | l=948, 19 | g=1400, 20 | r=2000, 21 | bits=26, 22 | invert, 23 | get=@0:{4}:key01:[15:++ 14:+f 12:+- 11:f+ 10:ff 8:f- 3:-+ 2:-f 0:--], 24 | get=@4:{4}:key23:[15:++ 14:+f 12:+- 11:f+ 10:ff 8:f- 3:-+ 2:-f 0:--], 25 | get=@8:{4}:key45:[15:++ 14:+f 12:+- 11:f+ 10:ff 8:f- 3:-+ 2:-f 0:--], 26 | get=@12:{4}:key67:[15:++ 14:+f 12:+- 11:f+ 10:ff 8:f- 3:-+ 2:-f 0:--], 27 | get=@16:{8}:button:[234:A 186:B 174:C 171:D] 28 | } 29 | -------------------------------------------------------------------------------- /include/samp_grab.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | IQ sample grabber (ring buffer and dumper). 3 | 4 | Copyright (C) 2018 Christian Zuckschwerdt 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | #ifndef INCLUDE_SAMP_GRAB_H_ 13 | #define INCLUDE_SAMP_GRAB_H_ 14 | 15 | #include 16 | 17 | typedef struct samp_grab { 18 | uint32_t *frequency; 19 | uint32_t *samp_rate; 20 | int *sample_size; 21 | 22 | unsigned sg_counter; 23 | char *sg_buf; 24 | unsigned sg_size; 25 | unsigned sg_index; 26 | unsigned sg_len; 27 | } samp_grab_t; 28 | 29 | samp_grab_t *samp_grab_create(unsigned size); 30 | 31 | void samp_grab_free(samp_grab_t *g); 32 | 33 | void samp_grab_push(samp_grab_t *g, unsigned char *iq_buf, uint32_t len); 34 | 35 | void samp_grab_reset(samp_grab_t *g); 36 | 37 | /// grab_end is counted in samples from end of buf. 38 | void samp_grab_write(samp_grab_t *g, unsigned grab_len, unsigned grab_end); 39 | 40 | #endif /* INCLUDE_SAMP_GRAB_H_ */ 41 | -------------------------------------------------------------------------------- /include/output_trigger.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | Trigger output for rtl_433 events. 3 | 4 | Copyright (C) 2021 Christian Zuckschwerdt 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | #ifndef INCLUDE_OUTPUT_TRIGGER_H_ 13 | #define INCLUDE_OUTPUT_TRIGGER_H_ 14 | 15 | #include "data.h" 16 | #include 17 | 18 | /// Construct data output for a trigger stream. 19 | /// 20 | /// This will print a `1` to the stream for every event. 21 | /// 22 | /// Use e.g. on a Raspberry Pi to flash the LED: 23 | /// 24 | /// $ sudo chmod a+w /sys/class/leds/led0/shot 25 | /// $ echo oneshot | sudo tee /sys/class/leds/led0/trigger 26 | /// $ rtl_433 ... -F trigger:/sys/class/leds/led0/shot 27 | /// 28 | /// @param file a trigger output stream 29 | /// @return The initialized data output. 30 | /// You must release this object with data_output_free once you're done with it. 31 | struct data_output *data_output_trigger_create(FILE *file); 32 | 33 | #endif /* INCLUDE_OUTPUT_TRIGGER_H_ */ 34 | -------------------------------------------------------------------------------- /include/write_sigrok.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | Sigrok Pulseview format writer. 3 | 4 | Copyright (C) 2020 by Christian Zuckschwerdt 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | #ifndef INCLUDE_WRITE_SIGROK_ 13 | #define INCLUDE_WRITE_SIGROK_ 14 | 15 | /** Write a Sigrok file from data dump files. 16 | 17 | @param filename file to write 18 | @param samplerate sample rate for the channels 19 | @param probes number of binary channels, needs "logic-1-1" file 20 | @param analogs number of analog channels, needs "analog-1-N-1" with N starting at probes+1 21 | @param labels channel labels, probes+analog strings or NULL for generic labels 22 | */ 23 | void write_sigrok(char const *filename, unsigned samplerate, unsigned probes, unsigned analogs, char const *labels[]); 24 | 25 | /** Open a file in a forked Pulseview. 26 | 27 | @param filename file to open in Pulseview 28 | */ 29 | void open_pulseview(char const *filename); 30 | 31 | #endif /* INCLUDE_WRITE_SIGROK_ */ 32 | -------------------------------------------------------------------------------- /conf/SalusRT300RF.conf: -------------------------------------------------------------------------------- 1 | # Decoder for Salus RT300RF thermostat 2 | # listen on 868.286Mhz 3 | # rtl_433 -Y classic -R 0 -X "name=SalusRT300RF, m=FSK_PCM, s=833, l=833, r=16000, preamble={24}0xaaaaaa,get=@0:{16}:Thermostat ID, get=@28:{4}:heat:[1:ON 2:OFF]" -f 868.286Mhz -F "mqtt://192.168.1.150,retain=1,devices=sensors/rtl_433/P[protocol]/C[channel]" 4 | # Run with: 5 | #rtl_433 -Y classic -R 0 -c /home/russ/.config/rtl_433/SalusRT300RF.conf -f 868.286Mhz -M newmodel 6 | 7 | # Report iso time: 8 | report_meta time:iso 9 | 10 | # Including the 'report_meta level' switch (commented out immediately below) means information on the signal quality and strength are added to the output. 11 | #report_meta level 12 | 13 | # specify MQTT output and formatting - replace the IP address (192.168.1.150) with your mosquitto broker's IP address and port number (I didn't need the port number) 14 | output mqtt://192.168.1.150,retain=1,devices=rtl_433/Salus[/id] 15 | 16 | 17 | decoder { 18 | name = SalusRT300RF, 19 | m = FSK_PCM, 20 | s = 833, 21 | l = 833, 22 | r = 16000, 23 | unique, 24 | preamble = {24}0xaaaaaa, 25 | get = @0:{8}:id, 26 | get = @28:{4}:Heat:[1:ON 2:OFF] 27 | } 28 | -------------------------------------------------------------------------------- /docs/HARDWARE.md: -------------------------------------------------------------------------------- 1 | # Hardware tested with rtl_433 2 | 3 | rtl_433 is known to work with or tested with the following SDR hardware: 4 | 5 | ## RTL-SDR 6 | 7 | Actively tested and supported are Realtek RTL2832 based DVB dongles (and other similar devices supported by RTL-SDR). 8 | 9 | See also [RTL-SDR](https://github.com/osmocom/rtl-sdr/). 10 | 11 | ## SoapySDR 12 | 13 | Actively tested and supported are 14 | - [LimeSDR USB](https://www.crowdsupply.com/lime-micro/limesdr) 15 | - [LimeSDR mini](https://www.crowdsupply.com/lime-micro/limesdr-mini) 16 | - [LimeNet Micro](https://www.crowdsupply.com/lime-micro/limenet-micro) 17 | - [PlutoSDR](https://www.analog.com/en/design-center/evaluation-hardware-and-software/evaluation-boards-kits/adalm-pluto.html) 18 | - [SDRplay](https://www.sdrplay.com/) (RSP1A tested) 19 | - [HackRF One](https://greatscottgadgets.com/hackrf/) (reported, we don't have a receiver) 20 | - [SoapyRemote](https://github.com/pothosware/SoapyRemote/wiki) 21 | 22 | LimeSDR and LimeNet engineering samples were kindly provided by [MyriadRf](https://myriadrf.org/). 23 | 24 | See also [SoapySDR](https://github.com/pothosware/SoapySDR/). 25 | 26 | ## Not supported 27 | 28 | - Ultra cheap 1-bit (OOK) receivers, and antenna-on-a-raspi-pin 29 | - CC1101, and alike special purpose / non general SDR chips 30 | -------------------------------------------------------------------------------- /conf/EV1527-4Button-Universal-Remote.conf: -------------------------------------------------------------------------------- 1 | # EV1527 4-Button Universal remote 2 | # 3 | # This decoder reads button pressed from a EV1527 based remote control. 4 | # The four rubber buttons are directly connected to the EV1527 data pins. 5 | # Therefore, pressing more than one button at the same time is possible. 6 | # The flex spec will match this and output a string containing the pressed 7 | # button combination e.g. 8 | # " code : REMOTE-B button : AC " 9 | # if button A and C have been pressed simultaneously 10 | # All possible combinations are matched for completeness, while not all 11 | # are are useful because of the size of the remote. 12 | # You can simply not press easily more than two buttons at the same time ;-) 13 | # 14 | # The shown example can distinguish between multiple remotes (two in this case). 15 | # If this is not desired, a simple "match=abcde" can limit the detection of 16 | # one unique EV1527 code. 17 | 18 | decoder { 19 | n=EV1527-Remote, 20 | m=OOK_PWM, 21 | s=369, 22 | l=1072, 23 | g=1400, 24 | r=12840, 25 | bits>=24, 26 | repeats>=3, 27 | invert, 28 | get=@0:{20}:code:[123456:REMOTE-A 987654:REMOTE-B], 29 | get=@20:{4}:button:[1:A 2:B 3:AB 4:C 5:AC 6:BC 7:ABC 8:D 9:AD 10:BD 11:ABD 12:CD 13:ACD 14:BCD 15:ALL], 30 | unique 31 | } 32 | -------------------------------------------------------------------------------- /conf/CAME-TOP432.conf: -------------------------------------------------------------------------------- 1 | # CAME-TOP432.conf 2 | # Copyright (C) 2020 JFORESTIER 3 | # Decode CAME remote control TOP-432EV, TOP-432NA, TOP-432EE. 4 | # This remote control is used for garage door and sliding gate. 5 | # It transmits on 433.92 MHz (as it is written on the case), built since 2006 6 | # (as said on the FCC site https://www.fcc.gov/oet/ea/fccid with reference M48 TOP-NA) 7 | # 8 | # It works with CAME radio receiver cards "AF43S", capable of handling 4096 codes. 9 | # CAME is an Italian company. These remote controls are mainly sold in Europe (France, Italy, Belgium). 10 | # https://www.came.com and https://www.came-europe.com . 11 | 12 | # Device information and test files: 13 | # https://github.com/psa-jforestier/rtl_433_tests/tree/master/tests/Came/TOP432 14 | # The device uses PWM encoding, 15 | # - 0 is encoded as 320 us gap and 640 us pulse, 16 | # - 1 is encoded as 640 us gap and 320 us pulse. 17 | # The device sends a 4 times the packet when a button on the remote control is pressed. 18 | # A transmission starts with a 320 us pulse. At the end of the packet, there is a minimum of 36 periods of 320us between messages (11520us) 19 | 20 | decoder { 21 | name=CAME-TOP432, 22 | modulation=OOK_PWM, 23 | short=320, 24 | long=640, 25 | reset=10000, 26 | bits=13, 27 | gap=830, 28 | preamble={1}8, 29 | get=@1:{12}:button_code 30 | } 31 | -------------------------------------------------------------------------------- /cmake/Modules/GetGitRevisionDescription.cmake.in: -------------------------------------------------------------------------------- 1 | # 2 | # Internal file for GetGitRevisionDescription.cmake 3 | # 4 | # Requires CMake 2.6 or newer (uses the 'function' command) 5 | # 6 | # Original Author: 7 | # 2009-2010 Ryan Pavlik 8 | # http://academic.cleardefinition.com 9 | # Iowa State University HCI Graduate Program/VRAC 10 | # 11 | # Copyright Iowa State University 2009-2010. 12 | # Distributed under the Boost Software License, Version 1.0. 13 | # (See accompanying file LICENSE_1_0.txt or copy at 14 | # http://www.boost.org/LICENSE_1_0.txt) 15 | 16 | set(HEAD_HASH) 17 | 18 | file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) 19 | 20 | string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) 21 | if(HEAD_CONTENTS MATCHES "ref") 22 | # named branch 23 | string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") 24 | if(EXISTS "@GIT_DIR@/${HEAD_REF}") 25 | configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) 26 | elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}") 27 | configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) 28 | set(HEAD_HASH "${HEAD_REF}") 29 | endif() 30 | else() 31 | # detached HEAD 32 | configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) 33 | endif() 34 | 35 | if(NOT HEAD_HASH) 36 | file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) 37 | string(STRIP "${HEAD_HASH}" HEAD_HASH) 38 | endif() 39 | -------------------------------------------------------------------------------- /include/confparse.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | Light-weight (i.e. dumb) config-file parser. 3 | 4 | Copyright (C) 2018 Christian W. Zuckschwerdt 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | #ifndef INCLUDE_CONFPARSE_H_ 13 | #define INCLUDE_CONFPARSE_H_ 14 | 15 | struct conf_keywords { 16 | char const *keyword; 17 | int key; 18 | }; 19 | 20 | /** Check if a file exists and can be read. 21 | 22 | @param path input file name 23 | @return 1 if the file exists and is readable, 0 otherwise 24 | */ 25 | int hasconf(char const *path); 26 | 27 | /** Open a config file, read contents to memory. 28 | 29 | @param path input file name 30 | @return allocated memory containing the config file 31 | */ 32 | char *readconf(char const *path); 33 | 34 | /** Return the next keyword token and set the optional argument. 35 | 36 | @param conf current position in conf 37 | @param keywords list of possible keywords 38 | @param arg optional out pointer to a argument string 39 | @return the next keyword token, -1 otherwise. 40 | */ 41 | int getconf(char **conf, struct conf_keywords const keywords[], char **arg); 42 | 43 | #endif /* INCLUDE_CONFPARSE_H_ */ 44 | -------------------------------------------------------------------------------- /include/abuf.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | array buffer (string builder). 3 | 4 | Copyright (C) 2018 Christian Zuckschwerdt 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | #ifndef INCLUDE_ABUF_H_ 13 | #define INCLUDE_ABUF_H_ 14 | 15 | #if defined _MSC_VER || defined ESP32 // Microsoft Visual Studio or ESP32 16 | // MSC and ESP32 have something like C99 restrict as __restrict 17 | #ifndef restrict 18 | #define restrict __restrict 19 | #endif 20 | #endif 21 | // Defined in newer for MSVC. 22 | #ifndef _Printf_format_string_ 23 | #define _Printf_format_string_ 24 | #endif 25 | 26 | #include 27 | 28 | typedef struct abuf { 29 | char *head; 30 | char *tail; 31 | size_t left; 32 | } abuf_t; 33 | 34 | void abuf_init(abuf_t *buf, char *dst, size_t len); 35 | 36 | void abuf_setnull(abuf_t *buf); 37 | 38 | char *abuf_push(abuf_t *buf); 39 | 40 | void abuf_pop(abuf_t *buf, char *end); 41 | 42 | void abuf_cat(abuf_t *buf, const char *str); 43 | 44 | int abuf_printf(abuf_t *buf, _Printf_format_string_ char const *restrict format, ...) 45 | #if defined(__GNUC__) || defined(__clang__) 46 | __attribute__((format(printf, 2, 3))) 47 | #endif 48 | ; 49 | 50 | #endif /* INCLUDE_ABUF_H_ */ 51 | -------------------------------------------------------------------------------- /cmake/Toolchain-gcc-mingw-w64-i686.cmake: -------------------------------------------------------------------------------- 1 | # CMAKE_SYSROOT and CMAKE_STAGING_PREFIX need 3.0 2 | cmake_minimum_required(VERSION 3.0) 3 | 4 | # Linux, Windows, or Darwin 5 | SET(CMAKE_SYSTEM_NAME Windows) 6 | 7 | # not really needed 8 | SET(CMAKE_SYSTEM_VERSION 1) 9 | 10 | # specify the base directory for the cross compiler 11 | IF(DEFINED ENV{tools}) 12 | SET(tools $ENV{tools}) 13 | ELSE() 14 | SET(tools /usr) 15 | ENDIF() 16 | 17 | # specify the cross compiler, choose 32/64 18 | SET(CMAKE_C_COMPILER ${tools}/bin/i686-w64-mingw32-gcc) 19 | #SET(CMAKE_C_COMPILER ${tools}/bin/x86_64-w64-mingw32-gcc) 20 | #SET(CMAKE_RC_COMPILER ${tools}/bin/i686-w64-mingw32-windres) 21 | 22 | # where is the target environment, choose 32/64 23 | #SET(CMAKE_FIND_ROOT_PATH ${tools}/lib/gcc/i686-w64-mingw32/4.8) 24 | #SET(CMAKE_FIND_ROOT_PATH ${tools}/lib/gcc/x86_64-w64-mingw32/4.8) 25 | 26 | # NOTE: use a sysroot with libusb and rtl-sdr if available 27 | IF(DEFINED ENV{CMAKE_SYSROOT}) 28 | SET(CMAKE_SYSROOT $ENV{CMAKE_SYSROOT}) 29 | SET(CMAKE_STAGING_PREFIX $ENV{CMAKE_SYSROOT}/usr) 30 | #SET(CMAKE_INSTALL_PREFIX /usr) 31 | #list(INSERT CMAKE_FIND_ROOT_PATH 0 $ENV{CMAKE_SYSROOT}) 32 | ENDIF() 33 | 34 | # search for programs in the build host directories 35 | SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 36 | # for libraries and headers in the target directories 37 | SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 38 | SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 39 | SET(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 40 | -------------------------------------------------------------------------------- /cmake/Toolchain-gcc-mingw-w64-x86-64.cmake: -------------------------------------------------------------------------------- 1 | # CMAKE_SYSROOT and CMAKE_STAGING_PREFIX need 3.0 2 | cmake_minimum_required(VERSION 3.0) 3 | 4 | # Linux, Windows, or Darwin 5 | SET(CMAKE_SYSTEM_NAME Windows) 6 | 7 | # not really needed 8 | SET(CMAKE_SYSTEM_VERSION 1) 9 | 10 | # specify the base directory for the cross compiler 11 | IF(DEFINED ENV{tools}) 12 | SET(tools $ENV{tools}) 13 | ELSE() 14 | SET(tools /usr) 15 | ENDIF() 16 | 17 | # specify the cross compiler, choose 32/64 18 | #SET(CMAKE_C_COMPILER ${tools}/bin/i686-w64-mingw32-gcc) 19 | SET(CMAKE_C_COMPILER ${tools}/bin/x86_64-w64-mingw32-gcc) 20 | #SET(CMAKE_RC_COMPILER ${tools}/bin/x86_64-w64-mingw32-windres) 21 | 22 | # where is the target environment, choose 32/64 23 | #SET(CMAKE_FIND_ROOT_PATH ${tools}/lib/gcc/i686-w64-mingw32/4.8) 24 | #SET(CMAKE_FIND_ROOT_PATH ${tools}/lib/gcc/x86_64-w64-mingw32/4.8) 25 | 26 | # NOTE: use a sysroot with libusb and rtl-sdr if available 27 | IF(DEFINED ENV{CMAKE_SYSROOT}) 28 | SET(CMAKE_SYSROOT $ENV{CMAKE_SYSROOT}) 29 | SET(CMAKE_STAGING_PREFIX $ENV{CMAKE_SYSROOT}/usr) 30 | #SET(CMAKE_INSTALL_PREFIX /usr) 31 | #list(INSERT CMAKE_FIND_ROOT_PATH 0 $ENV{CMAKE_SYSROOT}) 32 | ENDIF() 33 | 34 | # search for programs in the build host directories 35 | SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 36 | # for libraries and headers in the target directories 37 | SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 38 | SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 39 | SET(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 40 | -------------------------------------------------------------------------------- /cmake/cmake_uninstall.cmake.in: -------------------------------------------------------------------------------- 1 | # http://www.vtk.org/Wiki/CMake_FAQ#Can_I_do_.22make_uninstall.22_with_CMake.3F 2 | 3 | IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") 4 | MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") 5 | ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") 6 | 7 | FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) 8 | STRING(REGEX REPLACE "\n" ";" files "${files}") 9 | FOREACH(file ${files}) 10 | MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") 11 | IF(EXISTS "$ENV{DESTDIR}${file}") 12 | EXEC_PROGRAM( 13 | "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" 14 | OUTPUT_VARIABLE rm_out 15 | RETURN_VALUE rm_retval 16 | ) 17 | IF(NOT "${rm_retval}" STREQUAL 0) 18 | MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") 19 | ENDIF(NOT "${rm_retval}" STREQUAL 0) 20 | ELSEIF(IS_SYMLINK "$ENV{DESTDIR}${file}") 21 | EXEC_PROGRAM( 22 | "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" 23 | OUTPUT_VARIABLE rm_out 24 | RETURN_VALUE rm_retval 25 | ) 26 | IF(NOT "${rm_retval}" STREQUAL 0) 27 | MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") 28 | ENDIF(NOT "${rm_retval}" STREQUAL 0) 29 | ELSE(EXISTS "$ENV{DESTDIR}${file}") 30 | MESSAGE(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") 31 | ENDIF(EXISTS "$ENV{DESTDIR}${file}") 32 | ENDFOREACH(file) 33 | -------------------------------------------------------------------------------- /src/mongoose_ipv6.patch: -------------------------------------------------------------------------------- 1 | From 16eac315358e79e919c82cdaf3bb7be6ccf35e83 Mon Sep 17 00:00:00 2001 2 | From: "Christian W. Zuckschwerdt" 3 | Date: Fri, 22 Mar 2019 17:51:30 +0100 4 | Subject: [PATCH] Fix IPv6 connect in mg_socket_if_connect_tcp 5 | 6 | --- 7 | src/mg_net_if_socket.c | 6 ++++-- 8 | 1 file changed, 4 insertions(+), 2 deletions(-) 9 | 10 | diff --git a/src/mg_net_if_socket.c b/src/mg_net_if_socket.c 11 | index 607e2b53..51c0788f 100644 12 | --- a/src/mg_net_if_socket.c 13 | +++ b/src/mg_net_if_socket.c 14 | @@ -37,7 +37,7 @@ static int mg_is_error(void) { 15 | void mg_socket_if_connect_tcp(struct mg_connection *nc, 16 | const union socket_address *sa) { 17 | int rc, proto = 0; 18 | - nc->sock = socket(AF_INET, SOCK_STREAM, proto); 19 | + nc->sock = socket(sa->sa.sa_family, SOCK_STREAM, proto); 20 | if (nc->sock == INVALID_SOCKET) { 21 | nc->err = mg_get_errno() ? mg_get_errno() : 1; 22 | return; 23 | @@ -45,7 +45,9 @@ void mg_socket_if_connect_tcp(struct mg_connection *nc, 24 | #if !defined(MG_ESP8266) 25 | mg_set_non_blocking_mode(nc->sock); 26 | #endif 27 | - rc = connect(nc->sock, &sa->sa, sizeof(sa->sin)); 28 | + socklen_t sa_len = 29 | + (sa->sa.sa_family == AF_INET) ? sizeof(sa->sin) : sizeof(sa->sin6); 30 | + rc = connect(nc->sock, &sa->sa, sa_len); 31 | nc->err = rc < 0 && mg_is_error() ? mg_get_errno() : 0; 32 | DBG(("%p sock %d rc %d errno %d err %d", nc, nc->sock, rc, mg_get_errno(), 33 | nc->err)); 34 | -------------------------------------------------------------------------------- /conf/silverline_doorbell.conf: -------------------------------------------------------------------------------- 1 | # Silverline doorbell 2 | 3 | # A typical x1527 OTP encoder device 4 | # sends 12 tristate "bits" encoded as 2 bits each and a sync pulse 5 | 6 | decoder { 7 | name = Silverline-Doorbell, 8 | modulation = OOK_PWM, 9 | short = 120, 10 | long = 404, 11 | gap = 468, 12 | reset = 4472, 13 | bits = 25, 14 | get = channel:@1:555, 15 | get = sound:@17:15, 16 | } 17 | 18 | # The flex spec supports selecting random bits with a getter mask. E.g. 19 | # in this x1527 tristate example every odd bit is `1` and you want to get 20 | # 6 bits "channel" from the first 12 bits (discarding every odd bit) use: 21 | # get=channel:@1:555 22 | # Effectively this is 11 bits (starting with 1 bit offset to discard the 23 | # first bit) because the mask is always aligned on the first set bit 24 | # (i.e. 555, aaa, 000555, ... are identical). 25 | # 26 | # Imagine the 25 bits of the transmission as: 27 | # 1?1?1?1?1?1?1?1?1?1?1?1?1 28 | # The mask `555` is: 29 | # 0000 0101 0101 0101 30 | # aligned to the first set bit 31 | # 10101010101 32 | # offset 1 bit (`@1`) it selects like this: 33 | # 1?1?1?1?1?1?1?1?1?1?1?1?1 34 | # 10101010101 35 | # -> 36 | # ? ? ? ? ? ? 37 | # resulting in that 6 bit value `??????`. 38 | # 39 | # The same way 3 bits "sound" is `get=sound:@17:15` (where `15` here is 40 | # hex for the 5-bit pattern `10101` -- or use `a8` if you want to left align 41 | # the pattern in the byte there). 42 | -------------------------------------------------------------------------------- /cmake/Modules/FindLibRTLSDR.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find LibRTLSDR 2 | # Once done this will define 3 | # 4 | # LibRTLSDR_FOUND - System has librtlsdr 5 | # LibRTLSDR_INCLUDE_DIRS - The librtlsdr include directories 6 | # LibRTLSDR_LIBRARIES - The libraries needed to use librtlsdr 7 | # LibRTLSDR_DEFINITIONS - Compiler switches required for using librtlsdr 8 | # LibRTLSDR_VERSION - The librtlsdr version 9 | # 10 | 11 | find_package(PkgConfig) 12 | pkg_check_modules(PC_LibRTLSDR QUIET librtlsdr) 13 | set(LibRTLSDR_DEFINITIONS ${PC_LibRTLSDR_CFLAGS_OTHER}) 14 | 15 | find_path(LibRTLSDR_INCLUDE_DIR NAMES rtl-sdr.h 16 | HINTS ${PC_LibRTLSDR_INCLUDE_DIRS} 17 | PATHS 18 | /usr/include 19 | /usr/local/include ) 20 | 21 | find_library(LibRTLSDR_LIBRARY NAMES rtlsdr 22 | HINTS ${PC_LibRTLSDR_LIBRARY_DIRS} 23 | PATHS 24 | /usr/lib 25 | /usr/local/lib ) 26 | 27 | set(LibRTLSDR_VERSION ${PC_LibRTLSDR_VERSION}) 28 | 29 | include(FindPackageHandleStandardArgs) 30 | # handle the QUIETLY and REQUIRED arguments and set LibRTLSDR_FOUND to TRUE 31 | # if all listed variables are TRUE 32 | find_package_handle_standard_args(LibRTLSDR 33 | REQUIRED_VARS LibRTLSDR_LIBRARY LibRTLSDR_INCLUDE_DIR 34 | VERSION_VAR LibRTLSDR_VERSION) 35 | 36 | mark_as_advanced(LibRTLSDR_LIBRARY LibRTLSDR_INCLUDE_DIR LibRTLSDR_VERSION) 37 | 38 | set(LibRTLSDR_LIBRARIES ${LibRTLSDR_LIBRARY} ) 39 | set(LibRTLSDR_INCLUDE_DIRS ${LibRTLSDR_INCLUDE_DIR} ) 40 | -------------------------------------------------------------------------------- /examples/rtl_433_statsd_pipe.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Statsd monitoring for rtl_433 using pipes.""" 4 | 5 | # Needs Python statsd Network plugin, s.a. https://github.com/jsocol/pystatsd 6 | # pip install pystatsd 7 | # -or- 8 | # curl -o statsd.py https://github.com/jsocol/pystatsd/raw/v3.2/statsd/client.py 9 | 10 | import sys 11 | import json 12 | from statsd import StatsClient 13 | 14 | 15 | def sanitize(text): 16 | return text.replace(" ", "_") 17 | 18 | 19 | def rtl_433_probe(): 20 | statsd_host = "127.0.0.1" 21 | statsd_port = 8125 22 | statsd_prefix = 'rtlsdr' 23 | 24 | statsd = StatsClient(host=statsd_host, 25 | port=statsd_port, 26 | prefix=statsd_prefix) 27 | 28 | while True: 29 | line = sys.stdin.readline() 30 | if not line: 31 | break 32 | try: 33 | data = json.loads(line) 34 | 35 | label = sanitize(data["model"]) 36 | if "channel" in data: 37 | label += ".CH" + str(data["channel"]) 38 | 39 | if "battery_ok" in data: 40 | statsd.gauge(label + '.battery', data["battery_ok"]) 41 | 42 | if "humidity" in data: 43 | statsd.gauge(label + '.humidity', data["humidity"]) 44 | 45 | statsd.gauge(label + '.temperature', data["temperature_C"]) 46 | 47 | except KeyError: 48 | pass 49 | 50 | except ValueError: 51 | pass 52 | 53 | 54 | if __name__ == "__main__": 55 | rtl_433_probe() 56 | -------------------------------------------------------------------------------- /include/fatal.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | Fatal abort and warning macros for allocs. 3 | 4 | Copyright (C) 2019 Christian W. Zuckschwerdt 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | #ifndef INCLUDE_FATAL_H_ 13 | #define INCLUDE_FATAL_H_ 14 | 15 | #define STRINGIFYX(x) #x 16 | #define STRINGIFY(x) STRINGIFYX(x) 17 | #define FILE_LINE __FILE__ ":" STRINGIFY(__LINE__) 18 | #define FATAL(what) do { fprintf(stderr, "FATAL: " what " from " FILE_LINE "\n"); exit(1); } while (0) 19 | #define FATAL_MALLOC(what) FATAL("low memory? malloc() failed in " what) 20 | #define FATAL_CALLOC(what) FATAL("low memory? calloc() failed in " what) 21 | #define FATAL_REALLOC(what) FATAL("low memory? realloc() failed in " what) 22 | #define FATAL_STRDUP(what) FATAL("low memory? strdup() failed in " what) 23 | #define WARN(what) fprintf(stderr, "WARNING: " what " from " FILE_LINE "\n") 24 | #define WARN_MALLOC(what) WARN("low memory? malloc() failed in " what) 25 | #define WARN_CALLOC(what) WARN("low memory? calloc() failed in " what) 26 | #define WARN_REALLOC(what) WARN("low memory? realloc() failed in " what) 27 | #define WARN_STRDUP(what) WARN("low memory? strdup() failed in " what) 28 | 29 | /* 30 | Use like this: 31 | 32 | char *buf = malloc(size); 33 | if (!buf) 34 | FATAL_MALLOC("my_func()"); 35 | 36 | */ 37 | 38 | #endif /* INCLUDE_FATAL_H_ */ 39 | -------------------------------------------------------------------------------- /conf/FAN-53T.conf: -------------------------------------------------------------------------------- 1 | # Decoder for FAN-53T ceiling fan/light remote control 2 | # Very similar to FAN-11T. 3 | # 4 | # https://fccid.io/2AAZPFAN-53T/User-Manual/User-manual-2228959 5 | # 6 | # Code Format: 01 0 <6-bit button map> (all bits inverted) 7 | # Button Map Bits: [fan1, fan2, fan3, (some remotes send this when the button is released), fan0, light] 8 | # 9 | # Dip switches are used as a unique ID to talk to a specific unit 10 | # 11 | # Some remotes have buttons labeled 0,1,2,3 12 | # Others are labeled Off, Hi, Med, Low 13 | # 14 | # Fan 0 = "Off" 15 | # Fan 1 = "High" 16 | # Fan 2 = "Medium" 17 | # Fan 3 = "Low" 18 | # 19 | # Dipswitch: 0000 20 | # Fan 1 - 01 0000 0 100000 21 | # Fan 2 - 01 0000 0 010000 22 | # Fan 3 - 01 0000 0 001000 23 | # Fan 0 - 01 0000 0 000010 24 | # Light - 01 0000 0 000001 25 | # 26 | # Dipswitch: 0001 27 | # Fan 1 - 01 0001 0 100000 28 | # Fan 2 - 01 0001 0 010000 29 | # Fan 3 - 01 0001 0 001000 30 | # Fan 0 - 01 0001 0 000010 31 | # Light - 01 0001 0 000001 32 | # 33 | # Dipswitch: 1000 34 | # Fan 1 - 01 1000 0 100000 35 | # Fan 2 - 01 1000 0 010000 36 | # Fan 3 - 01 1000 0 001000 37 | # Fan 0 - 01 1000 0 000010 38 | # Light - 01 1000 0 000001 39 | 40 | 41 | frequency 303.900M 42 | 43 | decoder { 44 | name = FAN-53T, 45 | modulation = OOK_PWM, 46 | short = 360, 47 | long = 700, 48 | gap = 0, 49 | reset = 2000, 50 | invert, 51 | bits = 13, 52 | get = id:@2:{4}, 53 | get = button:@7:{6}:[32:fan_Hi 16:fan_Med 8:fan_Low 4:button_Released 2:fan_Off 1:light 0: ], 54 | } -------------------------------------------------------------------------------- /include/am_analyze.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | AM signal analyzer. 3 | 4 | Copyright (C) 2018 Christian Zuckschwerdt 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | #ifndef INCLUDE_AM_ANALYZE_H_ 13 | #define INCLUDE_AM_ANALYZE_H_ 14 | 15 | #include 16 | #include "samp_grab.h" 17 | 18 | #define PULSE_DATA_SIZE 4000 /* maximum number of pulses */ 19 | 20 | typedef struct am_analyze { 21 | int level_limit; 22 | int override_short; 23 | int override_long; 24 | uint32_t *frequency; 25 | uint32_t *samp_rate; 26 | int *sample_size; 27 | 28 | /* state */ 29 | unsigned counter; 30 | unsigned print; 31 | unsigned print2; 32 | unsigned pulses_found; 33 | unsigned prev_pulse_start; 34 | unsigned pulse_start; 35 | unsigned pulse_end; 36 | unsigned pulse_avg; 37 | unsigned signal_start; 38 | unsigned signal_pulse_counter; 39 | unsigned signal_pulse_data[4000][3]; 40 | } am_analyze_t; 41 | 42 | /// Create an AM-Analyzer. Might fail and return NULL. 43 | am_analyze_t *am_analyze_create(void); 44 | 45 | void am_analyze_free(am_analyze_t *a); 46 | 47 | void am_analyze_skip(am_analyze_t *a, unsigned n_samples); 48 | 49 | void am_analyze(am_analyze_t *a, int16_t *am_buf, unsigned n_samples, int debug_output, samp_grab_t *g); 50 | 51 | void am_analyze_classify(am_analyze_t *aa); 52 | 53 | #endif /* INCLUDE_AM_ANALYZE_H_ */ 54 | -------------------------------------------------------------------------------- /cmake/Modules/FindGperftools.cmake: -------------------------------------------------------------------------------- 1 | # Tries to find Gperftools. 2 | # 3 | # Usage of this module as follows: 4 | # 5 | # find_package(Gperftools) 6 | # 7 | # Variables used by this module, they can change the default behaviour and need 8 | # to be set before calling find_package: 9 | # 10 | # Gperftools_ROOT_DIR Set this variable to the root installation of 11 | # Gperftools if the module has problems finding 12 | # the proper installation path. 13 | # 14 | # Variables defined by this module: 15 | # 16 | # GPERFTOOLS_FOUND System has Gperftools libs/headers 17 | # GPERFTOOLS_LIBRARIES The Gperftools libraries (tcmalloc & profiler) 18 | # GPERFTOOLS_INCLUDE_DIR The location of Gperftools headers 19 | 20 | find_library(GPERFTOOLS_TCMALLOC 21 | NAMES tcmalloc 22 | HINTS ${Gperftools_ROOT_DIR}/lib) 23 | 24 | find_library(GPERFTOOLS_PROFILER 25 | NAMES profiler 26 | HINTS ${Gperftools_ROOT_DIR}/lib) 27 | 28 | find_library(GPERFTOOLS_TCMALLOC_AND_PROFILER 29 | NAMES tcmalloc_and_profiler 30 | HINTS ${Gperftools_ROOT_DIR}/lib) 31 | 32 | find_path(GPERFTOOLS_INCLUDE_DIR 33 | NAMES gperftools/heap-profiler.h 34 | HINTS ${Gperftools_ROOT_DIR}/include) 35 | 36 | set(GPERFTOOLS_LIBRARIES ${GPERFTOOLS_TCMALLOC_AND_PROFILER}) 37 | 38 | include(FindPackageHandleStandardArgs) 39 | find_package_handle_standard_args( 40 | Gperftools 41 | DEFAULT_MSG 42 | GPERFTOOLS_LIBRARIES 43 | GPERFTOOLS_INCLUDE_DIR) 44 | 45 | mark_as_advanced( 46 | Gperftools_ROOT_DIR 47 | GPERFTOOLS_TCMALLOC 48 | GPERFTOOLS_PROFILER 49 | GPERFTOOLS_TCMALLOC_AND_PROFILER 50 | GPERFTOOLS_LIBRARIES 51 | GPERFTOOLS_INCLUDE_DIR) 52 | -------------------------------------------------------------------------------- /src/abuf.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | array buffer (string builder). 3 | 4 | Copyright (C) 2018 Christian Zuckschwerdt 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "abuf.h" 18 | 19 | void abuf_init(abuf_t *buf, char *dst, size_t len) 20 | { 21 | buf->head = dst; 22 | buf->tail = dst; 23 | buf->left = len; 24 | } 25 | 26 | void abuf_setnull(abuf_t *buf) 27 | { 28 | buf->head = NULL; 29 | buf->tail = NULL; 30 | buf->left = 0; 31 | } 32 | 33 | char *abuf_push(abuf_t *buf) 34 | { 35 | return buf->tail; 36 | } 37 | 38 | void abuf_pop(abuf_t *buf, char *end) 39 | { 40 | buf->left += buf->tail - end; 41 | buf->tail = end; 42 | } 43 | 44 | void abuf_cat(abuf_t *buf, char const *str) 45 | { 46 | size_t len = strlen(str); 47 | if (buf->left >= len + 1) { 48 | strcpy(buf->tail, str); 49 | buf->tail += len; 50 | buf->left -= len; 51 | } 52 | } 53 | 54 | int abuf_printf(abuf_t *buf, _Printf_format_string_ char const *restrict format, ...) 55 | { 56 | va_list ap; 57 | va_start(ap, format); 58 | 59 | int n = vsnprintf(buf->tail, buf->left, format, ap); 60 | 61 | if (n > 0) { 62 | size_t len = (size_t)n < buf->left ? (size_t)n : buf->left; 63 | buf->tail += len; 64 | buf->left -= len; 65 | } 66 | 67 | va_end(ap); 68 | return n; 69 | } 70 | -------------------------------------------------------------------------------- /src/output_trigger.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Trigger output for rtl_433 events. 3 | 4 | Copyright (C) 2021 Christian Zuckschwerdt 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | #include "output_trigger.h" 13 | 14 | #include "data.h" 15 | #include "r_util.h" 16 | #include "fatal.h" 17 | 18 | #include 19 | #include 20 | 21 | /* Trigger printer */ 22 | 23 | typedef struct { 24 | struct data_output output; 25 | FILE *file; 26 | } data_output_trigger_t; 27 | 28 | static void R_API_CALLCONV print_trigger_data(data_output_t *output, data_t *data, char const *format) 29 | { 30 | UNUSED(data); 31 | UNUSED(format); 32 | data_output_trigger_t *trigger = (data_output_trigger_t *)output; 33 | 34 | fputc('1', trigger->file); 35 | fflush(trigger->file); 36 | } 37 | 38 | static void R_API_CALLCONV data_output_trigger_free(data_output_t *output) 39 | { 40 | if (!output) 41 | return; 42 | 43 | free(output); 44 | } 45 | 46 | struct data_output *data_output_trigger_create(FILE *file) 47 | { 48 | data_output_trigger_t *trigger = calloc(1, sizeof(data_output_trigger_t)); 49 | if (!trigger) { 50 | WARN_CALLOC("data_output_trigger_create()"); 51 | return NULL; // NOTE: returns NULL on alloc failure. 52 | } 53 | 54 | trigger->output.print_data = print_trigger_data; 55 | trigger->output.output_free = data_output_trigger_free; 56 | trigger->file = file; 57 | 58 | return &trigger->output; 59 | } 60 | -------------------------------------------------------------------------------- /include/list.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | Generic list. 3 | 4 | Copyright (C) 2018 Christian Zuckschwerdt 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | #ifndef INCLUDE_LIST_H_ 13 | #define INCLUDE_LIST_H_ 14 | 15 | #include 16 | 17 | /// Dynamically growing list, elems is always NULL terminated, call list_ensure_size() to alloc elems. 18 | typedef struct list { 19 | void **elems; 20 | size_t size; 21 | size_t len; 22 | } list_t; 23 | 24 | typedef void (*list_elem_free_fn)(void *); 25 | 26 | /// Alloc elems if needed and ensure the list has room for at least min_size elements. 27 | void list_ensure_size(list_t *list, size_t min_size); 28 | 29 | /// Add to the end of elems, allocs or grows the list if needed and ensures the list has a terminating NULL. 30 | void list_push(list_t *list, void *p); 31 | 32 | /// Adds all elements of a NULL terminated list to the end of elems, allocs or grows the list if needed and ensures the list has a terminating NULL. 33 | void list_push_all(list_t *list, void **p); 34 | 35 | /// Remove element from the list, frees element with fn. 36 | void list_remove(list_t *list, size_t idx, list_elem_free_fn elem_free); 37 | 38 | /// Clear the list, frees each element with fn, does not free backing or list itself. 39 | void list_clear(list_t *list, list_elem_free_fn elem_free); 40 | 41 | /// Clear the list, free backing, does not free list itself. 42 | void list_free_elems(list_t *list, list_elem_free_fn elem_free); 43 | 44 | #endif /* INCLUDE_LIST_H_ */ 45 | -------------------------------------------------------------------------------- /conf/MondeoRemote.conf: -------------------------------------------------------------------------------- 1 | # Decoder for Ford Mondeo key remote 2 | # 3 | # Tested with: 4 | # UK 2013 Ford Mondeo key remote 5 | # 6 | # Operates on 433.920MHz. 7 | # 8 | # S.a https://github.com/merbanan/rtl_433/issues/1282 9 | # 10 | # The data shows the expected clear header, then encrypted data, then perhaps a 16-bit checksum. 11 | # It's strange though that so much of the encrypted data collides. It should be random -- it isn't. 12 | # 13 | # Example cods: 14 | # 9ca8 7130 449d 20 c816 0fb3 92a2 9251 1c0c 15 | # 9ca8 7130 449d 22 62bc a519 383a aa5b 8e6c 16 | # 9ca8 7130 449d 20 9846 5fe3 c2d2 c263 edc4 17 | # 9ca8 7130 449d 20 9846 5fe2 c2d2 c161 3284 18 | # 9ca8 7130 449d 20 9846 5fe2 42da c1e8 46bc 19 | # 9ca8 7130 449d 20 8856 4ff2 52c2 d1f6 5344 20 | # 9ca8 7130 449d 22 429c 8538 981a c9fb 8b38 21 | # 9ca8 7130 449d 20 8856 4ff2 d2ca d17f 277c 22 | # 9ca8 7130 449d 22 6ab4 ad11 3032 f282 fd54 23 | # 9ca8 7130 449d 22 6ab4 ad10 3032 f180 2214 24 | # 9ca8 7130 449d 22 62bc a518 b83a fa08 9d2c 25 | # 9ca8 7130 449d 20 9846 5fe2 c2fa c188 71e4 26 | # 9ca8 7130 449d 20 8856 4ff3 d2e2 d294 bb58 27 | # 9ca8 7130 449d 20 8856 4ff2 d2e2 d196 6418 28 | # 9ca8 7130 449d 20 8856 4ff2 52ea d21c 7514 29 | # 9ca8 7130 449d 22 62bc a518 383a e999 85ac 30 | # 9ca8 7130 449d 20 d806 1fa3 8212 82a4 518c 31 | # 9ca8 7130 449d 20 d806 1fa2 8212 81a6 8ecc 32 | # 9ca8 7130 449d 20 d806 1fa2 021a 822c 9fc4 33 | # 9ca8 7130 449d 22 c21c 05b8 989a 19ab 5f3c 34 | # 9ca8 7130 449d 20 c816 0fb0 9202 93b0 917c 35 | # 9ca8 7130 449d 22 ca14 0db0 9092 01b2 f884 36 | # 9ca8 7130 449d 22 c21c 05ba 989a 0bb8 5cfc 37 | # 9ca8 7130 449d 20 c816 0fb2 920a 91b8 9b34 38 | 39 | decoder { 40 | name=Mondeo-Remote, 41 | modulation=FSK_MC_ZEROBIT, 42 | short=64, 43 | long=64, 44 | reset=136, 45 | preamble=aaae, 46 | } 47 | -------------------------------------------------------------------------------- /include/compat_pthread.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDE_COMPAT_PTHREAD_H_ 2 | #define INCLUDE_COMPAT_PTHREAD_H_ 3 | 4 | #ifndef THREADS 5 | 6 | // explicit request for "no threads" 7 | 8 | #elif _MSC_VER>=1200 9 | 10 | #include 11 | #include 12 | #define THREAD_CALL __stdcall 13 | #define THREAD_RETURN unsigned int 14 | typedef HANDLE pthread_t; 15 | #define pthread_create(tp, x, p, d) ((*tp=(HANDLE)_beginthread(p, 0, d)) == NULL ? -1 : 0) 16 | #define pthread_cancel(th) TerminateThread(th, 0) 17 | #define pthread_join(th, p) WaitForSingleObject(th, INFINITE) 18 | #define pthread_equal(a, b) ((a) == (b)) 19 | #define pthread_self() GetCurrentThread() 20 | 21 | typedef HANDLE pthread_mutex_t; 22 | #define pthread_mutex_init(mp, a) ((*mp = CreateMutex(NULL, FALSE, NULL)) == NULL ? -1 : 0) 23 | #define pthread_mutex_destroy(mp) (CloseHandle(*mp) == 0 ? -1 : 0) 24 | #define pthread_mutex_lock(mp) (WaitForSingleObject(*mp, INFINITE) == WAIT_OBJECT_0 ? 0 : -1) 25 | #define pthread_mutex_unlock(mp) (ReleaseMutex(*mp) == 0 ? -1 : 0) 26 | 27 | typedef CONDITION_VARIABLE pthread_cond_t; 28 | #define pthread_cond_init(cp, a) (InitializeConditionVariable(cp)) 29 | #define pthread_cond_destroy(cp) (0) 30 | #define pthread_cond_wait(cp, mp) (SleepConditionVariableCS(cp, *mp, INFINITE) ? 0 : 1) 31 | #define pthread_cond_signal(cp) (WakeConditionVariable(cp)) 32 | #define pthread_cond_broadcast(cp) (WakeAllConditionVariable(cp)) 33 | 34 | // #elif __GNUC__>3 || (__GNUC__==3 && __GNUC_MINOR__>3) 35 | #else 36 | 37 | #include 38 | #define THREAD_CALL 39 | #define THREAD_RETURN void* 40 | 41 | #endif 42 | 43 | #endif /* INCLUDE_COMPAT_PTHREAD_H_ */ 44 | -------------------------------------------------------------------------------- /examples/rtl_433_json_to_rtlwmbus.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | $ rtl_433 -R 104 -F json | rtl_433_json_to_rtlwmbus.py 4 | 5 | A script to convert rtl_433 wmbus json output to rtlwmbus output 6 | 7 | Copyright (C) 2019 Benjamin Larsson 8 | This program is free software; you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation; either version 2 of the License, or 11 | (at your option) any later version. 12 | """ 13 | 14 | import sys 15 | import json 16 | import time; 17 | 18 | def sanitize(text): 19 | return text.replace(" ", "_") 20 | 21 | def rtl_433_wmbus(): 22 | dup = {} 23 | seconds = 10 24 | while True: 25 | line = sys.stdin.readline() 26 | ts = int(time.time()) 27 | if not line: 28 | break 29 | 30 | try: 31 | event = json.loads(line) 32 | 33 | # Duplicate check + check if dictianary is initialized 34 | id = int(event['id']) 35 | if id in dup: 36 | #print("if %s D:%s T:%s" % (id, dup[id], ts)) 37 | if (dup[id] + seconds) < ts: 38 | duplicate = False 39 | dup[id] = ts; 40 | else: 41 | duplicate = True 42 | #print("Dup! %s" % (id)) 43 | else: 44 | #print("else %s" % (id)) 45 | dup[id] = ts; 46 | duplicate = False 47 | 48 | if duplicate != True: 49 | print("%s1;1;1;%s.000;54;46;%s;0x%s" % (event['mode'], event['time'], event['id'], event['data']) ) 50 | sys.stdout.flush() 51 | 52 | except KeyError: 53 | pass 54 | 55 | except ValueError: 56 | pass 57 | 58 | 59 | if __name__ == "__main__": 60 | dup_test = {} 61 | rtl_433_wmbus() 62 | -------------------------------------------------------------------------------- /src/compat_time.c: -------------------------------------------------------------------------------- 1 | // compat_time addresses following compatibility issue: 2 | // topic: high-resolution timestamps 3 | // issue: is not available on Windows systems 4 | // solution: provide a compatible version for Windows systems 5 | 6 | #include "compat_time.h" 7 | 8 | #ifdef _WIN32 9 | 10 | #include 11 | #include 12 | 13 | #if defined(_MSC_VER) || defined(_MSC_EXTENSIONS) 14 | #define DELTA_EPOCH_IN_MICROSECS 11644473600000000Ui64 15 | #else 16 | #define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL 17 | #endif 18 | 19 | int gettimeofday(struct timeval *tv, void *tz) 20 | { 21 | if (tz) 22 | return -1; // we don't support TZ 23 | 24 | FILETIME ft; 25 | unsigned __int64 t64; 26 | GetSystemTimeAsFileTime(&ft); 27 | t64 = (((unsigned __int64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; 28 | t64 /= 10; // convert to microseconds 29 | t64 -= DELTA_EPOCH_IN_MICROSECS; // convert file time to unix epoch 30 | tv->tv_sec = (long)(t64 / 1000000UL); 31 | tv->tv_usec = (long)(t64 % 1000000UL); 32 | 33 | return 0; 34 | } 35 | 36 | #endif // _WIN32 37 | 38 | int timeval_subtract(struct timeval *result, struct timeval *x, struct timeval *y) 39 | { 40 | // Perform the carry for the later subtraction by updating y 41 | if (x->tv_usec < y->tv_usec) { 42 | int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1; 43 | y->tv_usec -= 1000000 * nsec; 44 | y->tv_sec += nsec; 45 | } 46 | if (x->tv_usec - y->tv_usec > 1000000) { 47 | int nsec = (x->tv_usec - y->tv_usec) / 1000000; 48 | y->tv_usec += 1000000 * nsec; 49 | y->tv_sec -= nsec; 50 | } 51 | 52 | // Compute the time difference, tv_usec is certainly positive 53 | result->tv_sec = x->tv_sec - y->tv_sec; 54 | result->tv_usec = x->tv_usec - y->tv_usec; 55 | 56 | // Return 1 if result is negative 57 | return x->tv_sec < y->tv_sec; 58 | } 59 | -------------------------------------------------------------------------------- /include/jsmn.h: -------------------------------------------------------------------------------- 1 | #ifndef __JSMN_H_ 2 | #define __JSMN_H_ 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | /** 11 | * JSON type identifier. Basic types are: 12 | * o Object 13 | * o Array 14 | * o String 15 | * o Other primitive: number, boolean (true/false) or null 16 | */ 17 | typedef enum { 18 | JSMN_UNDEFINED = 0, 19 | JSMN_OBJECT = 1, 20 | JSMN_ARRAY = 2, 21 | JSMN_STRING = 3, 22 | JSMN_PRIMITIVE = 4 23 | } jsmntype_t; 24 | 25 | enum jsmnerr { 26 | /* Not enough tokens were provided */ 27 | JSMN_ERROR_NOMEM = -1, 28 | /* Invalid character inside JSON string */ 29 | JSMN_ERROR_INVAL = -2, 30 | /* The string is not a full JSON packet, more bytes expected */ 31 | JSMN_ERROR_PART = -3 32 | }; 33 | 34 | /** 35 | * JSON token description. 36 | * type type (object, array, string etc.) 37 | * start start position in JSON data string 38 | * end end position in JSON data string 39 | */ 40 | typedef struct { 41 | jsmntype_t type; 42 | int start; 43 | int end; 44 | int size; 45 | #ifdef JSMN_PARENT_LINKS 46 | int parent; 47 | #endif 48 | } jsmntok_t; 49 | 50 | /** 51 | * JSON parser. Contains an array of token blocks available. Also stores 52 | * the string being parsed now and current position in that string 53 | */ 54 | typedef struct { 55 | unsigned int pos; /* offset in the JSON string */ 56 | unsigned int toknext; /* next token to allocate */ 57 | int toksuper; /* superior token node, e.g parent object or array */ 58 | } jsmn_parser; 59 | 60 | /** 61 | * Create JSON parser over an array of tokens 62 | */ 63 | void jsmn_init(jsmn_parser *parser); 64 | 65 | /** 66 | * Run JSON parser. It parses a JSON data string into and array of tokens, each describing 67 | * a single JSON object. 68 | */ 69 | int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, 70 | jsmntok_t *tokens, unsigned int num_tokens); 71 | 72 | #ifdef __cplusplus 73 | } 74 | #endif 75 | 76 | #endif /* __JSMN_H_ */ 77 | -------------------------------------------------------------------------------- /cmake/Modules/FindLibUSB.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find LibUSB-1.0 2 | # Once done this will define 3 | # 4 | # LibUSB_FOUND - System has libusb 5 | # LibUSB_INCLUDE_DIRS - The libusb include directories 6 | # LibUSB_LIBRARIES - The libraries needed to use libusb 7 | # LibUSB_DEFINITIONS - Compiler switches required for using libusb 8 | # LibUSB_VERSION - the libusb version 9 | # 10 | 11 | find_package(PkgConfig) 12 | pkg_check_modules(PC_LibUSB QUIET libusb-1.0) 13 | set(LibUSB_DEFINITIONS ${PC_LibUSB_CFLAGS_OTHER}) 14 | 15 | find_path(LibUSB_INCLUDE_DIR NAMES libusb.h 16 | HINTS ${PC_LibUSB_INCLUDE_DIRS} 17 | PATH_SUFFIXES libusb-1.0 18 | PATHS 19 | /usr/include 20 | /usr/local/include ) 21 | 22 | #standard library name for libusb-1.0 23 | set(libusb1_library_names usb-1.0) 24 | 25 | #libusb-1.0 compatible library on freebsd 26 | if((CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") OR (CMAKE_SYSTEM_NAME STREQUAL "kFreeBSD")) 27 | list(APPEND libusb1_library_names usb) 28 | endif() 29 | 30 | #libusb-1.0 name on Windows (from PothosSDR distribution) 31 | if(CMAKE_SYSTEM_NAME STREQUAL "Windows") 32 | list(APPEND libusb1_library_names libusb-1.0) 33 | endif() 34 | 35 | find_library(LibUSB_LIBRARY 36 | NAMES ${libusb1_library_names} 37 | HINTS ${PC_LibUSB_LIBRARY_DIRS} 38 | PATHS 39 | /usr/lib 40 | /usr/local/lib ) 41 | 42 | set(LibUSB_VERSION ${PC_LibUSB_VERSION}) 43 | 44 | include(FindPackageHandleStandardArgs) 45 | # handle the QUIETLY and REQUIRED arguments and set LibUSB_FOUND to TRUE 46 | # if all listed variables are TRUE 47 | find_package_handle_standard_args(LibUSB 48 | REQUIRED_VARS LibUSB_LIBRARY LibUSB_INCLUDE_DIR 49 | VERSION_VAR LibUSB_VERSION) 50 | 51 | mark_as_advanced(LibUSB_LIBRARY LibUSB_INCLUDE_DIR LibUSB_VERSION) 52 | 53 | set(LibUSB_LIBRARIES ${LibUSB_LIBRARY} ) 54 | set(LibUSB_INCLUDE_DIRS ${LibUSB_INCLUDE_DIR} ) 55 | -------------------------------------------------------------------------------- /examples/rtl_433_collectd_pipe.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Collectd monitoring probe (i.e. no plugin) for rtl_433.""" 4 | 5 | # Needs Python collectd Network plugin, s.a. https://github.com/appliedsec/collectd 6 | # pip install collectd 7 | # -or- 8 | # curl -O https://github.com/appliedsec/collectd/raw/master/collectd.py 9 | 10 | import time 11 | import socket 12 | import fileinput 13 | import json 14 | import collectd 15 | 16 | 17 | def send_stats(when, stats, sender, to): 18 | for (plugin_type, plugin_inst), values in stats.items(): 19 | if not values: 20 | continue 21 | collectd.PLUGIN_TYPE = plugin_type 22 | for message in collectd.messages(values, when, sender, plugin_inst): 23 | collectd.sock.sendto(message, to) 24 | 25 | 26 | def sanitize(text): 27 | return text.replace(" ", "_") 28 | 29 | 30 | def rtl_433_probe(): 31 | hostname = socket.getfqdn() 32 | interval = 60.0 # seconds 33 | 34 | collectd.SEND_INTERVAL = interval 35 | collectd.PLUGIN_NAME = 'rtlsdr' 36 | 37 | collectd_host = "localhost" 38 | collectd_port = 25826 39 | 40 | for line in fileinput.input(): 41 | try: 42 | data = json.loads(line) 43 | 44 | when = int(time.time()) 45 | label = sanitize(data["model"]) 46 | if "channel" in data: 47 | label += ".CH" + str(data["channel"]) 48 | attributes = {} 49 | temperatures = {} 50 | 51 | attributes["battery"] = data["battery_ok"] 52 | 53 | attributes["humidity"] = data["humidity"] 54 | 55 | temperatures["sensor"] = data["temperature_C"] 56 | 57 | stats = {('gauge', label): attributes, 58 | ('temperature', label): temperatures} 59 | 60 | send_stats(when, stats, hostname, (collectd_host, collectd_port)) 61 | 62 | except ValueError: 63 | pass 64 | 65 | 66 | if __name__ == "__main__": 67 | rtl_433_probe() 68 | -------------------------------------------------------------------------------- /include/r_private.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | Definition of r_private state structure. 3 | */ 4 | 5 | #ifndef INCLUDE_R_PRIVATE_H_ 6 | #define INCLUDE_R_PRIVATE_H_ 7 | 8 | #include 9 | #include 10 | #include "list.h" 11 | #include "baseband.h" 12 | #include "pulse_detect.h" 13 | #include "fileformat.h" 14 | #include "samp_grab.h" 15 | #include "am_analyze.h" 16 | #include "rtl_433.h" 17 | #include "compat_time.h" 18 | 19 | struct dm_state { 20 | float auto_level; 21 | float squelch_offset; 22 | float level_limit; 23 | float noise_level; 24 | float min_level_auto; 25 | float min_level; 26 | float min_snr; 27 | float low_pass; 28 | int use_mag_est; 29 | int detect_verbosity; 30 | 31 | int16_t am_buf[MAXIMAL_BUF_LENGTH]; // AM demodulated signal (for OOK decoding) 32 | union { 33 | // These buffers aren't used at the same time, so let's use a union to save some memory 34 | int16_t fm[MAXIMAL_BUF_LENGTH]; // FM demodulated signal (for FSK decoding) 35 | uint16_t temp[MAXIMAL_BUF_LENGTH]; // Temporary buffer (to be optimized out..) 36 | } buf; 37 | uint8_t u8_buf[MAXIMAL_BUF_LENGTH]; // format conversion buffer 38 | float f32_buf[MAXIMAL_BUF_LENGTH]; // format conversion buffer 39 | int sample_size; // CU8: 2, CS16: 4 40 | pulse_detect_t *pulse_detect; 41 | filter_state_t lowpass_filter_state; 42 | demodfm_state_t demod_FM_state; 43 | int enable_FM_demod; 44 | unsigned fsk_pulse_detect_mode; 45 | unsigned frequency; 46 | samp_grab_t *samp_grab; 47 | am_analyze_t *am_analyze; 48 | int analyze_pulses; 49 | file_info_t load_info; 50 | list_t dumper; 51 | 52 | /* Protocol states */ 53 | list_t r_devs; 54 | 55 | pulse_data_t pulse_data; 56 | pulse_data_t fsk_pulse_data; 57 | unsigned frame_event_count; 58 | unsigned frame_start_ago; 59 | unsigned frame_end_ago; 60 | struct timeval now; 61 | float sample_file_pos; 62 | }; 63 | 64 | #endif /* INCLUDE_R_PRIVATE_H_ */ 65 | -------------------------------------------------------------------------------- /src/devices/silvercrest.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Silvercrest remote decoder. 3 | 4 | Copyright (C) 2018 Benjamin Larsson 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | #include "decoder.h" 13 | 14 | /** 15 | Silvercrest remote decoder. 16 | 17 | @todo Documentation needed. 18 | */ 19 | static int silvercrest_callback(r_device *decoder, bitbuffer_t *bitbuffer) 20 | { 21 | uint8_t const cmd_lu_tab[16] = {2, 3, 0, 1, 4, 5, 7, 6, 0xC, 0xD, 0xF, 0xE, 8, 9, 0xB, 0xA}; 22 | 23 | uint8_t *b; // bits of a row 24 | uint8_t cmd; 25 | data_t *data; 26 | 27 | if (bitbuffer->bits_per_row[1] != 33) 28 | return DECODE_ABORT_LENGTH; 29 | 30 | /* select second row, first might be bad */ 31 | b = bitbuffer->bb[1]; 32 | if ((b[0] == 0x7c) && (b[1] == 0x26)) { 33 | cmd = b[2] & 0xF; 34 | // Validate button 35 | if ((b[3] & 0xF) != cmd_lu_tab[cmd]) 36 | return DECODE_ABORT_EARLY; 37 | 38 | /* clang-format off */ 39 | data = data_make( 40 | "model", "", DATA_STRING, "Silvercrest-Remote", 41 | "button", "", DATA_INT, cmd, 42 | NULL); 43 | /* clang-format on */ 44 | 45 | decoder_output_data(decoder, data); 46 | 47 | return 1; 48 | } 49 | return DECODE_ABORT_EARLY; 50 | } 51 | 52 | static char *output_fields[] = { 53 | "model", 54 | "button", 55 | NULL, 56 | }; 57 | 58 | r_device silvercrest = { 59 | .name = "Silvercrest Remote Control", 60 | .modulation = OOK_PULSE_PWM, 61 | .short_width = 264, 62 | .long_width = 744, 63 | .reset_limit = 12000, 64 | .gap_limit = 5000, 65 | .decode_fn = &silvercrest_callback, 66 | .fields = output_fields, 67 | }; 68 | -------------------------------------------------------------------------------- /conf/ContinentalRemote.conf: -------------------------------------------------------------------------------- 1 | # Decoder for Continental / Nissan S180144020 key remote 2 | # FCC ID: KR5S180144014 3 | # 4 | # Tested with 2x US 2013 Nissan Altima key fobs 5 | # 6 | # Operates on 433.92MHz with FSK at +/-32kHz. With the default low-pass 7 | # filter of 10% of the sample rate, this means that a sample rate strictly 8 | # greater than 320ksps must be used. For a RTL-SDR, that means passing 9 | # "-s 1.024e6". 10 | # 11 | # The protocol was reverse engineered, so it may not be 100% complete. There 12 | # are 3 possible packet types: 13 | # - sync: 128 bits of all 0 14 | # - code: contains the rolling code, always sent twice for redundancy 15 | # - time: contains the amount of time that the button was held 16 | # 17 | # Every button press generates a sequence of sync, code, sync, code, time... 18 | # packets. There is always a 100ms gap between packets. Time packets will 19 | # be transmitted every 100ms until the button is released. 20 | # 21 | # More than one button can be held as well, which will set more than one bit 22 | # in the 'button' field. 23 | 24 | decoder { 25 | name=Continental-Remote-code, 26 | modulation=FSK_MC_ZEROBIT, 27 | short=122, 28 | reset=1000, 29 | preamble=ffff, 30 | bits>=104, 31 | 32 | get=message:@0:{8}:[0x2a:HOLD_TIME 0x2c:REL_TIME 0x26:HOLD_CODE 0x24:REL_CODE], 33 | get=src:@8:{16}:%x, 34 | get=dest:@24:{16}:%x, 35 | get=button:@40:{8}:[0x00:NONE 0x01:ALARM 0x02:TRUNK 0x04:UNLOCK 0x08:LOCK 0x40:START], 36 | get=rv:@52:{2}, 37 | get=seq:@56:{8}, 38 | get=code:@64:{32}:%08x, 39 | get=xorsum:@96:{8}:%02x, 40 | } 41 | 42 | decoder { 43 | name=Continental-Remote-time, 44 | modulation=FSK_MC_ZEROBIT, 45 | short=122, 46 | reset=1000, 47 | preamble=ffff, 48 | bits>=64, 49 | bits<=103, 50 | 51 | get=message:@0:{8}:[0x2a:HOLD_TIME 0x2c:REL_TIME 0x26:HOLD_CODE 0x24:REL_CODE], 52 | get=src:@8:{16}:%x, 53 | get=dest:@24:{16}:%x, 54 | get=button:@40:{8}:[0x00:NONE 0x01:ALARM 0x02:TRUNK 0x04:UNLOCK 0x08:LOCK 0x40:START], 55 | get=clk_10.4kHz:@48:{16}:, 56 | } 57 | -------------------------------------------------------------------------------- /src/mongoose_msvcdbg.patch: -------------------------------------------------------------------------------- 1 | From e6f124779d1c1edd050a6fc42389fcfdbd03915a Mon Sep 17 00:00:00 2001 2 | From: Gisle Vanem 3 | Date: Mon, 9 Sep 2019 10:27:08 +0200 4 | Subject: [PATCH] Fixes in Mongoose for MSVC debug-mode (-MDd) (#1146) 5 | 6 | In debug-mode (_DEBUG is defined), 'strdup()' is already defined to '_strdmp_dbg()'. 7 | And 'free()' is defined to '_free_dbg()'. 8 | --- 9 | include/mongoose.h | 4 ++-- 10 | src/mongoose.c | 4 ++-- 11 | 2 files changed, 4 insertions(+), 4 deletions(-) 12 | 13 | diff --git a/include/mongoose.h b/include/mongoose.h 14 | index 57c12af..f2b8891 100644 15 | --- a/include/mongoose.h 16 | +++ b/include/mongoose.h 17 | @@ -227,7 +227,7 @@ typedef int bool; 18 | #include 19 | #endif 20 | 21 | -#if defined(_MSC_VER) && _MSC_VER >= 1800 22 | +#if defined(_MSC_VER) && (_MSC_VER >= 1800) && !defined(_DEBUG) 23 | #define strdup _strdup 24 | #endif 25 | 26 | @@ -3634,7 +3634,7 @@ struct mg_iface { 27 | 28 | struct mg_iface_vtable { 29 | void (*init)(struct mg_iface *iface); 30 | - void (*free)(struct mg_iface *iface); 31 | + void (*_free)(struct mg_iface *iface); 32 | void (*add_conn)(struct mg_connection *nc); 33 | void (*remove_conn)(struct mg_connection *nc); 34 | time_t (*poll)(struct mg_iface *iface, int timeout_ms); 35 | diff --git a/src/mongoose.c b/src/mongoose.c 36 | index 593f8c6..c68a627 100644 37 | --- a/src/mongoose.c 38 | +++ b/src/mongoose.c 39 | @@ -2577,7 +2577,7 @@ void mg_mgr_free(struct mg_mgr *m) { 40 | { 41 | int i; 42 | for (i = 0; i < m->num_ifaces; i++) { 43 | - m->ifaces[i]->vtable->free(m->ifaces[i]); 44 | + m->ifaces[i]->vtable->_free(m->ifaces[i]); 45 | MG_FREE(m->ifaces[i]); 46 | } 47 | MG_FREE(m->ifaces); 48 | @@ -4494,7 +4494,7 @@ static int mg_socks_if_create_conn(struct mg_connection *c) { 49 | } 50 | 51 | static void mg_socks_if_destroy_conn(struct mg_connection *c) { 52 | - c->iface->vtable->free(c->iface); 53 | + c->iface->vtable->_free(c->iface); 54 | MG_FREE(c->iface); 55 | c->iface = NULL; 56 | LOG(LL_DEBUG, ("%p", c)); 57 | 58 | -------------------------------------------------------------------------------- /src/devices/elro_db286a.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Generic doorbell implementation for Elro DB286A devices. 3 | 4 | Copyright (C) 2016 Fabian Zaremba 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | */ 12 | /** 13 | Generic doorbell implementation for Elro DB286A devices. 14 | 15 | Note that each device seems to have two codes, which alternate 16 | for every other button press. 17 | 18 | short is 456 us pulse, 1540 us gap 19 | long is 1448 us pulse, 544 us gap 20 | packet gap is 7016 us 21 | 22 | Example code: 37f62a6c80 23 | */ 24 | 25 | #include "decoder.h" 26 | 27 | static int elro_db286a_callback(r_device *decoder, bitbuffer_t *bitbuffer) 28 | { 29 | // 33 bits expected, 5 minimum packet repetitions (14 expected) 30 | int row = bitbuffer_find_repeated_row(bitbuffer, 5, 33); 31 | 32 | if (row < 0 || bitbuffer->bits_per_row[row] != 33) 33 | return DECODE_ABORT_LENGTH; 34 | 35 | uint8_t *b = bitbuffer->bb[row]; 36 | 37 | // 32 bits, trailing bit is dropped 38 | char id_str[4 * 2 + 1]; 39 | sprintf(id_str, "%02x%02x%02x%02x", b[0], b[1], b[2], b[3]); 40 | 41 | /* clang-format off */ 42 | data_t *data = data_make( 43 | "model", "", DATA_STRING, "Elro-DB286A", 44 | "id", "ID", DATA_STRING, id_str, 45 | NULL); 46 | /* clang-format on */ 47 | 48 | decoder_output_data(decoder, data); 49 | return 1; 50 | } 51 | 52 | static char *output_fields[] = { 53 | "model", 54 | "id", 55 | NULL, 56 | }; 57 | 58 | r_device elro_db286a = { 59 | .name = "Elro DB286A Doorbell", 60 | .modulation = OOK_PULSE_PWM, 61 | .short_width = 456, 62 | .long_width = 1448, 63 | .gap_limit = 2000, 64 | .reset_limit = 8000, 65 | .decode_fn = &elro_db286a_callback, 66 | .disabled = 1, 67 | .fields = output_fields, 68 | }; 69 | -------------------------------------------------------------------------------- /examples/rtl_433_statsd_relay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Statsd monitoring relay for rtl_433.""" 4 | 5 | # Needs Python statsd Network plugin, s.a. https://github.com/jsocol/pystatsd 6 | # pip install pystatsd 7 | # -or- 8 | # curl -o statsd.py https://github.com/jsocol/pystatsd/raw/v3.2/statsd/client.py 9 | # Start rtl_433 (rtl_433 -F syslog::1433), then this script 10 | 11 | from __future__ import print_function 12 | 13 | import socket 14 | import json 15 | from statsd import StatsClient 16 | 17 | UDP_IP = "127.0.0.1" 18 | UDP_PORT = 1433 19 | STATSD_HOST = "127.0.0.1" 20 | STATSD_PORT = 8125 21 | STATSD_PREFIX = "rtlsdr" 22 | 23 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 24 | sock.bind((UDP_IP, UDP_PORT)) 25 | 26 | 27 | def sanitize(text): 28 | return text.replace(" ", "_") 29 | 30 | 31 | def parse_syslog(line): 32 | """Try to extract the payload from a syslog line.""" 33 | line = line.decode("ascii") # also UTF-8 if BOM 34 | if line.startswith("<"): 35 | # fields should be "VER", timestamp, hostname, command, pid, mid, sdata, payload 36 | fields = line.split(None, 7) 37 | line = fields[-1] 38 | return line 39 | 40 | 41 | def rtl_433_probe(): 42 | statsd = StatsClient(host=STATSD_HOST, 43 | port=STATSD_PORT, 44 | prefix=STATSD_PREFIX) 45 | 46 | while True: 47 | line, addr = sock.recvfrom(1024) 48 | 49 | try: 50 | line = parse_syslog(line) 51 | data = json.loads(line) 52 | 53 | label = sanitize(data["model"]) 54 | if "channel" in data: 55 | label += ".CH" + str(data["channel"]) 56 | 57 | if "battery_ok" in data: 58 | statsd.gauge(label + '.battery', data["battery_ok"]) 59 | 60 | if "humidity" in data: 61 | statsd.gauge(label + '.humidity', data["humidity"]) 62 | 63 | statsd.gauge(label + '.temperature', data["temperature_C"]) 64 | 65 | except KeyError: 66 | pass 67 | 68 | except ValueError: 69 | pass 70 | 71 | 72 | if __name__ == "__main__": 73 | rtl_433_probe() 74 | -------------------------------------------------------------------------------- /docs/LINKS.md: -------------------------------------------------------------------------------- 1 | # Links to tools and related projects 2 | 3 | ## SDR Inputs/Drivers 4 | 5 | - [RTL-SDR](https://github.com/osmocom/rtl-sdr/) 6 | - [SoapySDR](https://github.com/pothosware/SoapySDR/) 7 | 8 | ## Analysis 9 | 10 | - [SigRok](https://sigrok.org/) [PulseView](https://sigrok.org/wiki/PulseView) 11 | - [Audacity](https://www.audacityteam.org/) 12 | - [iqSpectrogram](http://triq.org/iqs) to visualize sample files 13 | - [BitBench](http://triq.net/bitbench) to analyze data formats 14 | 15 | # Related projects 16 | 17 | - [ShinySDR](https://shinysdr.switchb.org/) 18 | Web remote-controllable SDR receiver application supporting multiple simultaneous hardware devices and demodulators, including rtl_433 and other decoding tools. 19 | 20 | - [HASS addon to convert rtl433 output to mqtt](https://github.com/james-fry/hassio-addons/blob/master/rtl4332mqtt/rtl2mqtt.sh) 21 | 22 | - [rtl_fl2k_433](https://github.com/winterrace2/rtl_fl2k_433) 23 | an RX/TX prototyping tool. Aims to be a comfortable, GUI-based bridge between RTL-SDR dongles on RX side and cheap FL2K dongles on TX side. Currently, the GUI is available for Win64 only. 24 | 25 | - [rtl_433 with Snap7](https://github.com/merbanan/rtl_433/issues/950) 26 | to inject weather data to industrial control system (PLC - Siemens S7-300 or compatible VIPA) coming from Weather station WH1080. 27 | 28 | - [Domoticz](https://www.domoticz.com/) 29 | rtl_433 is usable from domoticz with a quite good integration: Domoticz launch rtl_433 with no data detection (relaunch rtl_433 if so) and process csv output format. All command line arguments are usable. 30 | 31 | - [WeeWx](http://weewx.com/) 32 | the weewx-sdr driver gets data from rtl_433 and feeds it into weewx. from there the data can be combined with data from other sources, displayed using any of the many weewx skins, and/or uploaded to many different web services. the first weewx-sdr release was in 2016. 33 | S.a. https://github.com/matthewwall/weewx-sdr https://github.com/weewx/weewx/wiki#skins https://github.com/weewx/weewx/wiki#uploaders 34 | 35 | - [rtl_snr](https://github.com/hdtodd/rtl_snr): 36 | snr is a pair of equivalent C and Python programs that catalog and analyze signal-to-noise ratios from devices seen by RTL_SDR dongles and logged in JSON format by rtl_433. 37 | -------------------------------------------------------------------------------- /src/devices/quhwa.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Quhwa HS1527. 3 | 4 | Copyright (C) 2016 Ask Jakobsen 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | /** 12 | Quhwa HS1527. 13 | 14 | Tested devices: 15 | QH-C-CE-3V (which should be compatible with QH-832AC), 16 | also sold as "1 by One" wireless doorbell 17 | */ 18 | 19 | #include "decoder.h" 20 | 21 | static int quhwa_callback(r_device *decoder, bitbuffer_t *bitbuffer) 22 | { 23 | int r = bitbuffer_find_repeated_row(bitbuffer, 5, 18); 24 | if (r < 0) 25 | return DECODE_ABORT_EARLY; 26 | 27 | uint8_t *b = bitbuffer->bb[r]; 28 | 29 | // No need to decode/extract values for simple test 30 | if (!b[0] && !b[1] && !b[2]) { 31 | decoder_log(decoder, 2, __func__, "DECODE_FAIL_SANITY data all 0x00"); 32 | return DECODE_FAIL_SANITY; 33 | } 34 | 35 | b[0] = ~b[0]; 36 | b[1] = ~b[1]; 37 | b[2] = ~b[2]; 38 | 39 | if (bitbuffer->bits_per_row[r] != 18 40 | || (b[1] & 0x03) != 0x03 41 | || (b[2] & 0xC0) != 0xC0) 42 | return DECODE_ABORT_LENGTH; 43 | 44 | uint32_t id = (b[0] << 8) | b[1]; 45 | 46 | /* clang-format off */ 47 | data_t *data = data_make( 48 | "model", "", DATA_STRING, "Quhwa-Doorbell", 49 | "id", "ID", DATA_INT, id, 50 | NULL); 51 | /* clang-format on */ 52 | 53 | decoder_output_data(decoder, data); 54 | 55 | return 1; 56 | } 57 | 58 | static char *output_fields[] = { 59 | "model", 60 | "id", 61 | NULL, 62 | }; 63 | 64 | r_device quhwa = { 65 | .name = "Quhwa", 66 | .modulation = OOK_PULSE_PWM, 67 | .short_width = 360, // Pulse: Short 360µs, Long 1070µs 68 | .long_width = 1070, // Gaps: Short 360µs, Long 1070µs 69 | .reset_limit = 6600, // Intermessage Gap 6500µs 70 | .gap_limit = 1200, // Long Gap 1120µs 71 | .sync_width = 0, // No sync bit used 72 | .tolerance = 80, // us 73 | .decode_fn = &quhwa_callback, 74 | .fields = output_fields, 75 | }; 76 | -------------------------------------------------------------------------------- /tests/data-test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * A general structure for extracting hierarchical data from the devices; 3 | * typically key-value pairs, but allows for more rich data as well 4 | * 5 | * Copyright (C) 2015 by Erkki Seppälä 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | */ 20 | 21 | #include 22 | 23 | #include "data.h" 24 | #include "output_file.h" 25 | 26 | int main() 27 | { 28 | data_t *data = data_make("label" , "", DATA_STRING, "1.2.3", 29 | "house_code" , "House Code", DATA_INT, 42, 30 | "temp" , "Temperature", DATA_DOUBLE, 99.9, 31 | "array" , "Array", DATA_ARRAY, data_array(2, DATA_STRING, (char*[2]){"hello", "world"}), 32 | "array2" , "Array 2", DATA_ARRAY, data_array(2, DATA_INT, (int[2]){4, 2}), 33 | "array3" , "Array 3", DATA_ARRAY, data_array(2, DATA_ARRAY, (data_array_t*[2]){ 34 | data_array(2, DATA_INT, (int[2]){4, 2}), 35 | data_array(2, DATA_INT, (int[2]){5, 5}) }), 36 | "data" , "Data", DATA_DATA, data_make("Hello", "hello", DATA_STRING, "world", NULL), 37 | NULL); 38 | const char *fields[] = { "label", "house_code", "temp", "array", "array2", "array3", "data", "house_code" }; 39 | 40 | void *json_output = data_output_json_create(stdout); 41 | void *kv_output = data_output_kv_create(stdout); 42 | void *csv_output = data_output_csv_create(stdout); 43 | data_output_start(csv_output, fields, sizeof fields / sizeof *fields); 44 | 45 | data_output_print(json_output, data); fprintf(stdout, "\n"); 46 | data_output_print(kv_output, data); 47 | data_output_print(csv_output, data); 48 | 49 | data_output_free(json_output); 50 | data_output_free(kv_output); 51 | data_output_free(csv_output); 52 | 53 | data_free(data); 54 | } 55 | -------------------------------------------------------------------------------- /src/devices/intertechno.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Intertechno remotes. 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | */ 10 | /** 11 | Intertechno remotes. 12 | 13 | Intertechno remote labeled ITT-1500 that came with 3x ITR-1500 remote outlets. The set is labeled IT-1500. 14 | The PPM consists of a 220µs high followed by 340µs or 1400µs of gap. 15 | 16 | There is another type of remotes that have an ID prefix of 0x56 and slightly shorter timing. 17 | 18 | */ 19 | 20 | #include "decoder.h" 21 | 22 | static int intertechno_callback(r_device *decoder, bitbuffer_t *bitbuffer) 23 | { 24 | data_t *data; 25 | bitrow_t *bb = bitbuffer->bb; 26 | uint8_t *b = bitbuffer->bb[1]; 27 | char id_str[11]; 28 | int slave; 29 | int master; 30 | int command; 31 | 32 | if (bb[0][0] != 0 || (bb[1][0] != 0x56 && bb[1][0] != 0x69)) 33 | return DECODE_ABORT_EARLY; 34 | 35 | sprintf(id_str, "%02x%02x%02x%02x%02x", b[0], b[1], b[2], b[3], b[4]); 36 | slave = b[7] & 0x0f; 37 | master = (b[7] & 0xf0) >> 4; 38 | command = b[6] & 0x07; 39 | 40 | /* clang-format off */ 41 | data = data_make( 42 | "model", "", DATA_STRING, "Intertechno-Remote", 43 | "id", "", DATA_STRING, id_str, 44 | "slave", "", DATA_INT, slave, 45 | "master", "", DATA_INT, master, 46 | "command", "", DATA_INT, command, 47 | NULL); 48 | /* clang-format on */ 49 | 50 | decoder_output_data(decoder, data); 51 | return 1; 52 | } 53 | 54 | static char *output_fields[] = { 55 | "model", 56 | "type", 57 | "id", 58 | "slave", 59 | "master", 60 | "command", 61 | NULL, 62 | }; 63 | 64 | r_device intertechno = { 65 | .name = "Intertechno 433", 66 | .modulation = OOK_PULSE_PPM, 67 | .short_width = 330, 68 | .long_width = 1400, 69 | .gap_limit = 1700, 70 | .reset_limit = 10000, 71 | .decode_fn = &intertechno_callback, 72 | .disabled = 1, 73 | .fields = output_fields, 74 | }; 75 | -------------------------------------------------------------------------------- /src/list.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Generic list. 3 | 4 | Copyright (C) 2018 Christian Zuckschwerdt 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | #include "list.h" 13 | #include "fatal.h" 14 | #include 15 | #include 16 | 17 | void list_ensure_size(list_t *list, size_t min_size) 18 | { 19 | if (!list->elems || list->size < min_size) { 20 | list->elems = realloc(list->elems, min_size * sizeof(*list->elems)); 21 | if (!list->elems) { 22 | FATAL_REALLOC("list_ensure_size()"); 23 | } 24 | list->size = min_size; 25 | 26 | list->elems[list->len] = NULL; // ensure a terminating NULL 27 | } 28 | } 29 | 30 | void list_push(list_t *list, void *p) 31 | { 32 | if (list->len + 1 >= list->size) // account for terminating NULL 33 | list_ensure_size(list, list->size < 8 ? 8 : list->size + list->size / 2); 34 | 35 | list->elems[list->len++] = p; 36 | 37 | list->elems[list->len] = NULL; // ensure a terminating NULL 38 | } 39 | 40 | void list_push_all(list_t *list, void **p) 41 | { 42 | for (void **iter = p; iter && *iter; ++iter) 43 | list_push(list, *iter); 44 | } 45 | 46 | void list_remove(list_t *list, size_t idx, list_elem_free_fn elem_free) 47 | { 48 | if (idx >= list->len) { 49 | return; // report error? 50 | } 51 | if (elem_free) { 52 | elem_free(list->elems[idx]); 53 | } 54 | for (size_t i = idx; i < list->len; ++i) { // list might contain NULLs 55 | list->elems[i] = list->elems[i + 1]; // ensures a terminating NULL 56 | } 57 | list->len--; 58 | } 59 | 60 | void list_clear(list_t *list, list_elem_free_fn elem_free) 61 | { 62 | if (elem_free) { 63 | for (size_t i = 0; i < list->len; ++i) { // list might contain NULLs 64 | elem_free(list->elems[i]); 65 | } 66 | } 67 | list->len = 0; 68 | if (list->elems) { 69 | list->elems[0] = NULL; // ensure a terminating NULL 70 | } 71 | } 72 | 73 | void list_free_elems(list_t *list, list_elem_free_fn elem_free) 74 | { 75 | list_clear(list, elem_free); 76 | free(list->elems); 77 | list->elems = NULL; 78 | list->size = 0; 79 | } 80 | -------------------------------------------------------------------------------- /src/compat_alarm.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Emulation of the Posix function `alarm()` 3 | * using `CreateTimerQueueTimer()`. 4 | * 5 | * \ref https://docs.microsoft.com/en-us/windows/win32/api/threadpoollegacyapiset/nf-threadpoollegacyapiset-createtimerqueuetimer 6 | */ 7 | #include 8 | #include "compat_alarm.h" 9 | 10 | #ifdef HAVE_win_alarm /* rest of file */ 11 | 12 | static HANDLE alarm_hnd = INVALID_HANDLE_VALUE; 13 | static int alarm_countdown; 14 | 15 | /** 16 | * The timer-callback that performs the countdown. 17 | */ 18 | void CALLBACK alarm_handler(PVOID param, BOOLEAN timer_fired) 19 | { 20 | if (alarm_countdown > 0) { 21 | alarm_countdown--; 22 | if (alarm_countdown == 0) 23 | raise(SIGALRM); 24 | } 25 | (void) timer_fired; 26 | (void) param; 27 | } 28 | 29 | /** 30 | * Destroy the timer.
31 | * Called as an `atexit()` function. 32 | */ 33 | static void alarm_delete(void) 34 | { 35 | if (!alarm_hnd || alarm_hnd == INVALID_HANDLE_VALUE) 36 | return; 37 | signal(SIGALRM, SIG_IGN); 38 | DeleteTimerQueueTimer(NULL, alarm_hnd, NULL); 39 | alarm_hnd = INVALID_HANDLE_VALUE; 40 | } 41 | 42 | /** 43 | * Create a kernel32 timer once. 44 | */ 45 | static void alarm_create(void) 46 | { 47 | if (alarm_hnd && alarm_hnd != INVALID_HANDLE_VALUE) 48 | return; 49 | 50 | if (!CreateTimerQueueTimer(&alarm_hnd, NULL, alarm_handler, 51 | NULL, 52 | 1000, /* call alarm_handler() after 1 sec */ 53 | 1000, /* an do it periodically every seconds */ 54 | WT_EXECUTEDEFAULT | WT_EXECUTEINTIMERTHREAD)) { 55 | fprintf(stderr, "CreateTimerQueueTimer() failed %lu\n", GetLastError()); 56 | alarm_hnd = NULL; 57 | } 58 | else 59 | atexit(alarm_delete); 60 | } 61 | 62 | /** 63 | * Emulate an `alarm(sec)` function. 64 | * 65 | * @param[in] seconds the number of seconds to countdown before a `raise(SIGALRM)` is done.
66 | * if `seconds == 0` the `alarm_handler()` will do nothing. 67 | */ 68 | int win_alarm(unsigned seconds) 69 | { 70 | alarm_countdown = seconds; 71 | alarm_create(); 72 | return (0); 73 | } 74 | #else 75 | 76 | /* 77 | * Just so this compilation unit isn't empty. 78 | */ 79 | int win_alarm(unsigned seconds); 80 | int win_alarm(unsigned seconds) 81 | { 82 | (void) seconds; 83 | return (0); 84 | } 85 | #endif /* HAVE_win_alarm */ 86 | -------------------------------------------------------------------------------- /src/devices/generic_temperature_sensor.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Generic temperature sensor 1. 3 | 4 | Copyright (C) 2015 Alexandre Coffignal 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | /** 12 | Generic temperature sensor 1. 13 | 14 | 10 24 bits frames: 15 | 16 | IIIIIIII BBTTTTTT TTTTTTTT 17 | 18 | - I: 8 bit ID 19 | - B: 2 bit? Battery ? 20 | - T: 12 bit Temp 21 | */ 22 | 23 | #include "decoder.h" 24 | 25 | static int generic_temperature_sensor_callback(r_device *decoder, bitbuffer_t *bitbuffer) 26 | { 27 | data_t *data; 28 | uint8_t *b = bitbuffer->bb[1]; 29 | int i, device, battery, temp_raw; 30 | float temp_f; 31 | 32 | for (i = 1; i < 10; i++) { 33 | if (bitbuffer->bits_per_row[i] != 24) { 34 | /*10 24 bits frame*/ 35 | return DECODE_ABORT_LENGTH; 36 | } 37 | } 38 | 39 | // reduce false positives 40 | if ((b[0] == 0 && b[1] == 0 && b[2] == 0) 41 | || (b[0] == 0xff && b[1] == 0xff && b[2] == 0xff)) { 42 | return DECODE_ABORT_EARLY; 43 | } 44 | 45 | device = (b[0]); 46 | battery = (b[1] & 0xC0) >> 6; 47 | temp_raw = (int16_t)(((b[1] & 0x3f) << 10) | (b[2] << 2)); 48 | temp_f = (temp_raw >> 4) * 0.1f; 49 | 50 | /* clang-format off */ 51 | data = data_make( 52 | "model", "", DATA_STRING, "Generic-Temperature", 53 | "id", "Id", DATA_INT, device, 54 | "battery_ok", "Battery?", DATA_INT, battery, 55 | "temperature_C", "Temperature", DATA_FORMAT, "%.02f C", DATA_DOUBLE, temp_f, 56 | NULL); 57 | /* clang-format on */ 58 | 59 | decoder_output_data(decoder, data); 60 | return 1; 61 | } 62 | 63 | static char *output_fields[] = { 64 | "model", 65 | "id", 66 | "battery_ok", 67 | "temperature_C", 68 | NULL, 69 | }; 70 | 71 | r_device generic_temperature_sensor = { 72 | .name = "Generic temperature sensor 1", 73 | .modulation = OOK_PULSE_PPM, 74 | .short_width = 2000, 75 | .long_width = 4000, 76 | .gap_limit = 4800, 77 | .reset_limit = 10000, 78 | .decode_fn = &generic_temperature_sensor_callback, 79 | .fields = output_fields, 80 | }; 81 | -------------------------------------------------------------------------------- /src/devices/hondaremote.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Honda Car Key. 3 | 4 | Copyright (C) 2016 Adrian Stevenson 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | /** 13 | Honda Car Key. 14 | 15 | Identifies button event, but does not attempt to decrypt rolling code... 16 | Note that this is actually Manchester coded and should be changed. 17 | 18 | */ 19 | #include "decoder.h" 20 | 21 | static char const *command_code[] = {"boot", "unlock" , "lock",}; 22 | 23 | static char const *get_command_codes(const uint8_t *bytes) 24 | { 25 | unsigned char command = bytes[46] - 0xAA; 26 | if (command < (sizeof(command_code) / sizeof(command_code[0]))) { 27 | return command_code[command]; 28 | } else { 29 | return "unknown"; 30 | } 31 | } 32 | 33 | static int hondaremote_callback(r_device *decoder, bitbuffer_t *bitbuffer) 34 | { 35 | data_t *data; 36 | uint8_t *b; 37 | char const *code; 38 | uint16_t device_id; 39 | 40 | for (int row = 0; row < bitbuffer->num_rows; ++row) { 41 | b = bitbuffer->bb[row]; 42 | // Validate package 43 | if (((bitbuffer->bits_per_row[row] < 385) || (bitbuffer->bits_per_row[row] > 394)) || 44 | ((b[0] != 0xFF) || (b[38] != 0xFF))) 45 | continue; // DECODE_ABORT_LENGTH 46 | 47 | code = get_command_codes(b); 48 | device_id = b[44]<<8 | b[45]; 49 | 50 | /* clang-format off */ 51 | data = data_make( 52 | "model", "", DATA_STRING, "Honda-CarRemote", 53 | "id", "", DATA_INT, device_id, 54 | "code", "", DATA_STRING, code, 55 | NULL); 56 | /* clang-format on */ 57 | 58 | decoder_output_data(decoder, data); 59 | return 1; 60 | } 61 | return 0; 62 | } 63 | 64 | static char *output_fields[] = { 65 | "model", 66 | "id", 67 | "code", 68 | NULL, 69 | }; 70 | 71 | r_device hondaremote = { 72 | .name = "Honda Car Key", 73 | .modulation = FSK_PULSE_PWM, 74 | .short_width = 250, 75 | .long_width = 500, 76 | .reset_limit = 2000, 77 | .decode_fn = &hondaremote_callback, 78 | .disabled = 1, // no MIC, weak sanity checks 79 | .fields = output_fields, 80 | }; 81 | -------------------------------------------------------------------------------- /src/devices/akhan_100F14.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Akhan remote keyless entry system. 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | */ 9 | 10 | /** 11 | Akhan remote keyless entry system. 12 | 13 | This RKE system uses a HS1527 OTP encoder (http://sc-tech.cn/en/hs1527.pdf) 14 | Each message consists of a preamble, 20 bit id and 4 data bits. 15 | 16 | (code based on chuango.c and generic_remote.c) 17 | 18 | Note: simple 24 bit fixed ID protocol (x1527 style) and should be handled by the flex decoder. 19 | */ 20 | 21 | #include "decoder.h" 22 | 23 | static int akhan_rke_callback(r_device *decoder, bitbuffer_t *bitbuffer) 24 | { 25 | data_t *data; 26 | uint8_t *b; 27 | int id; 28 | int cmd; 29 | char *cmd_str; 30 | 31 | if (bitbuffer->bits_per_row[0] != 25) 32 | return DECODE_ABORT_LENGTH; 33 | b = bitbuffer->bb[0]; 34 | 35 | // invert bits, short pulse is 0, long pulse is 1 36 | b[0] = ~b[0]; 37 | b[1] = ~b[1]; 38 | b[2] = ~b[2]; 39 | 40 | id = (b[0] << 12) | (b[1] << 4) | (b[2] >> 4); 41 | cmd = b[2] & 0x0F; 42 | switch (cmd) { 43 | case 0x1: cmd_str = "0x1 (Lock)"; break; 44 | case 0x2: cmd_str = "0x2 (Unlock)"; break; 45 | case 0x4: cmd_str = "0x4 (Mute)"; break; 46 | case 0x8: cmd_str = "0x8 (Alarm)"; break; 47 | default: cmd_str = NULL; break; 48 | } 49 | 50 | if (!cmd_str) 51 | return DECODE_FAIL_SANITY; 52 | 53 | /* clang-format off */ 54 | data = data_make( 55 | "model", "", DATA_STRING, "Akhan-100F14", 56 | "id", "ID (20bit)", DATA_FORMAT, "0x%x", DATA_INT, id, 57 | "data", "Data (4bit)", DATA_STRING, cmd_str, 58 | NULL); 59 | /* clang-format on */ 60 | 61 | decoder_output_data(decoder, data); 62 | return 1; 63 | } 64 | 65 | static char *output_fields[] = { 66 | "model", 67 | "id", 68 | "data", 69 | NULL, 70 | }; 71 | 72 | r_device akhan_100F14 = { 73 | .name = "Akhan 100F14 remote keyless entry", 74 | .modulation = OOK_PULSE_PWM, 75 | .short_width = 316, 76 | .long_width = 1020, 77 | .reset_limit = 1800, 78 | .sync_width = 0, 79 | .tolerance = 80, // us 80 | .decode_fn = &akhan_rke_callback, 81 | .fields = output_fields, 82 | }; 83 | -------------------------------------------------------------------------------- /src/devices/blyss.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Generic remote Blyss DC5-UK-WH as sold by B&Q. 3 | 4 | Copyright (C) 2016 John Jore 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | /** 12 | Generic remote Blyss DC5-UK-WH as sold by B&Q. 13 | 14 | DC5-UK-WH pair with receivers, the codes used may be specific to a receiver - use with caution 15 | 16 | warmup pulse 5552 us, 2072 gap 17 | short is 512 us pulse, 1484 us gap 18 | long is 1508 us pulse, 488 us gap 19 | packet gap is 6964 us 20 | 21 | */ 22 | #include "decoder.h" 23 | 24 | static int blyss_callback(r_device *decoder, bitbuffer_t *bitbuffer) 25 | { 26 | data_t *data; 27 | uint8_t *b; 28 | char id_str[16]; 29 | 30 | for (int i = 0; i < bitbuffer->num_rows; ++i) { 31 | if (bitbuffer->bits_per_row[i] != 33) // last row is 32 32 | continue; // DECODE_ABORT_LENGTH 33 | 34 | b = bitbuffer->bb[i]; 35 | 36 | //This needs additional validation, but works on mine. Suspect each DC5-UK-WH uses different codes as the transmitter 37 | //is paired to the receivers to avoid being triggered by the neighbours transmitter ?!? 38 | // TODO: cleaner implementation with 2 preamble arrays 39 | if (((b[0] != 0xce) || (b[1] != 0x8e) || (b[2] != 0x2a) || (b[3] != 0x6c) || (b[4] != 0x80)) && 40 | ((b[0] != 0xe7) || (b[1] != 0x37) || (b[2] != 0x7a) || (b[3] != 0x2c) || (b[4] != 0x80))) 41 | continue; // DECODE_ABORT_EARLY 42 | 43 | sprintf(id_str, "%02x%02x%02x%02x", b[0], b[1], b[2], b[3]); 44 | 45 | /* clang-format off */ 46 | data = data_make( 47 | "model", "", DATA_STRING, "Blyss-DC5ukwh", 48 | "id", "", DATA_STRING, id_str, 49 | NULL); 50 | /* clang-format on */ 51 | 52 | decoder_output_data(decoder, data); 53 | return 1; 54 | } 55 | 56 | return DECODE_FAIL_SANITY; 57 | } 58 | 59 | static char *output_fields[] = { 60 | "model", 61 | "id", 62 | NULL, 63 | }; 64 | 65 | r_device blyss = { 66 | .name = "Blyss DC5-UK-WH", 67 | .modulation = OOK_PULSE_PWM, 68 | .short_width = 500, 69 | .long_width = 1500, 70 | .gap_limit = 2500, 71 | .reset_limit = 8000, 72 | .decode_fn = &blyss_callback, 73 | .fields = output_fields, 74 | }; 75 | -------------------------------------------------------------------------------- /examples/rtl_433_custom.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Custom data handling example for rtl_433.""" 4 | 5 | # Start rtl_433 (rtl_433 -F syslog::1433), then this script 6 | 7 | from __future__ import print_function 8 | 9 | import socket 10 | import json 11 | 12 | # You can run rtl_433 and this script on different machines, 13 | # start rtl_433 with `-F syslog:YOURTARGETIP:1433`, and change 14 | # to `UDP_IP = "0.0.0.0"` (listen to the whole network) below. 15 | UDP_IP = "127.0.0.1" 16 | UDP_PORT = 1433 17 | 18 | 19 | def parse_syslog(line): 20 | """Try to extract the payload from a syslog line.""" 21 | line = line.decode("ascii") # also UTF-8 if BOM 22 | if line.startswith("<"): 23 | # fields should be "VER", timestamp, hostname, command, pid, mid, sdata, payload 24 | fields = line.split(None, 7) 25 | line = fields[-1] 26 | return line 27 | 28 | 29 | def rtl_433_listen(): 30 | """Listen to all messages in a loop forever.""" 31 | # Open a UDP socket 32 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 33 | # Bind the UDP socket to a listening address 34 | sock.bind((UDP_IP, UDP_PORT)) 35 | 36 | # Loop forever 37 | while True: 38 | # Receive a message 39 | line, addr = sock.recvfrom(1024) 40 | 41 | try: 42 | # Parse the message format 43 | line = parse_syslog(line) 44 | # Decode the message as JSON 45 | data = json.loads(line) 46 | 47 | # 48 | # Change for your custom handling below, this is a simple example 49 | # 50 | label = data["model"] 51 | if "channel" in data: 52 | label += ".CH" + str(data["channel"]) 53 | elif "id" in data: 54 | label += ".ID" + str(data["id"]) 55 | 56 | # E.g. match `model` and `id` to a descriptive name. 57 | if data["model"] == "LaCrosse-TX" and data["id"] == 123: 58 | label = "Living Room" 59 | 60 | if "battery_ok" in data: 61 | if data["battery_ok"] == 0: 62 | print(label + ' Battery empty!') 63 | 64 | if "temperature_C" in data: 65 | print(label + ' Temperature ', data["temperature_C"]) 66 | 67 | if "humidity" in data: 68 | print(label + ' Humidity ', data["humidity"]) 69 | 70 | # Ignore unknown message data and continue 71 | except KeyError: 72 | pass 73 | 74 | except ValueError: 75 | pass 76 | 77 | 78 | if __name__ == "__main__": 79 | rtl_433_listen() 80 | -------------------------------------------------------------------------------- /src/compat_paths.c: -------------------------------------------------------------------------------- 1 | // compat_paths addresses following compatibility issue: 2 | // topic: default search paths for config file 3 | // issue: Linux and Windows use different common paths for config files 4 | // solution: provide specific default paths for each system 5 | 6 | #ifndef _WIN32 7 | // Linux variant 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "compat_paths.h" 15 | 16 | char **compat_get_default_conf_paths() 17 | { 18 | static char *paths[5] = { NULL }; 19 | static char buf[256] = ""; 20 | char *env_config_home = getenv("XDG_CONFIG_HOME"); 21 | if (!paths[0]) { 22 | paths[0] = "rtl_433.conf"; 23 | if (env_config_home && *env_config_home) 24 | snprintf(buf, sizeof(buf), "%s%s", env_config_home, "/rtl_433/rtl_433.conf"); 25 | else 26 | snprintf(buf, sizeof(buf), "%s%s", getenv("HOME"), "/.config/rtl_433/rtl_433.conf"); 27 | paths[1] = buf; 28 | paths[2] = "/usr/local/etc/rtl_433/rtl_433.conf"; 29 | paths[3] = "/etc/rtl_433/rtl_433.conf"; 30 | paths[4] = NULL; 31 | }; 32 | return paths; 33 | } 34 | 35 | #else 36 | // Windows variant 37 | 38 | #include 39 | #include 40 | #include 41 | 42 | #include "compat_paths.h" 43 | 44 | char **compat_get_default_conf_paths() 45 | { 46 | static char bufs[3][256]; 47 | static char *paths[4] = { NULL }; 48 | if (paths[0]) return paths; 49 | // Working directory, i.e. where the binary is located 50 | if (GetModuleFileName(NULL, bufs[0], sizeof(bufs[0]))) { 51 | char *last_backslash = strrchr(bufs[0], '\\'); 52 | if (last_backslash) 53 | *last_backslash = '\0'; 54 | strcat_s(bufs[0], sizeof(bufs[0]), "\\rtl_433.conf"); 55 | paths[0] = bufs[0]; 56 | } 57 | else { 58 | paths[0] = NULL; 59 | } 60 | // Local per user configuration files (e.g. Win7: C:\Users\myusername\AppData\Local\rtl_433\rtl_433.conf) 61 | if (SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, bufs[1]) == S_OK) { 62 | strcat_s(bufs[1], sizeof(bufs[1]), "\\rtl_433\\rtl_433.conf"); 63 | paths[1] = bufs[1]; 64 | } 65 | else { 66 | paths[1] = NULL; 67 | } 68 | // Per machine configuration data (e.g. Win7: C:\ProgramData\rtl_433\rtl_433.conf) 69 | if (SHGetFolderPath(NULL, CSIDL_COMMON_APPDATA, NULL, 0, bufs[2]) == S_OK) { 70 | strcat_s(bufs[2], sizeof(bufs[2]), "\\rtl_433\\rtl_433.conf"); 71 | paths[2] = bufs[2]; 72 | } 73 | else { 74 | paths[2] = NULL; 75 | } 76 | paths[3] = NULL; 77 | return paths; 78 | } 79 | #endif // _WIN32 / !_WIN32 80 | -------------------------------------------------------------------------------- /src/devices/fordremote.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Ford Car Key. 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | */ 10 | /** 11 | Ford Car Key. 12 | 13 | Identifies event, but does not attempt to decrypt rolling code... 14 | Note: this used to have a broken PWM decoding, but is now proper DMC. 15 | The output changed and the fields are very likely not as intended. 16 | 17 | [00] {1} 80 : 1 18 | [01] {9} 00 80 : 00000000 1 19 | [02] {1} 80 : 1 20 | [03] {78} 03 e0 01 e4 e0 90 52 97 39 60 21 | 22 | */ 23 | 24 | #include "decoder.h" 25 | 26 | static int fordremote_callback(r_device *decoder, bitbuffer_t *bitbuffer) 27 | { 28 | data_t *data; 29 | uint8_t *bytes; 30 | int found = 0; 31 | int device_id, code; 32 | 33 | // expect {1} {9} {1} preamble 34 | for (int i = 3; i < bitbuffer->num_rows; i++) { 35 | if (bitbuffer->bits_per_row[i] < 78) { 36 | continue; // DECODE_ABORT_LENGTH 37 | } 38 | 39 | // Validate preamble 40 | if (bitbuffer->bits_per_row[i - 3] != 1 || bitbuffer->bits_per_row[i - 1] != 1 41 | || bitbuffer->bits_per_row[i - 2] != 9 || bitbuffer->bb[i - 2][0] != 0) { 42 | continue; // DECODE_ABORT_EARLY 43 | } 44 | 45 | decoder_log_bitbuffer(decoder, 1, __func__, bitbuffer, ""); 46 | 47 | bytes = bitbuffer->bb[i]; 48 | device_id = (bytes[0] << 16) | (bytes[1] << 8) | bytes[2]; 49 | code = bytes[7]; 50 | 51 | /* clang-format off */ 52 | data = data_make( 53 | "model", "model", DATA_STRING, "Ford-CarRemote", 54 | "id", "device-id", DATA_INT, device_id, 55 | "code", "data", DATA_INT, code, 56 | NULL); 57 | decoder_output_data(decoder, data); 58 | /* clang-format on */ 59 | 60 | found++; 61 | } 62 | return found; 63 | } 64 | 65 | static char *output_fields[] = { 66 | "model", 67 | "id", 68 | "code", 69 | NULL, 70 | }; 71 | 72 | r_device fordremote = { 73 | .name = "Ford Car Key", 74 | .modulation = OOK_PULSE_DMC, 75 | .short_width = 250, // half-bit width is 250 us 76 | .long_width = 500, // bit width is 500 us 77 | .reset_limit = 4000, // sync gap is 3500 us, preamble gap is 38400 us, packet gap is 52000 us 78 | .tolerance = 50, 79 | .decode_fn = &fordremote_callback, 80 | .fields = output_fields, 81 | }; 82 | -------------------------------------------------------------------------------- /conf/fan-11t.conf: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # FAN-11T Remote Control of Harbor Breeze Fan 4 | # 5 | # KUJCE9103 FAN-11T FAN-53T 2AAZPFAN-53T 6 | # 7 | # written: Peter Shipley 8 | # 9 | # https://www.amazon.com/s?k=Fan-11T 10 | # 11 | # Based on the Holtek the HT12E/HT12F chipsets 12 | # 13 | # HT12E : https://www.holtek.com/documents/10179/116711/2_12ev120.pdf 14 | # HT12F : https://www.holtek.com/documents/10179/116711/2_12dv120.pdf 15 | # 16 | # FCC ID: L3HFAN11T 17 | # 18 | # https://fccid.io/L3HFAN11T 19 | # 20 | # 0.36ms Short 21 | # 0.71ms Long 22 | # 23 | # 12bits of info ( 13 bits transmitted ) 24 | # 25 | # receiver requires three matching transmissions optionally followed by one packet with all shorts 26 | # 27 | # The Fan-11T uses a 4 bits dip-switch as an identifier 28 | # 29 | # packets can be described as 30 | # 31 | # + 4 bit ID + + 6 bit command 32 | # 33 | # The follow table uses '1001' as the ID code: 34 | # if short is 0 and Long is 1 35 | # 36 | # Hi 0 1 1 0 0 1 0 1 0 0 0 0 0 = 0110010100000 = 3232 = 0xca0 37 | # Med 0 1 1 0 0 1 0 0 1 0 0 0 0 = 0110010010000 = 3216 = 0xc90 38 | # Low 0 1 1 0 0 1 0 0 0 1 0 0 0 = 0110010001000 = 3208 = 0xc88 39 | # Off 0 1 1 0 0 1 0 0 0 0 0 1 0 = 0110010000010 = 3202 = 0xc82 40 | # Lit 0 1 1 0 0 1 0 0 0 0 0 0 1 = 0110010000001 = 3201 = 0xc81 41 | # End? 0 1 1 0 0 1 0 0 0 0 0 0 0 = 0110010000000 = 3200 = 0xC80 42 | # 43 | # 44 | # 45 | # 0110010000010 46 | 47 | # rtl_433 -R 0 -f 302450000 -c fan-11t.conf 48 | 49 | # The FCC Documentation list the frequency at 303.9MMz but the devices tests out to between 303.4 and 303.5 (depending on battery) 50 | 51 | 52 | # To reduce false positives, uncomment the match 53 | # field and set the value to your remote's dip switch pattern 54 | 55 | # eg: if your Dip-switch setting is up-down-up=up ( '1011' ) 56 | # set the match setting in the decoder definition to 0x6C 57 | # for example: 58 | # match={7}0x66C 59 | 60 | 61 | #` Dip Switch match preamble 62 | # 0000 0x40 63 | # 0001 0x44 64 | # 0010 0x48 65 | # 0011 0x4C 66 | # 0100 0x50 67 | # 0101 0x54 68 | # 0110 0x58 69 | # 0111 0x5C 70 | # 1000 0x60 71 | # 1001 0x64 72 | # 1010 0x68 73 | # 1011 0x6C 74 | # 1100 0x70 75 | # 1101 0x74 76 | # 1110 0x78 77 | # 1111 0x7C 78 | 79 | 80 | 81 | frequency 302.400M 82 | 83 | decoder { 84 | name = Fan-11t, 85 | modulation = OOK_PWM, 86 | short = 360, 87 | long = 710, 88 | gap = 0, 89 | reset = 5000, 90 | tolerance = 50, 91 | unique, 92 | invert, 93 | bits = 13, 94 | get = id:@2:{4}, 95 | get = button:@7:{6}:[32:fan_Hi 16:fan_Med 8:fan_Low 2:fan_Off 1:light 0: 4: ], 96 | } 97 | 98 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Build Analyze Check 2 | on: [push, pull_request] 3 | jobs: 4 | build_check_job: 5 | runs-on: ubuntu-latest 6 | name: Build with CMake 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v2 10 | - name: Run CMake+Ninja 11 | uses: lukka/run-cmake@v3 12 | with: 13 | cmakeListsOrSettingsJson: CMakeListsTxtAdvanced 14 | buildDirectory: '${{runner.workspace}}/b/ninja' 15 | cmakeAppendedArgs: '-GNinja -DENABLE_RTLSDR=OFF -DENABLE_SOAPYSDR=OFF' 16 | - name: Run CMake+UnixMakefiles 17 | uses: lukka/run-cmake@v3 18 | with: 19 | cmakeListsOrSettingsJson: CMakeListsTxtAdvanced 20 | buildDirectory: '${{runner.workspace}}/b/unixmakefiles' 21 | cmakeAppendedArgs: '-G "Unix Makefiles" -DENABLE_RTLSDR=OFF -DENABLE_SOAPYSDR=OFF' 22 | style_check_job: 23 | runs-on: ubuntu-latest 24 | name: Check code style 25 | steps: 26 | - uses: actions/checkout@v2 27 | - name: Style Check 28 | uses: ./.github/actions/style-check 29 | maintainer_update_check_job: 30 | runs-on: ubuntu-latest 31 | name: Needs maintainer_update 32 | steps: 33 | - uses: actions/checkout@v2 34 | - name: Working directory clean excluding untracked files 35 | run: | 36 | ./maintainer_update.py 37 | [ -z "$(git status --untracked-files=no --porcelain)" ] 38 | symbolizer_check_job: 39 | runs-on: ubuntu-latest 40 | name: Check symbol errors 41 | steps: 42 | - uses: actions/checkout@v2 43 | - name: Symbolizer report 44 | run: | 45 | ./tests/symbolizer.py check 46 | analyzer_check_job: 47 | # https://github.com/actions/virtual-environments 48 | # - Ubuntu 22.04 ubuntu-22.04 49 | # - Ubuntu 20.04 ubuntu-20.04 50 | # https://apt.llvm.org/ 51 | # - Jammy (22.04) 52 | # - Focal (20.04) 53 | # - Bionic (18.04) 54 | runs-on: ubuntu-22.04 55 | name: Analyze with Clang 56 | steps: 57 | - uses: actions/checkout@v2 58 | - name: Install Clang 59 | run: | 60 | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key 2>/dev/null | sudo apt-key add - 61 | sudo add-apt-repository 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-14 main' -y 62 | sudo apt-get update -q 63 | sudo apt-get install -y clang-14 lld-14 libc++-14-dev libc++abi-14-dev clang-tools-14 64 | - name: Clang Analyzer 65 | # excludes include/mongoose.h src/mongoose.c include/jsmn.h src/jsmn.c 66 | # exit code 1 if there is output 67 | run: | 68 | clang -Iinclude -DTHREADS --analyze -Xanalyzer -analyzer-output=text -Xanalyzer -analyzer-disable-checker=deadcode.DeadStores include/[a-ikln-z]*.h src/[a-ikln-z]*.c src/devices/*.c 2>&1 | tee analyzer.out 69 | [ ! -s analyzer.out ] 70 | -------------------------------------------------------------------------------- /src/devices/ft004b.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | FT-004-B Temperature Sensor. 3 | 4 | Copyright (C) 2017 George Hopkins 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | /** 12 | FT-004-B Temperature Sensor. 13 | 14 | The sensor sends a packet every 60 seconds. Each frame of 46 bits 15 | is sent 3 times without padding/pauses. 16 | 17 | Format: FFFFFFFF ???????? ???????? tttttttt TTT????? ?????? 18 | Fixed type code: 0xf4, Temperature (t=lsb, T=msb), Unknown (?) 19 | 20 | {137} 2f cf 24 78 21 c8 bf 3c 91 e0 87 22 fc f2 47 82 1c 80 21 | {137} 2f ce 24 72 a1 70 bf 38 91 ca 85 c2 fc e2 47 2a 17 00 22 | 23 | Aligning at [..] (insert 2 bits) we get: 24 | 25 | 2f cf 24 78 21 c8 [..] 2f cf 24 78 21 c8 [..] 2f cf 24 78 21 c8 26 | 2f ce 24 72 a1 70 [..] 2f ce 24 72 a1 70 [..] 2f ce 24 72 a1 70 27 | 28 | */ 29 | 30 | #include "decoder.h" 31 | 32 | static int ft004b_callback(r_device *decoder, bitbuffer_t *bitbuffer) 33 | { 34 | uint8_t *msg; 35 | float temperature; 36 | data_t *data; 37 | 38 | if (bitbuffer->bits_per_row[0] != 137 && bitbuffer->bits_per_row[0] != 138) { 39 | return DECODE_ABORT_LENGTH; 40 | } 41 | 42 | /* take the majority of all 46 bits (pattern is sent 3 times) and reverse them */ 43 | msg = bitbuffer->bb[0]; 44 | for (int i = 0; i < (46 + 7) / 8; i++) { 45 | uint8_t a = bitrow_get_byte(msg, i * 8); 46 | uint8_t b = bitrow_get_byte(msg, i * 8 + 46); 47 | uint8_t c = bitrow_get_byte(msg, i * 8 + 46 * 2); 48 | msg[i] = reverse8((a & b) | (b & c) | (a & c)); 49 | } 50 | 51 | if (msg[0] != 0xf4) 52 | return DECODE_FAIL_SANITY; 53 | 54 | int temp_raw = ((msg[4] & 0x7) << 8) | msg[3]; 55 | temperature = (temp_raw * 0.05f) - 40.0f; 56 | 57 | /* clang-format off */ 58 | data = data_make( 59 | "model", "", DATA_STRING, "FT-004B", 60 | "temperature_C", "Temperature", DATA_FORMAT, "%.1f", DATA_DOUBLE, temperature, 61 | NULL); 62 | /* clang-format on */ 63 | decoder_output_data(decoder, data); 64 | 65 | return 1; 66 | } 67 | 68 | static char *output_fields[] = { 69 | "model", 70 | "temperature_C", 71 | NULL, 72 | }; 73 | 74 | r_device ft004b = { 75 | .name = "FT-004-B Temperature Sensor", 76 | .modulation = OOK_PULSE_PPM, 77 | .short_width = 1956, 78 | .long_width = 3900, 79 | .gap_limit = 4000, 80 | .reset_limit = 4000, 81 | .decode_fn = &ft004b_callback, 82 | .fields = output_fields, 83 | }; 84 | -------------------------------------------------------------------------------- /src/devices/jasco.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Jasco/GE Choice Alert Wireless Device Decoder. 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | */ 9 | /** 10 | Jasco/GE Choice Alert Wireless Device Decoder. 11 | 12 | - Frequency: 318.01 MHz 13 | 14 | Manchester PCM with a de-sync preamble of 0xFC0C (11111100000011000). 15 | 16 | Packets are 32 bit, 24 bit data and 8 bit XOR checksum. 17 | 18 | */ 19 | 20 | #include "decoder.h" 21 | 22 | static int jasco_decode(r_device *decoder, bitbuffer_t *bitbuffer) 23 | { 24 | uint8_t const preamble[] = {0xfc, 0x0c}; // length 16 25 | 26 | if (bitbuffer->bits_per_row[0] < 80 27 | || bitbuffer->bits_per_row[0] > 87) { 28 | if (bitbuffer->bits_per_row[0] > 0) { 29 | decoder_logf(decoder, 2, __func__, "invalid bit count %d", bitbuffer->bits_per_row[0]); 30 | } 31 | return DECODE_ABORT_EARLY; 32 | } 33 | 34 | unsigned start_pos = bitbuffer_search(bitbuffer, 0, 0, preamble, 16) + 16; 35 | 36 | if (start_pos + 64 > bitbuffer->bits_per_row[0]) { 37 | return DECODE_ABORT_LENGTH; 38 | } 39 | 40 | bitbuffer_t packet_bits = {0}; 41 | bitbuffer_manchester_decode(bitbuffer, 0, start_pos, &packet_bits, 32); 42 | 43 | if (packet_bits.bits_per_row[0] < 32) { 44 | return DECODE_ABORT_LENGTH; 45 | } 46 | 47 | uint8_t *b = packet_bits.bb[0]; 48 | 49 | int chk = b[0] ^ b[1] ^ b[2] ^ b[3]; 50 | if (chk) { 51 | return DECODE_FAIL_MIC; 52 | } 53 | 54 | int sensor_id = (b[0] << 8) | b[1]; 55 | 56 | int s_closed = ((b[2] & 0xef) == 0xef); 57 | // int battery = 0; 58 | 59 | /* clang-format off */ 60 | data_t *data = data_make( 61 | "model", "", DATA_STRING, "Jasco-Security", 62 | "id", "Id", DATA_INT, sensor_id, 63 | "status", "Closed", DATA_INT, s_closed, 64 | "mic", "Integrity", DATA_STRING, "CHECKSUM", 65 | NULL); 66 | /* clang-format on */ 67 | 68 | decoder_output_data(decoder, data); 69 | return 1; 70 | } 71 | 72 | static char *output_fields[] = { 73 | "model", 74 | "id", 75 | "status", 76 | "mic", 77 | NULL, 78 | }; 79 | 80 | r_device jasco = { 81 | .name = "Jasco/GE Choice Alert Security Devices", 82 | .modulation = OOK_PULSE_PCM, 83 | .short_width = 250, 84 | .long_width = 250, 85 | .reset_limit = 1800, // Maximum gap size before End Of Message 86 | .decode_fn = &jasco_decode, 87 | .fields = output_fields, 88 | 89 | }; 90 | -------------------------------------------------------------------------------- /src/devices/thermopro_tp11.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Thermopro TP-11 Thermometer. 3 | 4 | Copyright (C) 2017 Google Inc. 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | /** 12 | Thermopro TP-11 Thermometer. 13 | 14 | normal sequence of bit rows: 15 | 16 | [00] {33} db 41 57 c2 80 : 11011011 01000001 01010111 11000010 1 17 | [01] {33} db 41 57 c2 80 : 11011011 01000001 01010111 11000010 1 18 | [02] {33} db 41 57 c2 80 : 11011011 01000001 01010111 11000010 1 19 | [03] {32} db 41 57 c2 : 11011011 01000001 01010111 11000010 20 | 21 | */ 22 | #include "decoder.h" 23 | 24 | static int thermopro_tp11_sensor_callback(r_device *decoder, bitbuffer_t *bitbuffer) 25 | { 26 | // Compare first four bytes of rows that have 32 or 33 bits. 27 | int row = bitbuffer_find_repeated_row(bitbuffer, 2, 32); 28 | if (row < 0) 29 | return DECODE_ABORT_EARLY; 30 | uint8_t *b = bitbuffer->bb[row]; 31 | 32 | if (bitbuffer->bits_per_row[row] > 33) 33 | return DECODE_ABORT_LENGTH; 34 | 35 | uint8_t ic = lfsr_digest8_reflect(b, 3, 0x51, 0x04); 36 | if (ic != b[3]) { 37 | return DECODE_FAIL_MIC; 38 | } 39 | 40 | // No need to decode/extract values for simple test 41 | if ( (!b[0] && !b[1] && !b[2] && !b[3]) 42 | || (b[0] == 0xff && b[1] == 0xff && b[2] == 0xff && b[3] == 0xff)) { 43 | decoder_log(decoder, 2, __func__, "DECODE_FAIL_SANITY data all 0x00 or 0xFF"); 44 | return DECODE_FAIL_SANITY; 45 | } 46 | 47 | int device = (b[0] << 4) | (b[1] >> 4); 48 | int temp_raw = ((b[1] & 0x0f) << 8) | b[2]; 49 | float temp_c = (temp_raw - 200) * 0.1f; 50 | 51 | /* clang-format off */ 52 | data_t *data = data_make( 53 | "model", "", DATA_STRING, "Thermopro-TP11", 54 | "id", "Id", DATA_INT, device, 55 | "temperature_C", "Temperature", DATA_FORMAT, "%.01f C", DATA_DOUBLE, temp_c, 56 | "mic", "Integrity", DATA_STRING, "CRC", 57 | NULL); 58 | /* clang-format on */ 59 | decoder_output_data(decoder, data); 60 | return 1; 61 | } 62 | 63 | static char *output_fields[] = { 64 | "model", 65 | "id", 66 | "temperature_C", 67 | "mic", 68 | NULL, 69 | }; 70 | 71 | r_device thermopro_tp11 = { 72 | .name = "Thermopro TP11 Thermometer", 73 | .modulation = OOK_PULSE_PPM, 74 | .short_width = 500, 75 | .long_width = 1500, 76 | .gap_limit = 2000, 77 | .reset_limit = 4000, 78 | .decode_fn = &thermopro_tp11_sensor_callback, 79 | .fields = output_fields, 80 | }; 81 | -------------------------------------------------------------------------------- /src/devices/rftech.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | RF-tech decoder (INFRA 217S34). 3 | 4 | Copyright (C) 2016 Erik Johannessen 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | /** 12 | RF-tech decoder (INFRA 217S34). 13 | 14 | Also marked INFRA 217S34 15 | Ewig Industries Macao 16 | 17 | Example of message: 18 | 19 | 01001001 00011010 00000100 20 | 21 | - First byte is unknown, but probably id. 22 | - Second byte is the integer part of the temperature. 23 | - Third byte bits 0-3 is the fraction/tenths of the temperature. 24 | - Third byte bit 7 is 1 with fresh batteries. 25 | - Third byte bit 6 is 1 on button press. 26 | 27 | More sample messages: 28 | 29 | {24} ad 18 09 : 10101101 00011000 00001001 30 | {24} 3e 17 09 : 00111110 00010111 00001001 31 | {24} 70 17 03 : 01110000 00010111 00000011 32 | {24} 09 17 01 : 00001001 00010111 00000001 33 | 34 | With fresh batteries and button pressed: 35 | 36 | {24} c5 16 c5 : 11000101 00010110 11000101 37 | 38 | */ 39 | 40 | #include "decoder.h" 41 | 42 | static int rftech_callback(r_device *decoder, bitbuffer_t *bitbuffer) 43 | { 44 | int r = bitbuffer_find_repeated_row(bitbuffer, 3, 24); 45 | 46 | if (r < 0 || bitbuffer->bits_per_row[r] != 24) 47 | return DECODE_ABORT_LENGTH; 48 | uint8_t *b = bitbuffer->bb[r]; 49 | 50 | int sensor_id = b[0]; 51 | float temp_c = (b[1] & 0x7f) + (b[2] & 0x0f) * 0.1f; 52 | if (b[1] & 0x80) 53 | temp_c = -temp_c; 54 | 55 | int battery = (b[2] & 0x80) == 0x80; 56 | int button = (b[2] & 0x60) != 0; 57 | 58 | /* clang-format off */ 59 | data_t *data = data_make( 60 | "model", "", DATA_STRING, "RF-tech", 61 | "id", "Id", DATA_INT, sensor_id, 62 | "battery_ok", "Battery", DATA_INT, battery, 63 | "temperature_C", "Temperature", DATA_FORMAT, "%.01f C", DATA_DOUBLE, temp_c, 64 | "button", "Button", DATA_INT, button, 65 | NULL); 66 | /* clang-format on */ 67 | 68 | decoder_output_data(decoder, data); 69 | return 1; 70 | } 71 | 72 | static char *csv_output_fields[] = { 73 | "model", 74 | "id", 75 | "battery_ok", 76 | "temperature_C", 77 | "button", 78 | NULL, 79 | }; 80 | 81 | r_device rftech = { 82 | .name = "RF-tech", 83 | .modulation = OOK_PULSE_PPM, 84 | .short_width = 2000, 85 | .long_width = 4000, 86 | .gap_limit = 5000, 87 | .reset_limit = 10000, 88 | .decode_fn = &rftech_callback, 89 | .disabled = 1, 90 | .fields = csv_output_fields, 91 | }; 92 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ######################################################################## 2 | # Compile test cases 3 | ######################################################################## 4 | add_executable(data-test data-test.c ../src/output_file.c ../src/term_ctl.c) 5 | 6 | target_link_libraries(data-test data) 7 | 8 | add_test(data-test data-test) 9 | 10 | add_executable(baseband-test baseband-test.c ../src/baseband.c) 11 | 12 | if(UNIX) 13 | target_link_libraries(baseband-test m) 14 | endif() 15 | 16 | #add_test(baseband-test baseband-test) 17 | 18 | ######################################################################## 19 | # Define and build all unit tests 20 | ######################################################################## 21 | # target_compile_definitions was only added in CMake 2.8.11 22 | add_definitions(-D_TEST) 23 | foreach(testSrc bitbuffer.c fileformat.c optparse.c util.c) 24 | get_filename_component(testName ${testSrc} NAME_WE) 25 | 26 | add_executable(test_${testName} ../src/${testSrc}) 27 | 28 | add_test(${testName}_test test_${testName}) 29 | endforeach(testSrc) 30 | 31 | ######################################################################## 32 | # Define integration tests 33 | ######################################################################## 34 | add_test(rtl_433_help ../src/rtl_433 -h) 35 | 36 | ######################################################################## 37 | # Define style checks 38 | ######################################################################## 39 | add_executable(style-check style-check.c) 40 | file(GLOB STYLE_CHECK_FILES ../include/*.h ../src/*.c ../src/devices/*.c ../CMakeLists.txt ../*/CMakeLists.txt) 41 | list(REMOVE_ITEM STYLE_CHECK_FILES 42 | "${CMAKE_CURRENT_SOURCE_DIR}/../include/jsmn.h" 43 | "${CMAKE_CURRENT_SOURCE_DIR}/../src/jsmn.c" 44 | "${CMAKE_CURRENT_SOURCE_DIR}/../include/mongoose.h" 45 | "${CMAKE_CURRENT_SOURCE_DIR}/../src/mongoose.c") 46 | add_test(style-check style-check ${STYLE_CHECK_FILES}) 47 | 48 | ######################################################################## 49 | # Define clang static analyzer checks 50 | ######################################################################## 51 | if(BUILD_TESTING_ANALYZER) 52 | file(GLOB ANALYZER_CHECK_FILES ../include/*.h ../src/*.c ../src/devices/*.c) 53 | list(REMOVE_ITEM ANALYZER_CHECK_FILES 54 | "${CMAKE_CURRENT_SOURCE_DIR}/../include/jsmn.h" 55 | "${CMAKE_CURRENT_SOURCE_DIR}/../src/jsmn.c" 56 | "${CMAKE_CURRENT_SOURCE_DIR}/../include/mongoose.h" 57 | "${CMAKE_CURRENT_SOURCE_DIR}/../src/mongoose.c") 58 | add_test(clang-analyzer 59 | ${CMAKE_CURRENT_SOURCE_DIR}/exitcode-for-output.sh 60 | clang 61 | -I${CMAKE_CURRENT_SOURCE_DIR}/../include 62 | --analyze 63 | -Xanalyzer 64 | -analyzer-output=text 65 | -Xanalyzer 66 | -analyzer-disable-checker=deadcode.DeadStores 67 | ${ANALYZER_CHECK_FILES}) 68 | endif() 69 | -------------------------------------------------------------------------------- /conf/heatilator.conf: -------------------------------------------------------------------------------- 1 | # Decoder for Heatilator gas log remotes. 2 | # 3 | # Heatilator gas logs use OOK_PULSE_PPM encoding. The format is very similar to 4 | # that decoded by 'generic_remote', but seems to differ slightly in timing. The 5 | # device does _not_ use a discrete chip to generate the waveform; it's generated 6 | # in code. 7 | # 8 | # The packet starts with 380 uS start pulse followed by an eternity (14.3 mS) of silence. 9 | # - 0 is defined as a 1430 uS pulse followed by a 460 uS gap. 10 | # - 1 is defined as a 380 uS pulse followed by a 1420 uS gap. 11 | # 12 | # Transmissions consist of the start bit followed by 24 data bits. These packets are 13 | # repeated many times. 14 | # 15 | # Because there's such a long start bit/preamble, the decoder usually creates the first 16 | # row with a single bit, followed by 'n' rows with 25 bits (the 24 data bits and the 17 | # start bit of the following packet), then the last row with the expected 24 bits. 18 | # 19 | # Packet layout: 20 | # 21 | # Bit number 22 | # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 23 | # - - - - - - - - - - DEVICE SERIAL NUMBER - - - - - - - - - |- COMMAND - 24 | # 25 | # The device serial number is (presumedly) burned into the device when manufactured. 26 | # The command is further broken down into the following bits: 27 | # 28 | # 20 21 22 23 29 | # X X S T 30 | # 31 | # X bits are unknown in function. S is the 'state' of the gas valve/flame. S = 0 32 | # means 'flame off'. S = 1 means 'flame on'. T indicates whether or not the remote 33 | # is in 'thermo' mode - this is a mode where the remote detects the room temperature 34 | # and commands the gas logs on/off to maintain the temperature selected on the remote. 35 | # 36 | # There are safety mechanisms afoot - whenever the gas logs are 'on', on with a timer, 37 | # or on in thermo mode, occasional 'keepalive' messages are sent to the gas logs to 38 | # guarantee that the remote is still in range and the batteries are not dead. Generally 39 | # these messages are exactly the same as the last command that the remote sent - that is, 40 | # if you turn the logs 'on' manually, the remote will send the same 'on' command every so 41 | # often. 42 | # 43 | # The COMMAND S and T bits have these meanings: 44 | # S T 45 | # ---- 46 | # 0 0 - Off, Manual mode 47 | # 0 1 - Off, Thermo mode (room is too warm) 48 | # 1 0 - On, Manual mode. 49 | # 1 1 - On, Thermo more (room is too cold) 50 | 51 | frequency 433.92M 52 | 53 | decoder { 54 | name = Heatilator-gas-log-remote, 55 | modulation = OOK_PWM, 56 | short = 380, 57 | long = 1420, 58 | gap = 0, 59 | reset = 1800, 60 | bits >= 24, 61 | bits <= 25, 62 | get = id:@0:{20}, 63 | get = state:@22:{1}:[0:flame_off 1:flame_on], 64 | get = mode:@23:{1}:[0:manual 1:thermo], 65 | } 66 | -------------------------------------------------------------------------------- /src/devices/generic_motion.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Generic off-brand wireless motion sensor and alarm system on 433.3MHz. 3 | 4 | Copyright (C) 2015 Christian W. Zuckschwerdt 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | /** 12 | Generic off-brand wireless motion sensor and alarm system on 433.3MHz. 13 | 14 | Example codes are: 80042 Arm alarm, 80002 Disarm alarm, 15 | 80008 System ping (every 15 minutes), 800a2, 800c2, 800e2 Motion event 16 | (following motion detection the sensor will blackout for 90 seconds). 17 | 18 | 2315 baud on/off rate and alternating 579 baud bit rate and 463 baud bit rate 19 | Each transmission has a warmup of 17 to 32 pulse widths then 8 packets with 20 | alternating 1:3 / 2:2 or 1:4 / 2:3 gap:pulse ratio for 0/1 bit in the packet 21 | with a repeat gap of 4 pulse widths, i.e.: 22 | - 6704 us to 13092 us warmup pulse, 1672 us gap, 23 | - 0: 472 us gap, 1332 us pulse 24 | - 1: 920 us gap, 888 us pulse 25 | - 1672 us repeat gap, 26 | - 0: 472 us gap, 1784 us pulse 27 | - 1: 920 us gap, 1332 us pulse 28 | - ... 29 | */ 30 | 31 | #include "decoder.h" 32 | 33 | static int generic_motion_callback(r_device *decoder, bitbuffer_t *bitbuffer) 34 | { 35 | data_t *data; 36 | uint8_t *b; 37 | int code; 38 | char code_str[6]; 39 | 40 | for (int i = 0; i < bitbuffer->num_rows; ++i) { 41 | b = bitbuffer->bb[i]; 42 | // strictly validate package as there is no checksum 43 | if ((bitbuffer->bits_per_row[i] != 20) 44 | || ((b[1] == 0) && (b[2] == 0)) 45 | || ((b[1] == 0xff) && (b[2] == 0xff)) 46 | || bitbuffer_count_repeats(bitbuffer, i, 0) < 3) 47 | continue; // DECODE_ABORT_EARLY 48 | 49 | code = (b[0] << 12) | (b[1] << 4) | (b[2] >> 4); 50 | sprintf(code_str, "%05x", code); 51 | 52 | /* clang-format off */ 53 | data = data_make( 54 | "model", "", DATA_STRING, "Generic-Motion", 55 | "code", "", DATA_STRING, code_str, 56 | NULL); 57 | /* clang-format on */ 58 | 59 | decoder_output_data(decoder, data); 60 | return 1; 61 | } 62 | return DECODE_ABORT_EARLY; 63 | } 64 | 65 | static char *output_fields[] = { 66 | "model", 67 | "code", 68 | NULL, 69 | }; 70 | 71 | r_device generic_motion = { 72 | .name = "Generic wireless motion sensor", 73 | .modulation = OOK_PULSE_PWM, 74 | .short_width = 888, 75 | .long_width = (1332 + 1784) / 2, 76 | .sync_width = 1784 + 670, 77 | .gap_limit = 1200, 78 | .reset_limit = 2724 * 1.5, 79 | .decode_fn = &generic_motion_callback, 80 | .fields = output_fields, 81 | }; 82 | -------------------------------------------------------------------------------- /examples/rtl_433_custom.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | 1) 51 | { 52 | $debug = $argv[1]; 53 | } 54 | //udp server IP and Port for listen 55 | $UDP_IP = "127.0.0.1"; 56 | $UDP_PORT = 1433; 57 | //create socket and bind them 58 | $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); 59 | socket_bind($socket, $UDP_IP, $UDP_PORT); 60 | //init of this variables 61 | $from = ''; 62 | $port = 0; 63 | 64 | //use the output of rtl_433 -f 433920000 -f 433920000 -H 120 -F syslog:127.0.0.1:1433" 65 | //returns the json payload 66 | function parse_syslog($line) 67 | { 68 | //Try to extract the payload from a syslog line.// 69 | $line = mb_convert_encoding($line, "ASCII"); 70 | 71 | if (startsWith($line,"<")) 72 | { 73 | //fields should be "VER", timestamp, hostname, command, pid, mid, sdata, payload 74 | $fields = explode(" ",$line, 8); 75 | $line = $fields[7]; 76 | } 77 | return $line; 78 | } 79 | 80 | //server main loop 81 | for (;;) 82 | { 83 | //read from $socket into $line 84 | socket_recvfrom($socket, $line, 1024, 0, $from, $port); 85 | try 86 | { 87 | //parse $line -> returns the json payload 88 | $line = parse_syslog($line); 89 | 90 | /* 91 | do something with content of $line 92 | for example decode $line into a array 93 | $arr = json_decode($line,true); 94 | 95 | do something with that array and 96 | puted into a file as json 97 | 98 | file_put_contents('test.json', json_encode($arr, JSON_PRETTY_PRINT); 99 | */ 100 | } 101 | catch (Exception $e) { 102 | echo "---------------------------------------------\n"; 103 | echo 'Exception intercepted: ', $e->getMessage(), "\n"; 104 | echo "------------------------------------------- -\n"; 105 | } 106 | } 107 | ?> 108 | 109 | -------------------------------------------------------------------------------- /include/pulse_detect.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | Pulse detection functions. 3 | 4 | Copyright (C) 2015 Tommy Vestermark 5 | Copyright (C) 2020 Christian W. Zuckschwerdt 6 | 7 | This program is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; either version 2 of the License, or 10 | (at your option) any later version. 11 | */ 12 | 13 | #ifndef INCLUDE_PULSE_DETECT_H_ 14 | #define INCLUDE_PULSE_DETECT_H_ 15 | 16 | #include 17 | #include 18 | #include "pulse_data.h" 19 | #include "data.h" 20 | 21 | /// Package types. 22 | enum package_types { 23 | PULSE_DATA_OOK = 1, 24 | PULSE_DATA_FSK = 2, 25 | }; 26 | 27 | /// FSK pulse detector to use. 28 | enum { 29 | FSK_PULSE_DETECT_OLD, 30 | FSK_PULSE_DETECT_NEW, 31 | FSK_PULSE_DETECT_AUTO, 32 | FSK_PULSE_DETECT_END, 33 | }; 34 | 35 | typedef struct pulse_detect pulse_detect_t; 36 | 37 | pulse_detect_t *pulse_detect_create(void); 38 | 39 | void pulse_detect_free(pulse_detect_t *pulse_detect); 40 | 41 | /// Set pulse detector level values. 42 | /// 43 | /// @param pulse_detect The pulse_detect instance 44 | /// @param use_mag_est Use magnitude instead of amplitude 45 | /// @param fixed_high_level Manual high level override, default is 0 (auto) 46 | /// @param min_high_level Minimum high level, default is -12 dB 47 | /// @param high_low_ratio Minimum signal noise ratio, default is 9 dB 48 | /// @param verbosity Debug output verbosity, 0=None, 1=Levels, 2=Histograms 49 | void pulse_detect_set_levels(pulse_detect_t *pulse_detect, int use_mag_est, float fixed_high_level, float min_high_level, float high_low_ratio, int verbosity); 50 | 51 | /// Demodulate On/Off Keying (OOK) and Frequency Shift Keying (FSK) from an envelope signal. 52 | /// 53 | /// Function is stateful and can be called with chunks of input data. 54 | /// 55 | /// @param pulse_detect The pulse_detect instance 56 | /// @param envelope_data Samples with amplitude envelope of carrier 57 | /// @param fm_data Samples with frequency offset from center frequency 58 | /// @param len Number of samples in input buffers 59 | /// @param samp_rate Sample rate in samples per second 60 | /// @param sample_offset Offset tracking for ringbuffer 61 | /// @param[in,out] pulses Will return a pulse_data_t structure 62 | /// @param[in,out] fsk_pulses Will return a pulse_data_t structure for FSK demodulated data 63 | /// @param fpdm Index of filter setting to use 64 | /// @return if a package is detected 65 | /// @retval 0 all input sample data is processed 66 | /// @retval 1 OOK package is detected (but all sample data is still not completely processed) 67 | /// @retval 2 FSK package is detected (but all sample data is still not completely processed) 68 | int pulse_detect_package(pulse_detect_t *pulse_detect, int16_t const *envelope_data, int16_t const *fm_data, int len, uint32_t samp_rate, uint64_t sample_offset, pulse_data_t *pulses, pulse_data_t *fsk_pulses, unsigned fpdm); 69 | 70 | #endif /* INCLUDE_PULSE_DETECT_H_ */ 71 | -------------------------------------------------------------------------------- /src/devices/generic_remote.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Generic remotes and sensors using PT2260/PT2262 SC2260/SC2262 EV1527 protocol. 3 | 4 | Copyright (C) 2015 Tommy Vestermark 5 | Copyright (C) 2015 nebman 6 | 7 | This program is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; either version 2 of the License, or 10 | (at your option) any later version. 11 | */ 12 | /** 13 | Generic remotes and sensors using PT2260/PT2262 SC2260/SC2262 EV1527 protocol. 14 | 15 | Tested devices: 16 | - SC2260 17 | - EV1527 18 | */ 19 | 20 | #include "decoder.h" 21 | 22 | static int generic_remote_callback(r_device *decoder, bitbuffer_t *bitbuffer) 23 | { 24 | data_t *data; 25 | uint8_t *b = bitbuffer->bb[0]; 26 | char tristate[23]; 27 | char *p = tristate; 28 | 29 | //invert bits, short pulse is 0, long pulse is 1 30 | b[0] = ~b[0]; 31 | b[1] = ~b[1]; 32 | b[2] = ~b[2]; 33 | 34 | unsigned bits = bitbuffer->bits_per_row[0]; 35 | 36 | // Validate package 37 | if ((bits != 25) 38 | || (b[3] & 0x80) == 0 // Last bit (MSB here) is always 1 39 | || (b[0] == 0 && b[1] == 0) // Reduce false positives. ID 0x0000 not supported 40 | || (b[2] == 0)) // Reduce false positives. CMD 0x00 not supported 41 | return DECODE_ABORT_LENGTH; 42 | 43 | int id_16b = b[0] << 8 | b[1]; 44 | int cmd_8b = b[2]; 45 | 46 | // output tristate coding 47 | uint32_t full = b[0] << 16 | b[1] << 8 | b[2]; 48 | 49 | for (int i = 22; i >= 0; i -= 2) { 50 | switch ((full >> i) & 0x03) { 51 | case 0x00: *p++ = '0'; break; 52 | case 0x01: *p++ = 'Z'; break; // floating / "open" 53 | case 0x02: *p++ = 'X'; break; // tristate 10 is invalid code for SC226x but valid in EV1527 54 | case 0x03: *p++ = '1'; break; 55 | default: *p++ = '?'; break; // not possible anyway 56 | } 57 | } 58 | *p = '\0'; 59 | 60 | /* clang-format off */ 61 | data = data_make( 62 | "model", "", DATA_STRING, "Generic-Remote", 63 | "id", "House Code", DATA_INT, id_16b, 64 | "cmd", "Command", DATA_INT, cmd_8b, 65 | "tristate", "Tri-State", DATA_STRING, tristate, 66 | NULL); 67 | /* clang-format on */ 68 | 69 | decoder_output_data(decoder, data); 70 | 71 | return 1; 72 | } 73 | 74 | static char *output_fields[] = { 75 | "model", 76 | "id", 77 | "cmd", 78 | "tristate", 79 | NULL, 80 | }; 81 | 82 | r_device generic_remote = { 83 | .name = "Generic Remote SC226x EV1527", 84 | .modulation = OOK_PULSE_PWM, 85 | .short_width = 464, 86 | .long_width = 1404, 87 | .reset_limit = 1800, 88 | .sync_width = 0, // No sync bit used 89 | .tolerance = 200, // us 90 | .decode_fn = &generic_remote_callback, 91 | .fields = output_fields, 92 | }; 93 | -------------------------------------------------------------------------------- /examples/rtl_433_http_stream.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Custom data handling example for rtl_433's HTTP (line) streaming API of JSON events.""" 4 | 5 | # Start rtl_433 (`rtl_433 -F http`), then this script. 6 | # Needs the Requests package to be installed. 7 | 8 | import requests 9 | import json 10 | from time import sleep 11 | 12 | # You can run rtl_433 and this script on different machines, 13 | # start rtl_433 with `-F http:0.0.0.0`, and change 14 | # to e.g. `HTTP_HOST = "192.168.1.100"` (use your server ip) below. 15 | HTTP_HOST = "127.0.0.1" 16 | HTTP_PORT = 8433 17 | 18 | 19 | def stream_lines(): 20 | url = f'http://{HTTP_HOST}:{HTTP_PORT}/stream' 21 | headers = {'Accept': 'application/json'} 22 | 23 | # You will receive JSON events, one per line terminated with CRLF. 24 | # On Events and Stream endpoints a keep-alive of CRLF will be send every 60 seconds. 25 | response = requests.get(url, headers=headers, timeout=70, stream=True) 26 | print(f'Connected to {url}') 27 | 28 | for chunk in response.iter_lines(): 29 | yield chunk 30 | 31 | 32 | def handle_event(line): 33 | try: 34 | # Decode the message as JSON 35 | data = json.loads(line) 36 | 37 | # 38 | # Change for your custom handling below, this is a simple example 39 | # 40 | label = data["model"] 41 | if "channel" in data: 42 | label += ".CH" + str(data["channel"]) 43 | elif "id" in data: 44 | label += ".ID" + str(data["id"]) 45 | 46 | # E.g. match `model` and `id` to a descriptive name. 47 | if data["model"] == "LaCrosse-TX" and data["id"] == 123: 48 | label = "Living Room" 49 | 50 | if "battery_ok" in data: 51 | if data["battery_ok"] == 0: 52 | print(label + ' Battery empty!') 53 | 54 | if "temperature_C" in data: 55 | print(label + ' Temperature ', data["temperature_C"]) 56 | 57 | if "humidity" in data: 58 | print(label + ' Humidity ', data["humidity"]) 59 | 60 | except KeyError: 61 | # Ignore unknown message data and continue 62 | pass 63 | 64 | except ValueError as e: 65 | # Warn on decoding errors 66 | print(f'Event format not recognized: {e}') 67 | 68 | 69 | def rtl_433_listen(): 70 | """Listen to all messages in a loop forever.""" 71 | 72 | # Loop forever 73 | while True: 74 | try: 75 | # Open the HTTP (line) streaming API of JSON events 76 | for chunk in stream_lines(): 77 | # print(chunk) 78 | chunk = chunk.rstrip() 79 | if not chunk: 80 | # filter out keep-alive empty lines 81 | continue 82 | # Decode the JSON message 83 | handle_event(chunk) 84 | 85 | except requests.ConnectionError: 86 | print('Connection failed, retrying...') 87 | sleep(5) 88 | 89 | 90 | if __name__ == "__main__": 91 | try: 92 | rtl_433_listen() 93 | except KeyboardInterrupt: 94 | print('\nExiting.') 95 | pass 96 | -------------------------------------------------------------------------------- /src/devices/abmt.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Amazon Basics Meat Thermometer 3 | 4 | Copyright (C) 2021 Benjamin Larsson 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | /** 12 | Amazon Basics Meat Thermometer 13 | 14 | Manchester encoded PCM signal. 15 | 16 | [00] {48} e4 00 a3 01 40 ff 17 | 18 | II 00 UU TT T0 FF 19 | 20 | I - power on random id 21 | 0 - zeros 22 | U - Unknown 23 | T - bcd coded temperature 24 | F - ones 25 | 26 | 27 | */ 28 | #include "decoder.h" 29 | 30 | #define SYNC_PATTERN_START_OFF 72 31 | 32 | // Convert two BCD encoded nibbles to an integer 33 | static unsigned bcd2int(uint8_t bcd) 34 | { 35 | return 10 * (bcd >> 4) + (bcd & 0xF); 36 | } 37 | 38 | static int abmt_callback(r_device *decoder, bitbuffer_t *bitbuffer) 39 | { 40 | int row; 41 | float temp_c; 42 | bitbuffer_t packet_bits = {0}; 43 | unsigned int id; 44 | data_t *data; 45 | unsigned bitpos = 0; 46 | uint8_t *b; 47 | int16_t temp; 48 | uint8_t const sync_pattern[3] = {0x55, 0xAA, 0xAA}; 49 | 50 | // Find repeats 51 | row = bitbuffer_find_repeated_row(bitbuffer, 4, 90); 52 | if (row < 0) 53 | return DECODE_ABORT_EARLY; 54 | 55 | if (bitbuffer->bits_per_row[row] > 120) 56 | return DECODE_ABORT_LENGTH; 57 | 58 | // search for 24 bit sync pattern 59 | bitpos = bitbuffer_search(bitbuffer, row, bitpos, sync_pattern, 24); 60 | // if sync is not found or sync is found with to little bits available, abort 61 | if ((bitpos == bitbuffer->bits_per_row[row]) || (bitpos < SYNC_PATTERN_START_OFF)) 62 | return DECODE_FAIL_SANITY; 63 | 64 | // sync bitstream 65 | bitbuffer_manchester_decode(bitbuffer, row, bitpos - SYNC_PATTERN_START_OFF, &packet_bits, 48); 66 | bitbuffer_invert(&packet_bits); 67 | 68 | b = packet_bits.bb[0]; 69 | id = b[0]; 70 | temp = bcd2int(b[3]) * 10 + bcd2int(b[4] >> 4); 71 | temp_c = (float)temp; 72 | 73 | /* clang-format off */ 74 | data = data_make( 75 | "model", "", DATA_STRING, "Basics-Meat", 76 | "id", "Id", DATA_INT, id, 77 | "temperature_C", "Temperature", DATA_FORMAT, "%.01f C", DATA_DOUBLE, temp_c, 78 | NULL); 79 | /* clang-format on */ 80 | decoder_output_data(decoder, data); 81 | return 1; 82 | } 83 | 84 | static char *output_fields[] = { 85 | "model", 86 | "id", 87 | "temperature_C", 88 | NULL, 89 | }; 90 | 91 | r_device abmt = { 92 | .name = "Amazon Basics Meat Thermometer", 93 | .modulation = OOK_PULSE_PCM, 94 | .short_width = 550, 95 | .long_width = 550, 96 | .gap_limit = 2000, 97 | .reset_limit = 5000, 98 | .decode_fn = &abmt_callback, 99 | .fields = output_fields, 100 | }; 101 | -------------------------------------------------------------------------------- /examples/rtl_433_http_events.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Custom data handling example for rtl_433's HTTP (chunked) streaming API of JSON events.""" 4 | 5 | # Start rtl_433 (`rtl_433 -F http`), then this script. 6 | # Needs the Requests package to be installed. 7 | 8 | import requests 9 | import json 10 | from time import sleep 11 | 12 | # You can run rtl_433 and this script on different machines, 13 | # start rtl_433 with `-F http:0.0.0.0`, and change 14 | # to e.g. `HTTP_HOST = "192.168.1.100"` (use your server ip) below. 15 | HTTP_HOST = "127.0.0.1" 16 | HTTP_PORT = 8433 17 | 18 | 19 | def stream_events(): 20 | url = f'http://{HTTP_HOST}:{HTTP_PORT}/events' 21 | headers = {'Accept': 'application/json'} 22 | 23 | # You will receive JSON events, one per line terminated with CRLF. 24 | # On Events and Stream endpoints a keep-alive of CRLF will be send every 60 seconds. 25 | response = requests.get(url, headers=headers, timeout=70, stream=True) 26 | print(f'Connected to {url}') 27 | 28 | for chunk in response.iter_content(chunk_size=None): 29 | yield chunk 30 | 31 | 32 | def handle_event(line): 33 | try: 34 | # Decode the message as JSON 35 | data = json.loads(line) 36 | 37 | # 38 | # Change for your custom handling below, this is a simple example 39 | # 40 | label = data["model"] 41 | if "channel" in data: 42 | label += ".CH" + str(data["channel"]) 43 | elif "id" in data: 44 | label += ".ID" + str(data["id"]) 45 | 46 | # E.g. match `model` and `id` to a descriptive name. 47 | if data["model"] == "LaCrosse-TX" and data["id"] == 123: 48 | label = "Living Room" 49 | 50 | if "battery_ok" in data: 51 | if data["battery_ok"] == 0: 52 | print(label + ' Battery empty!') 53 | 54 | if "temperature_C" in data: 55 | print(label + ' Temperature ', data["temperature_C"]) 56 | 57 | if "humidity" in data: 58 | print(label + ' Humidity ', data["humidity"]) 59 | 60 | except KeyError: 61 | # Ignore unknown message data and continue 62 | pass 63 | 64 | except ValueError as e: 65 | # Warn on decoding errors 66 | print(f'Event format not recognized: {e}') 67 | 68 | 69 | def rtl_433_listen(): 70 | """Listen to all messages in a loop forever.""" 71 | 72 | # Loop forever 73 | while True: 74 | try: 75 | # Open the HTTP (chunked) streaming API of JSON events 76 | for chunk in stream_events(): 77 | # print(chunk) 78 | chunk = chunk.rstrip() 79 | if not chunk: 80 | # filter out keep-alive empty lines 81 | continue 82 | # Decode the JSON message 83 | handle_event(chunk) 84 | 85 | except requests.ConnectionError: 86 | print('Connection failed, retrying...') 87 | sleep(5) 88 | 89 | 90 | if __name__ == "__main__": 91 | try: 92 | rtl_433_listen() 93 | except KeyboardInterrupt: 94 | print('\nExiting.') 95 | pass 96 | -------------------------------------------------------------------------------- /examples/rtl_433_http_ws.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Custom data handling example for rtl_433's HTTP WebSocket API of JSON events.""" 4 | 5 | # Start rtl_433 (`rtl_433 -F http`), then this script. 6 | # Needs the websocket-client package to be installed. 7 | 8 | import websocket 9 | import json 10 | from time import sleep 11 | 12 | # You can run rtl_433 and this script on different machines, 13 | # start rtl_433 with `-F http:0.0.0.0`, and change 14 | # to e.g. `HTTP_HOST = "192.168.1.100"` (use your server ip) below. 15 | HTTP_HOST = "127.0.0.1" 16 | HTTP_PORT = 8433 17 | 18 | 19 | def ws_events(): 20 | url = f'ws://{HTTP_HOST}:{HTTP_PORT}/ws' 21 | ws = websocket.WebSocket() 22 | ws.connect(url) 23 | 24 | # You will receive JSON events, one per message. 25 | print(f'Connected to {url}') 26 | 27 | while True: 28 | yield ws.recv() 29 | 30 | 31 | def handle_event(line): 32 | try: 33 | # Decode the message as JSON 34 | data = json.loads(line) 35 | 36 | # 37 | # Change for your custom handling below, this is a simple example 38 | # 39 | label = data["model"] 40 | if "channel" in data: 41 | label += ".CH" + str(data["channel"]) 42 | elif "id" in data: 43 | label += ".ID" + str(data["id"]) 44 | 45 | # E.g. match `model` and `id` to a descriptive name. 46 | if data["model"] == "LaCrosse-TX" and data["id"] == 123: 47 | label = "Living Room" 48 | 49 | if "battery_ok" in data: 50 | if data["battery_ok"] == 0: 51 | print(label + ' Battery empty!') 52 | 53 | if "temperature_C" in data: 54 | print(label + ' Temperature ', data["temperature_C"]) 55 | 56 | if "humidity" in data: 57 | print(label + ' Humidity ', data["humidity"]) 58 | 59 | except KeyError: 60 | # Ignore unknown message data and continue 61 | pass 62 | 63 | except ValueError as e: 64 | # Warn on decoding errors 65 | print(f'Event format not recognized: {e}') 66 | 67 | 68 | def rtl_433_listen(): 69 | """Listen to all messages in a loop forever.""" 70 | 71 | # Loop forever 72 | while True: 73 | try: 74 | # Open the HTTP WebSocket API of JSON events 75 | for chunk in ws_events(): 76 | # print(chunk) 77 | chunk = chunk.rstrip() 78 | if not chunk: 79 | # filter out keep-alive empty lines 80 | continue 81 | # Decode the JSON message 82 | handle_event(chunk) 83 | 84 | except ConnectionRefusedError: 85 | print('Connection refused, retrying...') 86 | sleep(5) 87 | pass 88 | 89 | except websocket._exceptions.WebSocketConnectionClosedException: 90 | print('Connection failed, retrying...') 91 | sleep(5) 92 | 93 | 94 | if __name__ == "__main__": 95 | try: 96 | rtl_433_listen() 97 | except KeyboardInterrupt: 98 | print('\nExiting.') 99 | pass 100 | -------------------------------------------------------------------------------- /src/devices/nexa.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Nexa decoder. 3 | 4 | Copyright (C) 2017 Christian Juncker Brædstrup 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | /** 12 | Nexa decoder. 13 | Might be similar to an x1527. 14 | S.a. Kaku, Proove. 15 | 16 | Tested devices: 17 | - Magnetic sensor - LMST-606 18 | 19 | Packet gap is 10 ms. 20 | 21 | This device is very similar to the proove magnetic sensor. 22 | The proove decoder will capture the OFF-state but not the ON-state 23 | since the Nexa uses two different bit lengths for ON and OFF. 24 | */ 25 | 26 | #include "decoder.h" 27 | 28 | static int nexa_callback(r_device *decoder, bitbuffer_t *bitbuffer) 29 | { 30 | data_t *data; 31 | 32 | /* Reject missing sync */ 33 | if (bitbuffer->syncs_before_row[0] != 1) 34 | return DECODE_ABORT_EARLY; 35 | 36 | /* Reject codes of wrong length */ 37 | if (bitbuffer->bits_per_row[0] != 64 && bitbuffer->bits_per_row[0] != 72) 38 | return DECODE_ABORT_LENGTH; 39 | 40 | bitbuffer_t databits = {0}; 41 | // note: not manchester encoded but actually ternary 42 | unsigned pos = bitbuffer_manchester_decode(bitbuffer, 0, 0, &databits, 80); 43 | bitbuffer_invert(&databits); 44 | 45 | /* Reject codes when Manchester decoding fails */ 46 | if (pos != 64 && pos != 72) 47 | return DECODE_ABORT_LENGTH; 48 | 49 | uint8_t *b = databits.bb[0]; 50 | 51 | uint32_t id = (b[0] << 18) | (b[1] << 10) | (b[2] << 2) | (b[3] >> 6); // ID 26 bits 52 | uint32_t group_cmd = (b[3] >> 5) & 1; 53 | uint32_t on_bit = (b[3] >> 4) & 1; 54 | uint32_t channel = ((b[3] >> 2) & 0x03) ^ 0x03; // inverted 55 | uint32_t unit = (b[3] & 0x03) ^ 0x03; // inverted 56 | 57 | /* clang-format off */ 58 | data = data_make( 59 | "model", "", DATA_STRING, "Nexa-Security", 60 | "id", "House Code", DATA_INT, id, 61 | "channel", "Channel", DATA_INT, channel, 62 | "state", "State", DATA_STRING, on_bit ? "ON" : "OFF", 63 | "unit", "Unit", DATA_INT, unit, 64 | "group", "Group", DATA_INT, group_cmd, 65 | NULL); 66 | /* clang-format on */ 67 | 68 | decoder_output_data(decoder, data); 69 | return 1; 70 | } 71 | 72 | static char *output_fields[] = { 73 | "model", 74 | "id", 75 | "channel", 76 | "state", 77 | "unit", 78 | "group", 79 | NULL, 80 | }; 81 | 82 | r_device nexa = { 83 | .name = "Nexa", 84 | .modulation = OOK_PULSE_PPM, 85 | .short_width = 270, // 1:1 86 | .long_width = 1300, // 1:5 87 | .sync_width = 2650, // 1:10, tuned to widely match 2450 to 2850 88 | .tolerance = 200, 89 | .gap_limit = 1500, 90 | .reset_limit = 2800, 91 | .decode_fn = &nexa_callback, 92 | .fields = output_fields, 93 | }; 94 | -------------------------------------------------------------------------------- /src/devices/nice_flor_s.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Nice Flor-s remote for gates. 3 | 4 | Copyright (C) 2020 Samuel Tardieu 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | 12 | /** 13 | Nice Flor-s remote for gates. 14 | 15 | Protocol description: 16 | The protocol has been analyzed at this link: http://phreakerclub.com/1615 17 | 18 | A packet is made of 52 bits (13 nibbles S0 to S12): 19 | 20 | - S0: button ID from 1 to 4 (or 1 to 2 depending on the remote) 21 | - S1: retransmission count starting from 1, xored with ~S0 22 | - S2 and S7-S12: 28 bit encrypted serial number 23 | - S3-S6: 16 bits encrypted rolling code 24 | */ 25 | 26 | #include "decoder.h" 27 | 28 | static int nice_flor_s_decode(r_device *decoder, bitbuffer_t *bitbuffer) 29 | { 30 | if (bitbuffer->num_rows != 2 || bitbuffer->bits_per_row[1] != 0) { 31 | return DECODE_ABORT_EARLY; 32 | } 33 | if (bitbuffer->bits_per_row[0] != 52) { 34 | return DECODE_ABORT_LENGTH; 35 | } 36 | 37 | bitbuffer_invert(bitbuffer); 38 | uint8_t *b = bitbuffer->bb[0]; 39 | 40 | uint8_t button_id = b[0] >> 4; 41 | if (button_id < 1 || button_id > 4) { 42 | return DECODE_ABORT_EARLY; 43 | } 44 | int count = 1 + (((b[0] ^ ~button_id) - 1) & 0xf); 45 | uint32_t serial = ((b[1] & 0xf0) << 20) | ((b[3] & 0xf) << 20) | 46 | (b[4] << 12) | (b[5] << 4) | (b[6] >> 4); 47 | uint16_t code = (b[1] << 12) | (b[2] << 4) | (b[3] >> 4); 48 | 49 | /* clang-format off */ 50 | data_t *data = data_make( 51 | "model", "", DATA_STRING, "Nice-FlorS", 52 | "button", "Button ID", DATA_INT, button_id, 53 | "serial", "Serial (enc.)", DATA_FORMAT, "%07x", DATA_INT, serial, 54 | "code", "Code (enc.)", DATA_FORMAT, "%04x", DATA_INT, code, 55 | "count", "", DATA_INT, count, 56 | NULL); 57 | /* clang-format on */ 58 | 59 | decoder_output_data(decoder, data); 60 | return 1; 61 | } 62 | 63 | static char *output_fields[] = { 64 | "model", 65 | "button", 66 | "serial", 67 | "code", 68 | "count", 69 | NULL, 70 | }; 71 | 72 | // Example: 73 | // $ rtl_433 -R 169 -y "{52} 0xe7a760b94372e {0}" 74 | // time : 2020-10-21 11:06:12 75 | // model : Nice Flor-s Button ID : 1 Serial (enc.): 56bc8d1 Code (enc.): 89f4 76 | // count : 6 77 | 78 | r_device nice_flor_s = { 79 | .name = "Nice Flor-s remote control for gates", 80 | .modulation = OOK_PULSE_PWM, 81 | .short_width = 500, // short pulse is ~500 us + ~1000 us gap 82 | .long_width = 1000, // long pulse is ~1000 us + ~500 us gap 83 | .sync_width = 1500, // sync pulse is ~1500 us + ~1500 us gap 84 | .gap_limit = 2000, 85 | .reset_limit = 5000, 86 | .tolerance = 100, 87 | .decode_fn = &nice_flor_s_decode, 88 | .disabled = 1, 89 | .fields = output_fields, 90 | }; 91 | -------------------------------------------------------------------------------- /src/devices/mebus.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Mebus 433. 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | */ 9 | 10 | #include "decoder.h" 11 | 12 | /** 13 | Mebus 433. 14 | 15 | @todo Documentation needed. 16 | */ 17 | static int mebus433_decode(r_device *decoder, bitbuffer_t *bitbuffer) 18 | { 19 | bitrow_t *bb = bitbuffer->bb; 20 | int16_t temp; 21 | int8_t hum; 22 | uint8_t address; 23 | uint8_t channel; 24 | uint8_t battery; 25 | uint8_t unknown1; 26 | uint8_t unknown2; 27 | data_t *data; 28 | 29 | // TODO: missing packet length validation 30 | 31 | if (bb[0][0] == 0 && bb[1][4] !=0 && (bb[1][0] & 0x60) && bb[1][3]==bb[5][3] && bb[1][4] == bb[12][4]) { 32 | 33 | address = bb[1][0] & 0x1f; 34 | 35 | channel = ((bb[1][1] & 0x30) >> 4) + 1; 36 | // Always 0? 37 | unknown1 = (bb[1][1] & 0x40) >> 6; 38 | battery = bb[1][1] & 0x80; 39 | 40 | // Upper 4 bits are stored in nibble 1, lower 8 bits are stored in nibble 2 41 | // upper 4 bits of nibble 1 are reserved for other usages. 42 | temp = (int16_t)((uint16_t)(bb[1][1] << 12) | bb[1][2] << 4); 43 | temp = temp >> 4; 44 | // lower 4 bits of nibble 3 and upper 4 bits of nibble 4 contains 45 | // humidity as decimal value 46 | hum = (bb[1][3] << 4 | bb[1][4] >> 4); 47 | 48 | // Always 0b1111? 49 | unknown2 = (bb[1][3] & 0xf0) >> 4; 50 | 51 | /* clang-format off */ 52 | data = data_make( 53 | "model", "", DATA_STRING, "Mebus-433", 54 | "id", "Address", DATA_INT, address, 55 | "channel", "Channel", DATA_INT, channel, 56 | "battery_ok", "Battery", DATA_INT, !!battery, 57 | "unknown1", "Unknown 1", DATA_INT, unknown1, 58 | "unknown2", "Unknown 2", DATA_INT, unknown2, 59 | "temperature_C", "Temperature", DATA_FORMAT, "%.02f C", DATA_DOUBLE, temp * 0.1f, 60 | "humidity", "Humidity", DATA_FORMAT, "%u %%", DATA_INT, hum, 61 | NULL); 62 | /* clang-format on */ 63 | 64 | decoder_output_data(decoder, data); 65 | return 1; 66 | } 67 | return DECODE_ABORT_EARLY; 68 | } 69 | 70 | static char *output_fields[] = { 71 | "model", 72 | "id", 73 | "channel", 74 | "battery_ok", 75 | "unknown1", 76 | "unknown2", 77 | "temperature_C", 78 | "humidity", 79 | NULL, 80 | }; 81 | 82 | r_device mebus433 = { 83 | .name = "Mebus 433", 84 | .modulation = OOK_PULSE_PPM, 85 | .short_width = 800, // guessed, no samples available 86 | .long_width = 1600, // guessed, no samples available 87 | .gap_limit = 2400, 88 | .reset_limit = 6000, 89 | .decode_fn = &mebus433_decode, 90 | .disabled = 1, // add docs, tests, false positive checks and then re-enable 91 | .fields = output_fields, 92 | }; 93 | -------------------------------------------------------------------------------- /examples/rtl_433_influxdb_relay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """InfluxDB monitoring relay for rtl_433.""" 4 | 5 | # Start rtl_433 (rtl_433 -F syslog::1433), then this script 6 | 7 | # Option: PEP 3143 - Standard daemon process library 8 | # (use Python 3.x or pip install python-daemon) 9 | # import daemon 10 | 11 | from __future__ import print_function 12 | from __future__ import with_statement 13 | 14 | from influxdb import InfluxDBClient 15 | import socket 16 | from datetime import datetime 17 | import json 18 | import sys 19 | 20 | UDP_IP = "127.0.0.1" 21 | UDP_PORT = 1433 22 | INFLUXDB_HOST = "127.0.0.1" 23 | INFLUXDB_PORT = 8086 24 | INFLUXDB_USERNAME = "" 25 | INFLUXDB_PASSWORD = "" 26 | INFLUXDB_DATABASE = "rtl433" 27 | 28 | TAGS = [ 29 | "channel", 30 | "id", 31 | ] 32 | 33 | FIELDS = [ 34 | "temperature_C", 35 | "humidity", 36 | "battery_ok", 37 | "pressure_hPa", 38 | ] 39 | 40 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) 41 | sock.bind((UDP_IP, UDP_PORT)) 42 | 43 | 44 | def sanitize(text): 45 | return text.replace(" ", "_").replace("/", "_").replace(".", "_").replace("&", "") 46 | 47 | 48 | def parse_syslog(line): 49 | """Try to extract the payload from a syslog line.""" 50 | line = line.decode("ascii") # also UTF-8 if BOM 51 | if line.startswith("<"): 52 | # fields should be "VER", timestamp, hostname, command, pid, mid, sdata, payload 53 | fields = line.split(None, 7) 54 | line = fields[-1] 55 | return line 56 | 57 | 58 | def rtl_433_probe(): 59 | client = InfluxDBClient(host=INFLUXDB_HOST, port=INFLUXDB_PORT, 60 | username=INFLUXDB_USERNAME, password=INFLUXDB_PASSWORD, 61 | database=INFLUXDB_DATABASE) 62 | 63 | while True: 64 | line, _addr = sock.recvfrom(1024) 65 | 66 | try: 67 | line = parse_syslog(line) 68 | data = json.loads(line) 69 | 70 | if not "model" in data: 71 | continue 72 | measurement = sanitize(data["model"]) 73 | 74 | tags = {} 75 | for tag in TAGS: 76 | if tag in data: 77 | tags[tag] = data[tag] 78 | 79 | fields = {} 80 | for field in FIELDS: 81 | if field in data: 82 | fields[field] = data[field] 83 | 84 | if len(fields) == 0: 85 | continue 86 | 87 | point = { 88 | "measurement": measurement, 89 | "time": datetime.now().isoformat(), 90 | "tags": tags, 91 | "fields": fields, 92 | } 93 | 94 | try: 95 | client.write_points([point]) 96 | except Exception as e: 97 | print("error {} writing {}".format(e, point), file=sys.stderr) 98 | 99 | except KeyError: 100 | pass 101 | 102 | except ValueError: 103 | pass 104 | 105 | 106 | def run(): 107 | # with daemon.DaemonContext(files_preserve=[sock]): 108 | # detach_process=True 109 | # uid 110 | # gid 111 | # working_directory 112 | rtl_433_probe() 113 | 114 | 115 | if __name__ == "__main__": 116 | run() 117 | -------------------------------------------------------------------------------- /conf/HeatmiserPRT-W.conf: -------------------------------------------------------------------------------- 1 | # Decoder for Heatmiser PRT-W thermostat with mqtt output 2 | 3 | # These are thermostats principally designed to control underfloor heating systems although they also can be used to control radiators. 4 | 5 | # The thermostat transmits to a receiver which controls physical valves to provide heat to the zone where the thermostat is. I think the receiver also has a hard wire link to the boiler to tell it to wake up and get heating when this happens, as I've detected no signal to suggest it's wireless. 6 | 7 | # The thermostats all transmit on or around 869.01Mhz 8 | 9 | # I run the conf file from the command line with: 10 | #rtl_433 -Y classic -R 0 -c /home/russ/.config/rtl_433/HeatmiserPRT-W.conf -f 869.01Mhz -M newmodel 11 | 12 | # You will need to change the prettified channel codes to match your thermostat's output. 13 | 14 | # The heatmiser's bit stream at bit no. 8 gives the receiver address for 8 bits, the next 4 bits is the on/off command and the next 4 is the thermostat ID number. The consequence of all this is that to get a meaningful unique ID we need both the receiver and the stat ID. I don't know how to concatenate the first 8bits, skip 4 and then add a further 4. To keep things simple, I just took the entire 16 bits, but this means we end up with two values for each thermostat - one for the on state and one for the off state. Thus on my system both 0x8821 and 0x8831 relate to the Living room thermostat. I then just aliased both bitwise outputs to the same text string: Living_Room. This seems to work ok, but feels like a bit of hack. 15 | # 16 | # You will also need to change the receiver location to be meaningful for your system. Mine was configured with two receivers, one on ground floor (L00) and another on the second floor (L02) 17 | # 18 | #get=@8:{16}:Stat Name:[0x8821: Living_Room 0x8831:Living_Room 0x8921:Family_Room 0x8931:Family_Room 0x8922:Kitchen 0x8932:Kitchen 0x8923:Hallway 0x8933:Hallway], 19 | 20 | 21 | # Report iso time: 22 | report_meta time:iso 23 | 24 | # Including the 'report_meta level' switch (commented out below) means information on the signal quality and strength are added to the output. This was useful to me to determine which thermostat was which without running down 2 flights or stairs every time I wanted to make a change. Basically the higher the rssi number, generally, the closer the stat was physically to my SDR aerial. 25 | 26 | #report_meta level 27 | 28 | # specify MQTT output and formatting - replace the IP address (192.168.1.150) with your mosquitto broker's IP address and port number (I didn't need the port number) 29 | output mqtt://192.168.1.150,retain=1,devices=rtl_433[/channel] 30 | 31 | decoder { 32 | name = Heatmiser_PRT-W_Thermostat, 33 | modulation = FSK_PCM, 34 | s = 416, 35 | l = 416, 36 | r = 20000, 37 | unique, 38 | bits >= 120, 39 | preamble = aaaa2dd4, 40 | get = channel:@8:{16}:[0x8821:10 0x8831:10 0x8921:20 0x8931:20 0x8922:30 0x8932:30 0x8923:40 0x8933:40], 41 | get = StatName:@8:{16}:[0x8821: Living_Room 0x8831:Living_Room 0x8921:Family_Room 0x8931:Family_Room 0x8922:Kitchen 0x8932:Kitchen 0x8923:Hallway 0x8933:Hallway], 42 | get = Receiver:@8:{8}:[0x88:L02 0x89:L00], 43 | get = StatID:@20:{4}, 44 | get = event:@16:{4}:[0x3:ON 0x2:OFF], 45 | } 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /include/pulse_detect_fsk.h: -------------------------------------------------------------------------------- 1 | /** @file 2 | Pulse detect functions, FSK pulse detector. 3 | 4 | Copyright (C) 2015 Tommy Vestermark 5 | Copyright (C) 2019 Benjamin Larsson. 6 | Copyright (C) 2022 Christian W. Zuckschwerdt 7 | 8 | This program is free software; you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation; either version 2 of the License, or 11 | (at your option) any later version. 12 | */ 13 | 14 | #ifndef INCLUDE_PULSE_DETECT_FSK_H_ 15 | #define INCLUDE_PULSE_DETECT_FSK_H_ 16 | 17 | #include "pulse_data.h" 18 | #include 19 | 20 | /// State data for pulse_detect_fsk_ functions. 21 | /// 22 | /// This should be private/opaque but the OOK pulse_detect uses this. 23 | typedef struct { 24 | unsigned int fsk_pulse_length; ///< Counter for internal FSK pulse detection 25 | enum { 26 | PD_FSK_STATE_INIT = 0, ///< Initial frequency estimation 27 | PD_FSK_STATE_FH = 1, ///< High frequency (pulse) 28 | PD_FSK_STATE_FL = 2, ///< Low frequency (gap) 29 | PD_FSK_STATE_ERROR = 3 ///< Error - stay here until cleared 30 | } fsk_state; 31 | 32 | int fm_f1_est; ///< Estimate for the F1 frequency for FSK 33 | int fm_f2_est; ///< Estimate for the F2 frequency for FSK 34 | 35 | int16_t var_test_max; 36 | int16_t var_test_min; 37 | int16_t maxx; 38 | int16_t minn; 39 | int16_t midd; 40 | int skip_samples; 41 | } pulse_detect_fsk_t; 42 | 43 | /// Init/clear Demodulate Frequency Shift Keying (FSK) state. 44 | /// 45 | /// @param s Internal state 46 | void pulse_detect_fsk_init(pulse_detect_fsk_t *s); 47 | 48 | /// Demodulate Frequency Shift Keying (FSK) sample by sample. 49 | /// 50 | /// Function is stateful between calls 51 | /// Builds estimate for initial frequency. When frequency deviates more than a 52 | /// threshold value it will determine whether the deviation is positive or negative 53 | /// to classify it as a pulse or gap. It will then transition to other state (F1 or F2) 54 | /// and build an estimate of the other frequency. It will then transition back and forth when current 55 | /// frequency is closer to other frequency estimate. 56 | /// Includes spurious suppression by coalescing pulses when pulse/gap widths are too short. 57 | /// Pulses equal higher frequency (F1) and Gaps equal lower frequency (F2) 58 | /// @param s Internal state 59 | /// @param fm_n One single sample of FM data 60 | /// @param fsk_pulses Will return a pulse_data_t structure for FSK demodulated data 61 | void pulse_detect_fsk_classic(pulse_detect_fsk_t *s, int16_t fm_n, pulse_data_t *fsk_pulses); 62 | 63 | /// Wrap up FSK modulation and store last data at End Of Package. 64 | /// 65 | /// @param s Internal state 66 | /// @param fsk_pulses Pulse_data_t structure for FSK demodulated data 67 | void pulse_detect_fsk_wrap_up(pulse_detect_fsk_t *s, pulse_data_t *fsk_pulses); 68 | 69 | /// Demodulate Frequency Shift Keying (FSK) sample by sample. 70 | /// 71 | /// Function is stateful between calls 72 | /// @param s Internal state 73 | /// @param fm_n One single sample of FM data 74 | /// @param fsk_pulses Will return a pulse_data_t structure for FSK demodulated data 75 | void pulse_detect_fsk_minmax(pulse_detect_fsk_t *s, int16_t fm_n, pulse_data_t *fsk_pulses); 76 | 77 | #endif /* INCLUDE_PULSE_DETECT_FSK_H_ */ 78 | -------------------------------------------------------------------------------- /src/devices/newkaku.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Kaku decoder. 3 | */ 4 | /** 5 | Kaku decoder. 6 | Might be similar to an x1527. 7 | S.a. Nexa, Proove. 8 | 9 | Two bits map to 2 states, 0 1 -> 0 and 1 0 -> 1 10 | Status bit can be 1 1 -> 1 which indicates DIM value. 4 extra bits are present with value 11 | start pulse: 1T high, 10.44T low 12 | - 26 bit: Address 13 | - 1 bit: group bit 14 | - 1 bit: Status bit on/off/[dim] 15 | - 4 bit: unit 16 | - [4 bit: dim level. Present if [dim] is used, but might be present anyway...] 17 | - stop pulse: 1T high, 40T low 18 | */ 19 | 20 | #include "decoder.h" 21 | 22 | static int newkaku_callback(r_device *decoder, bitbuffer_t *bitbuffer) 23 | { 24 | uint8_t *b = bitbuffer->bb[0]; 25 | 26 | if (b[0] != 0x65 && b[0] != 0x59) // always starts with 0110 0101 or 0101 1001 27 | return DECODE_ABORT_EARLY; 28 | 29 | /* Reject missing sync */ 30 | if (bitbuffer->syncs_before_row[0] != 1) 31 | return DECODE_ABORT_EARLY; 32 | 33 | /* Reject codes of wrong length */ 34 | if (bitbuffer->bits_per_row[0] != 64 && bitbuffer->bits_per_row[0] != 72) 35 | return DECODE_ABORT_LENGTH; 36 | 37 | // 11 for command indicates DIM, 4 extra bits indicate DIM value 38 | uint8_t dim_cmd = (b[6] & 0x03) == 0x03; 39 | if (dim_cmd) { 40 | b[6] &= 0xfe; // change DIM to ON to use Manchester 41 | } 42 | 43 | bitbuffer_t databits = {0}; 44 | // note: not manchester encoded but actually ternary 45 | unsigned pos = bitbuffer_manchester_decode(bitbuffer, 0, 0, &databits, 80); 46 | bitbuffer_invert(&databits); 47 | 48 | /* Reject codes when Manchester decoding fails */ 49 | if (pos != 64 && pos != 72) 50 | return DECODE_ABORT_LENGTH; 51 | 52 | b = databits.bb[0]; 53 | 54 | uint32_t id = (b[0] << 18) | (b[1] << 10) | (b[2] << 2) | (b[3] >> 6); // ID 26 bits 55 | uint32_t group_cmd = (b[3] >> 5) & 1; 56 | uint32_t on_bit = (b[3] >> 4) & 1; 57 | uint32_t unit = (b[3] & 0x0f); 58 | uint32_t dv = (b[4] >> 4); 59 | 60 | /* clang-format off */ 61 | data_t *data = data_make( 62 | "model", "", DATA_STRING, "KlikAanKlikUit-Switch", 63 | "id", "", DATA_INT, id, 64 | "unit", "Unit", DATA_INT, unit, 65 | "group_call", "Group Call", DATA_STRING, group_cmd ? "Yes" : "No", 66 | "command", "Command", DATA_STRING, on_bit ? "On" : "Off", 67 | "dim", "Dim", DATA_STRING, dim_cmd ? "Yes" : "No", 68 | "dim_value", "Dim Value", DATA_INT, dv, 69 | NULL); 70 | /* clang-format on */ 71 | 72 | decoder_output_data(decoder, data); 73 | return 1; 74 | } 75 | 76 | static char *output_fields[] = { 77 | "model", 78 | "id", 79 | "unit", 80 | "group_call", 81 | "command", 82 | "dim", 83 | "dim_value", 84 | NULL, 85 | }; 86 | 87 | r_device newkaku = { 88 | .name = "KlikAanKlikUit Wireless Switch", 89 | .modulation = OOK_PULSE_PPM, 90 | .short_width = 300, // 1:1 91 | .long_width = 1400, // 1:5 92 | .sync_width = 2650, // 1:10, tuned to widely match 2450 to 2850 93 | .tolerance = 200, 94 | .reset_limit = 3200, 95 | .decode_fn = &newkaku_callback, 96 | .fields = output_fields, 97 | }; 98 | -------------------------------------------------------------------------------- /src/devices/eurochron.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | Eurochron temperature and humidity sensor. 3 | 4 | Copyright (c) 2019 by Oliver Weyhmüller 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | /** 12 | Eurochron temperature and humidity sensor. 13 | 14 | Datagram format: 15 | 16 | IIIIIIII B00P0000 HHHHHHHH TTTTTTTT TTTT 17 | 18 | - I: ID (new ID will be generated at battery change!) 19 | - B: Battery low 20 | - P: TX-Button pressed 21 | - H: Humidity (%) 22 | - T: Temperature (°C10) 23 | - 0: Unknown / always zero 24 | 25 | Device type identification is only possible by datagram length 26 | and some zero bits. Therefore this device is disabled 27 | by default (as it could easily trigger false alarms). 28 | 29 | Observed update intervals: 30 | - transmission time slot every 12 seconds 31 | - at least once within 120 seconds (with stable values) 32 | - down to 12 seconds (with rapidly changing values) 33 | */ 34 | 35 | #include "decoder.h" 36 | 37 | static int eurochron_decode(r_device *decoder, bitbuffer_t *bitbuffer) 38 | { 39 | data_t *data; 40 | int row; 41 | uint8_t *b; 42 | int temp_raw, humidity, device, battery_low, button; 43 | float temp_c; 44 | 45 | /* Validation checks */ 46 | row = bitbuffer_find_repeated_row(bitbuffer, 3, 36); 47 | 48 | if (row < 0) // repeated rows? 49 | return DECODE_ABORT_EARLY; 50 | 51 | if (bitbuffer->bits_per_row[row] > 36) // 36 bits per row? 52 | return DECODE_ABORT_LENGTH; 53 | 54 | b = bitbuffer->bb[row]; 55 | 56 | if (b[1] & 0x0F) // is lower nibble of second byte zero? 57 | return DECODE_FAIL_SANITY; 58 | 59 | /* Extract data */ 60 | device = b[0]; 61 | 62 | temp_raw = (int16_t)((b[3] << 8) | (b[4] & 0xf0)); 63 | temp_c = (temp_raw >> 4) * 0.1f; 64 | 65 | humidity = b[2]; 66 | 67 | battery_low = b[1] >> 7; 68 | 69 | button = (b[1] & 0x10) >> 4; 70 | 71 | /* clang-format off */ 72 | data = data_make( 73 | "model", "", DATA_STRING, "Eurochron-TH", 74 | "id", "", DATA_INT, device, 75 | "battery_ok", "Battery", DATA_INT, !battery_low, 76 | "temperature_C", "Temperature", DATA_FORMAT, "%.01f C", DATA_DOUBLE, temp_c, 77 | "humidity", "Humidity", DATA_INT, humidity, 78 | "button", "Button", DATA_INT, button, 79 | NULL); 80 | /* clang-format on */ 81 | 82 | decoder_output_data(decoder, data); 83 | return 1; 84 | } 85 | 86 | static char *output_fields[] = { 87 | "model", 88 | "id", 89 | "battery_ok", 90 | "temperature_C", 91 | "humidity", 92 | "button", 93 | NULL, 94 | }; 95 | 96 | r_device eurochron = { 97 | .name = "Eurochron temperature and humidity sensor", 98 | .modulation = OOK_PULSE_PPM, 99 | .short_width = 1016, 100 | .long_width = 2024, 101 | .gap_limit = 2100, 102 | .reset_limit = 8200, 103 | .decode_fn = &eurochron_decode, 104 | .disabled = 1, 105 | .fields = output_fields, 106 | }; 107 | -------------------------------------------------------------------------------- /src/devices/tfa_pool_thermometer.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | TFA pool temperature sensor. 3 | 4 | Copyright (C) 2015 Alexandre Coffignal 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | /** 12 | TFA pool temperature sensor. 13 | 14 | 10 24 bits frames 15 | 16 | CCCCIIII IIIITTTT TTTTTTTT DDBF 17 | 18 | - C: checksum, sum of nibbles - 1 19 | - I: device id (changing only after reset) 20 | - T: temperature 21 | - D: channel number 22 | - B: battery status 23 | - F: first transmission 24 | */ 25 | 26 | #include "decoder.h" 27 | 28 | static int tfa_pool_thermometer_decode(r_device *decoder, bitbuffer_t *bitbuffer) 29 | { 30 | data_t *data; 31 | uint8_t *b; 32 | int checksum, checksum_rx, device, channel, battery; 33 | int temp_raw; 34 | float temp_f; 35 | 36 | // require 7 of 10 repeats 37 | int row = bitbuffer_find_repeated_row(bitbuffer, 7, 28); 38 | if (row < 0) { 39 | return DECODE_ABORT_EARLY; // no repeated row found 40 | } 41 | if (bitbuffer->bits_per_row[row] != 28) { 42 | return DECODE_ABORT_LENGTH; // prevent false positives 43 | } 44 | 45 | b = bitbuffer->bb[row]; 46 | 47 | checksum_rx = ((b[0] & 0xF0) >> 4); 48 | checksum = ((b[0] & 0x0F) + 49 | (b[1] >> 4) + 50 | (b[1] & 0x0F) + 51 | (b[2] >> 4) + 52 | (b[2] & 0x0F) + 53 | (b[3] >> 4) - 1); 54 | 55 | if (checksum_rx != (checksum & 0x0F)) { 56 | decoder_logf_bitrow(decoder, 2, __func__, b, bitbuffer->bits_per_row[row], "checksum fail (%02x)", checksum); 57 | return DECODE_FAIL_MIC; 58 | } 59 | 60 | device = ((b[0] & 0x0F) << 4) + ((b[1] & 0xF0) >> 4); 61 | temp_raw = ((b[1] & 0x0F) << 8) + b[2]; 62 | temp_f = (temp_raw > 2048 ? temp_raw - 4096 : temp_raw) * 0.1f; 63 | channel = ((b[3] & 0xC0) >> 6); 64 | battery = ((b[3] & 0x20) >> 5); 65 | 66 | /* clang-format off */ 67 | data = data_make( 68 | "model", "", DATA_STRING, "TFA-Pool", 69 | "id", "Id", DATA_INT, device, 70 | "channel", "Channel", DATA_INT, channel, 71 | "battery_ok", "Battery", DATA_INT, battery, 72 | "temperature_C", "Temperature", DATA_FORMAT, "%.01f C", DATA_DOUBLE, temp_f, 73 | "mic", "Integrity", DATA_STRING, "CHECKSUM", 74 | NULL); 75 | /* clang-format on */ 76 | 77 | decoder_output_data(decoder, data); 78 | return 1; 79 | } 80 | 81 | static char *output_fields[] = { 82 | "model", 83 | "id", 84 | "channel", 85 | "battery_ok", 86 | "temperature_C", 87 | "mic", 88 | NULL, 89 | }; 90 | 91 | r_device tfa_pool_thermometer = { 92 | .name = "TFA pool temperature sensor", 93 | .modulation = OOK_PULSE_PPM, 94 | .short_width = 2000, 95 | .long_width = 4600, 96 | .gap_limit = 7800, 97 | .reset_limit = 10000, 98 | .decode_fn = &tfa_pool_thermometer_decode, 99 | .fields = output_fields, 100 | }; 101 | -------------------------------------------------------------------------------- /src/devices/ibis_beacon.c: -------------------------------------------------------------------------------- 1 | /** @file 2 | IBIS vehicle information beacon. 3 | 4 | Copyright (C) 2017 Christian W. Zuckschwerdt 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | */ 11 | /** 12 | IBIS vehicle information beacon. 13 | (used in public transportation) 14 | 15 | The packet is 28 manchester encoded bytes with a Preamble of 0xAAB and 16 | 16-bit CRC, containing a company ID, vehicle ID, (door opening) counter, 17 | and various flags. 18 | 19 | */ 20 | 21 | #include "decoder.h" 22 | 23 | static int ibis_beacon_callback(r_device *decoder, bitbuffer_t *bitbuffer) 24 | { 25 | data_t *data; 26 | uint8_t search = 0xAB; // preamble is 0xAAB 27 | uint8_t msg[32]; 28 | unsigned len; 29 | unsigned pos; 30 | unsigned i; 31 | int id; 32 | unsigned counter; 33 | int crc; 34 | int crc_calculated; 35 | char code_str[63]; 36 | 37 | // 224 bits data + 12 bits preamble 38 | if (bitbuffer->num_rows != 1 || bitbuffer->bits_per_row[0] < 232 || bitbuffer->bits_per_row[0] > 250) { 39 | return DECODE_ABORT_LENGTH; // Unrecognized data 40 | } 41 | 42 | pos = bitbuffer_search(bitbuffer, 0, 0, &search, 8); 43 | if (pos > 26) { 44 | return DECODE_ABORT_EARLY; // short buffer or preamble not found 45 | } 46 | pos += 8; // skip preamble 47 | len = bitbuffer->bits_per_row[0] - pos; 48 | // we want 28 bytes (224 bits) 49 | if (len < 224) { 50 | return DECODE_ABORT_LENGTH; // short buffer 51 | } 52 | len = 224; // cut the last pulse 53 | 54 | bitbuffer_extract_bytes(bitbuffer, 0, pos, (uint8_t *)&msg, len); 55 | 56 | crc_calculated = crc16(msg, 26, 0x8005, 0x0000); 57 | crc = (msg[26] << 8) | msg[27]; 58 | if (crc != crc_calculated) { 59 | return DECODE_FAIL_MIC; // bad crc 60 | } 61 | 62 | id = ((msg[5]&0x0f) << 12) | (msg[6] << 4) | ((msg[7]&0xf0) >> 4); 63 | counter = ((unsigned)msg[20] << 24) | (msg[21] << 16) | (msg[22] << 8) | msg[23]; 64 | 65 | for (i=0; i<(len+7)/8 ; ++i) { 66 | sprintf(&code_str[i*2], "%02x", msg[i]); 67 | } 68 | 69 | /* clang-format off */ 70 | data = data_make( 71 | "model", "", DATA_STRING, "IBIS-Beacon", 72 | "id", "Vehicle No.", DATA_INT, id, 73 | "counter", "Counter", DATA_INT, counter, 74 | "code", "Code data", DATA_STRING, code_str, 75 | "mic", "Integrity", DATA_STRING, "CRC", 76 | NULL); 77 | /* clang-format on */ 78 | 79 | decoder_output_data(decoder, data); 80 | return 1; 81 | } 82 | 83 | static char *output_fields[] = { 84 | "model", 85 | "id", 86 | "counter", 87 | "code", 88 | "mic", 89 | NULL, 90 | }; 91 | 92 | r_device ibis_beacon = { 93 | .name = "IBIS beacon", 94 | .modulation = OOK_PULSE_MANCHESTER_ZEROBIT, 95 | .short_width = 30, // Nominal width of clock half period [us] 96 | .long_width = 0, // Not used 97 | .reset_limit = 100, // Maximum gap size before End Of Message [us]. 98 | .decode_fn = &ibis_beacon_callback, 99 | .fields = output_fields, 100 | }; 101 | --------------------------------------------------------------------------------