Doorbell Hacking

Recently I bought my first house, and that means no more silly tenancy agreement rules, e.g. no picture hooks on the walls or screwing with the wiring, aka ultimate smart home freedom :-D. To complement my CurrentCost SDR logging and Temperature & Humidity sensors I decided it was time to buy a cheap hackable doorbell, the general idea is on each doorbell press I’d capture the date, time and a quick photo of who was at the door.

To get started I checked out what sort of doorbells rtl_433 could intercept and hunted out a compatible door bell and cheap outdoor wireless IP camera from Amazon. In the end I settled for an Elro DB286A Doorbell and Knewmart IP Camera. Once they arrived I got to work configuring the IP camera and placing it in a suitable location on my porch so I could see who was pressing the door bell and stuck the doorbell’s push button to the door frame, the doorbell communicates wirelessly over 433mhz and is ran on batteries so no cabling there, I had to drill a few small holes for the power cable for the IP camera, but the installation was pretty tidy and the camera is somewhat obscured by the guttering making it less obvious.

Next the fun could begin. I opened rtl_433 to checkout the packets sent from the doorbell:

$ rtl_433 -F json
{"time" : "2017-08-06 19:59:44", "model" : "Elro DB286A", "code" : "1d7c2a6c80"}

Now I had the doorbell’s unique ID I could extend my existing 433mhz ingestion script to raise a HTTP request to my home automation system every time the doorbell is pressed as well as continuing to log CurrentCost data to my Graphite server. Using the code from the rtl_433 payload is particularly useful for identifying your doorbell from your neighbour’s devices. In the end I settled for something like the following:

#!/usr/bin/env python3

import subprocess
import threading
import json
import time
import requests
import graphyte

# Configuration values.
currentcost_id = 1636
doorbell_id = '1d7c2a6c80'
rtl_433_cmd = '/usr/local/bin/rtl_433 -F json'
graphite_server = '172.20.1.189'
home_automation_url = 'http://172.20.1.189:5000/api/doorbell'
home_automation_api_key = 'blablabla'

# No further user configurable stuff after this line.
graphyte.init(graphite_server, prefix='sensors.%s' % currentcost_id)


def shovel_results():
    for line in iter(p.stdout.readline, b''):
        try:
            data = json.loads(line)
        except:
            data = None

        if not data:
            print("Data from RTL433 is invalid")
        else:
            try:

                if data.get('dev_id', None) == currentcost_id:
                    dev_id = data['dev_id']
                    power = data['power0']
                    print("Received valid packet from CurrentCost, power usage: %s watts"
                           % power)
                    graphyte.send('power', power)

                elif data.get('code', None) == doorbell_id:
                    print("Received valid packet from doorbell.")
                    headers = {"authorization": home_automation_api_key}
                    requests.get(home_automation_url, headers=headers)

                else:
                    print("Packet received from some other device: %s" % data)

            except:
                print("Received packet from non-compliant device")


p = subprocess.Popen(rtl_433_cmd.split(),
                     stdout=subprocess.PIPE,
                     stderr=subprocess.STDOUT,
                     universal_newlines=True)

t = threading.Thread(target=shovel_results)
t.start()


try:
    while True:
        time.sleep(1)
        if p.poll() is not None:
            break

finally:
    p.terminate()

The updated script still sends my CurrentCost power usage data into Graphite, and any door bell pushes raise a GET request to my home automation system. As the home automation system is written in Flask I continued to add route to my API blueprint to handle the doorbell pushes. Every time this route receives an API key authenticated request it will take a snapshot of the IP camera, base64 encode the image and dump it into the DB as a blob which can then be retrieved in the automation systems UI. Later on my intentions is to add a callback to some notifications queue to send a notification to our telephones to let us know somebody is at the door. Lets have a look at the route, yep it’s bad as the camera credentials and URL is hard coded, but it’ll do the job for now:

@API_BP.route('/api/doorbell')
def post_doorbell():
    try:
        auth_header = request.headers.get('Authorization', None)

        if not auth_header:
            return "Unauthorized", 401

        if not check_api_key_validity(auth_header):
            return "Unauthorized", 401

        door_image_resp = requests.get('http://172.20.1.66/web/tmpfs/snap.jpg',
                                      auth=('admin','admin'))
        content = base64.b64encode(door_image_resp.content)

        reading = Sensor_Reading(sensor_id=5,
                                 received_at=datetime.datetime.now(),
                                 value=content)
        g.db.add(reading)
        g.db.commit()
        return "OK", 200

    except:
        return "Invalid Request", 400

Lets test it by pressing the doorbell and check out what is in the sensor_readings db table, as you can see it dumps out a large base64 payload in the value column with our complete image, probably not the best way to store the image but it’ll do for now. Long term it would be good to upload the image to something like Amazon S3 or Rackspace Cloud Files and just store the image’s URL in the DB:

MariaDB [homeapp]> select * from sensor_reading where sensor_id=5 LIMIT 1 \G;
*************************** 1. row ***************************
sensor_reading_id: 5889
        sensor_id: 5
      received_at: 2017-08-06 20:52:10
            value: /9j/4AAQSkZJRgABAQAAAQABAAD/2wDFAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaH....REST OF BASE64 ENCODED IMAGE HERE....qNJ4UENwhG5R901o/2hJAh2gEHswzUTk+gvQ//9k=

This is great, but reading doorbell activations from the DB using the MySQL client is not very friendly so I added a HTML template to the UI of my home automation system to display doorbell events. It simply reads the DB for the last 10 activations and dumps them out to a page, later I should probably add pagination and a date / time filter so I can search historic doorbell pushes.

Notice there is nobody at the door? Well I had already locked up for the night when I was writing the code for this so I opted to simulate a doorbell press each time I wanted to check out if it was working by manually sending a request to the home automation API, hence the human-less photos. I appreciate I have been particularly vague about my home automation system here, but its just a Flask app with some SQLAlchemy models for storing events in a MySQL DB and a Graphite server for storing time series data, it’s pretty rough around the edges so I am not keen to release it on Github yet, but maybe later after some more development. I hope you enjoyed the post and maybe I have inspired you to hack your own doorbell :-).

By @Robert Putt in
Tags : #internet, #iot, #electronics, #technology,