Making a REST API for your Doorbell


For a while I've been wanting to do a project around something wireless based, but it seems like it's something that's quite difficult to get in to. For one I didn't have any tools that would really allow me to inspect anything, how do you go about reverse engineering something wireless.

I had a couple of projects in mind that I wanted to try out:

Hacking my doorbell Getting it to post to a Slack channel whenever someone rang the doorbell.

LED Strip control I currently have two LED strips, one behind my monitor/under my desk. Another that is in the lounge behind the TV. I wanted to turn these off automatically and also change the colours via software.

So I figured the easiest one to tackle first would be the doorbell, now I'm going to cover just the first part of this which is reverse engineering the code that the doorbell sends to the receivers and then replaying it programatically using a cheap 433Mhz transmitter.

Part 1 - Initial investigation

So the first step we need to make is that we need to work out what exactly is being sent out from our doorbell when we press it, fortunately when I bought the doorbell set I had two recievers and two bells. So it was easy to just have a doorbell close by and observe the signal whenever the doorbell was pressed.

So taking the doorbell apart it was easy to see just from the PCB that this was using 433Mhz given that it was labelled "Touch Tx 433".

But if yours doesn't then you may need to scour the PCB looking for the chip and look this up online. On this doorbell it also had a chip labelled R433, so that's a pretty telling sign it was using 433Mhz.

So my first attempt at reverse engineering this was to buy some cheap 433Mhz transmitters and receivers.

These are really simple chips where you have 5V, Data and Ground pins. So I hooked one of the receivers up to a 5V power source and attached my oscillioscope to the data pin to see what I was getting out of it when I pressed the doorbell button.

This was quite an arduous process based on the fact that I needed to try to capture the signal at the point it was sent, however creating a trigger around this was difficult because these receivers have automatic gain control. So when not presented with a strong signal you get a lot of background noise which would set off the trigger. I eventually landed on just pressing the button and pressing the stop button on my scope...

This allowed me to capture the signal but I found it pretty difficult to read the symbols. Though admittedly, this is just my inexperience using a scope. It completely would have been possible to reverse engineer this without other tools using the same process further down in this article.

Really I wanted a simpler way to capture this, preferably with a reciever directly attached to my computer so I could feed this information into tools and process it.

Part 2 - Initial investigation (with a lot less effort)

So I ended up buying a HackRF, I'd read about them a couple of years ago and I wanted one then, but never really had any reason to have one. This seemed like the perfect excuse. I managed to pick one up on Amazon for around £260 which included the ANT500 antenna and some SMA antenna adapters.

I then went about watching some of the videos on the HackRF website which provide a really good introduction.

So lets try to reverse engineer this, first of all what we can do is do a really quick test to see how simple the protocol is, you'd think something this simple it would just send a single message to indicate to the receiver that the button has been pressed and that message wouldn't change each time. There's no security involved with it really so you would assume it would be susceptible to a simple replay based attack.

The HackRF utilities on Debian come with some utilities that just allow you to receive data into a file, and then replay that exact data. Just doing this will tell you if the protocol can just be replayed without any additional investigation.

So lets start by pulling down the HackRF tools:

1
sudo apt install hackrf

This will give us hackrf_transfer, the main options we're concerned about are these:

1
2
3
4
5
6
7
8
NAME
   hackrf_transfer - file based transmit and receive sdr

OPTIONS
   -r <filename> # Receive data into file.
   -t <filename> # Transmit data from file.
   [-f set_freq_hz] # Set Freq in Hz
   [-x gain_db] # TX VGA (IF) gain, 0-47dB, 1dB steps

So now we can simply capture our signal at 433Mhz and then replay it back, lets start by capturing some data. We simply run the following, then press the doorbell as it's capturing.

1
sudo hackrf_transfer -t doorbell.dat -f 433e6

Once we've captured this data we can then attempt to play it back:

1
sudo hackrf_transfer -r doorbell.dat -f 433e6 -x 20

Here we've just added the -x flag to increase the TX gain.

If you're doorbell rings at this point, then that's good news, you can simply replay the signal.

Technically this works, we can just do this to ring our doorbell but we have a few problems. Namely we've just purchased a £260 device to ring a doorbell...

So really we want to be able to replay this data programatically on a cheaper device, probably once of the cheap RF transmitters we bought for pennies.

Which brings us onto our next stage, decoding the signal.

Part 3 - Decoding the signal

Now we need to start looking at our signal and trying to work out what how the data is being sent, looking at the modulation and what that data contains.

The first thing to do would be to use part of gnuradio to capture some data for us to look at, for this we're going to use osmocom_fft which you can get via the gr-osmosdr package.

1
sudo osmocom_fft -f 433e6 -W

The flags we're passing here just set our frequency and sets our visualization to waterfall, though this isn't really required I find it a little easier to see what kind of modulation is being used.

From this we will get something like the following when we press our doorbell:

From this output we can immediately tell a couple of things:

  • The signal is around 433.808MHz
  • It doesn't vary in frequency

This is an indication that we're looking most likely at ASK modulation given the simplicity of the circuit.

You can also see some sections here where there's blocks of signal followed by silence, which means we're most likely looking at OOK, but we'll be able to see this more clearly once we start digging into the data.

You should at the bottom of the FFT dialog box see that you can record the data. What we want to do at this stage is to start recording, hit our doorbell, then save the file.

In the stdout of the osmocom_fft software you'll get a cfile dumped in /tmp. Next we need to open this up in another application called inspectrum.

Unfortunately the version in Debian is a bit old, so what you're most likely going to want to do is grab the source for inspectrum and compile the latest version yourself. The main advantage to this is you get the cursor options which will help you work out symbol timing. By the time you're reading this the version in Debian may have been updated, check if your version already has the cursor options. If it doesn't then follow the instructions on the Inspectrum Github page to compile:

Once you have the latest version open up your capture file with it:

This allows you to view the full dump of your signal data, where time is on the X axis, and the frequency is on the Y axis. 0MHz here relates to our 433MHz and plus an minus values are relative to that central frequency.

Looking at this it's very obvious we're looking at ASK-OOK and we can start to see a pattern. In this case we have the same message repeated 25 times. So we effectively have a pattern we can replicate easily using our cheap transmitter!

The next stage is working out our timing and symbols so we can replicate this data. Initially looking at this you would think perhaps that presence of a signal means 1 and absence of a signal for the same period means 0. However if you look closely at the signal you can see there's actually repeated symbols which denote a 1 or a zero.

Within inspectrum you can add a plot on top of your data by right clicking on the main view and selecting "Add derived plot > Add amplitude plot". You then drag the red line over the strongest part of your signal. You can also adjust the "Power Max" slider which will filter out some of your data showing a clear TTL plot.

In the bottom image I've tried to highlight two different symbols we can see.

  • Red Short burst, followed by a long pause
  • Blue Long burst, followed by a short pause

Though it doesn't really matter what these symbols mean for the basis of recreating it, I'm going to assume that our short burst is binary 0 and our long burst is binary 1.

The other bit of information we need here is the timing metrics, we need to know how long each burst and pause is for. Fortunately inspectrum has a cursor option which allows us to measure the timing of various parts of our graph.

There's a little estimation involved but you need to work out the period for each part of the signal, for my doorbell it was the following:

Type Period
Short Pulse 200us
Short Delay 650us
Long Pulse 680us
Long Delay 240us
Repeat Delay 5880us

We also need to know how many times the signal repeated, looking at the whole communication it was repeated 25 times.

We're now most of the way towards having all of the data needed to be able to reproduce the signal accurately.

Lastly we need the actual whole sequence, this was mainly a manual process of eyeballing the data and writing down the sequence. Unfortuantely since the data isn't completely uniform it's not possible to use Inspectrums cursor symbol feature.

After making the assumption that our short pulse is binary 0 and long pulse is binary 1 we can write down the sequence as the following:

1
0b0011001101111110000000110

Now we have everything we need to recreate this signal, so lets move on to interacting with our 433MHz transmitter.

Part 4 - Recreating the signal

Usually I would opt for a microcontroller here to recreate the signal but we mostly want to interact with our doorbell via an API. Instead I opted for interfacing the transmitter with a Raspberry Pi. This has the benefit that we can interact with it directly using the Pi's GPIO pins to bit-bang the data out.

Additionally we can run our REST API on the same hardware calling the underlying code to ring the doorbell.

My initial attempt was to use Python to perform this task, effectively using the Pi's GPIO module to turn the inputs on and off.

The hardware setup is basically to connect your 5V to the Pi's 5V outputs, GND to a GND pin and your data pin to GPIO 0.

I additionally made a small antenna for the transmitter based on the following: http://www.instructables.com/id/433-MHz-Coil-loaded-antenna/

Then to send data it's a simple case of turning your GPIO on and off with the timings you obtained in Part 3.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import time
import sys
import RPi.GPIO as GPIO

data = "0011001101111110000000110"

short_pulse = 0.000200
long_pulse = 0.000600

long_pulse_delay = 0.000240
short_pulse_delay = 0.000650
repeat_delay = 0.00588

TRANSMIT_PIN = 0
REPEAT_COUNT = 25

GPIO.setmode(GPIO.BCM)
GPIO.setup(TRANSMIT_PIN, GPIO.OUT)

count = 0
while count <= REPEAT_COUNT:
  for i in data:
    if i == '1':
      GPIO.output(TRANSMIT_PIN, 1)
      time.sleep(long_pulse)
      GPIO.output(TRANSMIT_PIN, 0)
      time.sleep(long_pulse_delay)
    else:
      GPIO.output(TRANSMIT_PIN, 1)
      time.sleep(short_pulse)
      GPIO.output(TRANSMIT_PIN, 0)
      time.sleep(short_pulse_delay)

  GPIO.output(TRANSMIT_PIN, 0)
  time.sleep(repeat_delay)

  count += 1

GPIO.cleanup()

This first attempt yielded some interesting results, sometimes it worked, and sometimes it didn't. Fortunately since I'm actually creating the signal with the 433 transmitter I can now debug this signal from the HackRF and work out what's going wrong when it doesn't trigger.

This shows two messages that were sent, what's most interesting is the first message. It shows clearly that part of the message is incorrect, highlighted in red. Our script has failed to keep within the timing requirements and has increased the length of time that the GPIO pin is held high. This is a fairly exteme example but obviously the timing of all our messages were incorrect because the bell never rang.

However, that's the nature of running userland software on top of an operating system running multiple tasks. You can't be guaranteed of the timing being correct when other applications also demand CPU time. This is further exacerbated using Python where you have various things happening in the background like GC. It was easy to make this worse by simply running something else on the Pi while the script was being run (apt get update, for example).

So we need to get lower down, we can't get the strict timing requirements from Python and we need something that can give us that. This article gives a really good benchmark of a few options available to you for the Pi.

From this it seemed the best option would be to go for something in C. WiringPi is a GPIO access library which makes things a little easier and also offers us some delay functions as a convenience so lets re-implement our Python code in C.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <wiringPi.h>

#define OUTPUT_PIN 0

#define SHORT_PULSE 200
#define LONG_PULSE 680

#define SHORT_PULSE_DELAY 650
#define LONG_PULSE_DELAY 240
#define REPEAT_DELAY 5880

#define REPEAT 25
const int code[] = {0,0,1,1,0,0,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,0};

int main (void)
{
  wiringPiSetup ();
  pinMode (OUTPUT_PIN, OUTPUT);

  for(int count = 0; count <= REPEAT; count++) {

    for(int i = 0; i < sizeof(code)/sizeof(code[0]); i++) {
      if (code[i] == 1) {
         digitalWrite(OUTPUT_PIN, 1);
         delayMicroseconds(LONG_PULSE);
         digitalWrite(OUTPUT_PIN, 0);
         delayMicroseconds(LONG_PULSE_DELAY);
      } else {
         digitalWrite(OUTPUT_PIN, 1);
         delayMicroseconds(SHORT_PULSE);
         digitalWrite(OUTPUT_PIN, 0);
         delayMicroseconds(SHORT_PULSE_DELAY);
      }
    }

    delayMicroseconds(REPEAT_DELAY);
  }

    return 0 ;
}

Here the same logic applies, we're looping through our binary 1's and 0's and we're recreating our symbols as the doorbell did. This yielded perfect results, the doorbell would chime every time.

Lets just make sure we have the libraries, and compile that all, and then copy the binary to /usr/bin/ringdoorbell.

1
2
3
apt install wiringpi
gcc -Wall -l wiringPi main.c -std=c99
mv a.out /usr/bin/ringdoorbell

At this stage we've reverse engineered our doorbell code, but that's not what this article was all about... We want to add some useless crap on top, so lets go about making a REST API for our Doorbell.

Part 4 - REST API

I'm using the term REST API here pretty unfairly, what I really mean is when we POST to a URI lets get the doorbell to ring... I'm not looking for anything special here (not even authentication, relax, it's internal on my network) so it seems like Flask is the easiest option.

I'm really lazy so I'm not even going to make a virtualenv on my Pi, just install pip and install Flask globally.

1
2
apt install python-pip
pip install Flask

Create main.py with the following contents, all this really does is call the binary ringdoorbell when you POST to /ring.

1
2
3
4
5
6
7
8
9
import subprocess
from flask import Flask, jsonify
app = Flask(__name__)


@app.route("/ring", methods=['POST'])
def ring():
    subprocess.call(['ringdoorbell'])
    return jsonify({'success': 'true'})

And lets run that now:

1
2
export FLASK_APP=main.py
flask run -h 0.0.0.0 -p 80

Now, when you make a POST request to /ring, your doorbell should ring! I'm using httpie here but cURL would be just as suitable.

1
http POST 192.168.1.164/ring

From here you can do whatever you want with it, maybe you want to make a Slack bot where users can ring your doorbell.

Part 5 - What Next

Well understandibly this is quite a useless project by itself but it gave me a basic understanding of reverse engineering RF signals. And it also leads me on to the real part of the project which will be to listen out for these signals and then do something when that signal is recieved.

That would allow me to set up some automation, for example:

  • Text message saying someone has rung the doorbell
  • Sending a slack message
  • Pausing Kodi on my HTPC
  • Displaying a message on my Desktop to say that someone is at the door

Comments