Saturday, 2 November 2019

Smart House Alarm with a Pi: The Dumb Doorbell

For some reason last year I decided it would be a good idea to build a smart home alarm system using a Raspberry Pi, part of the reason for this was the ease of integration into my existing smart lights but also because it would allow me to learn more about electronics. As a result, this is my venture into the world of Raspberry Pi's, electronics, GPIOs and software programming, I will of course, also include a nice sprinkling of frustration, name calling and hopefully some nice flashy lights.
The first stop on this not so magical tour is the humble doorbell, I'm not going to add cameras or intercoms yet as this is a planned improvement for the future, plus it will serve as a good learning curve for me.
I should note that I currently have 4 Raspberry Pi's in use around my home with a Pi Zero being the latest addition, I'm also currently using a recent version of openhab (https://www.openhab.org) that is setup to control my lights.
The basic doorbell is just a push button switch making my first project relatively easy to achieve, to start with, I created the following simple circuit using falstad’s circuit simulator (https://www.falstad.com/circuit/) that would pull the GPIO high. This means that when the switch is closed (doorbell is pressed) the GPIO pin is presented with a voltage of more than 2 volts, thus causing the pin to read a high state.

Note: The pin numbers listed in the image above are physical board numbers
Next I wired up the circuit on a breadboard and connected a spare Raspberry Pi that I had laying about, I then set about checking that I could actually read the GPIO state using these simple bash commands.


echo "17" > /sys/class/gpio/export                  
echo "in" > /sys/class/gpio/gpio17/direction
cat /sys/class/gpio/gpio17/value
The commands listed export the GPIO pin to be in a read state, in this case pin 17 (I use BCM numbering for all source code), then setting the direction in or out (read or write respectively) and finally by reading the value using the cat command.
Once reading of the pin state had been verified and after checking that I was able to detect the pin did in fact change state, I set about writing the following python script which waits for a specific GPIO pin to be pulled high (has over 2 volts readable) before calling the http post request.
The script is simple and most importantly it works, as such it became an important tool during my testing.
But first was the task of getting the excellent raspberry-gpio-python (https://sourceforge.net/projects/raspberry-gpio-python/) project installed, created by croston it is an absolute breeze to use. Luckily, it is also easy to setup as long as you install the correct packages first, so without further ado I present the 2 commands needed to install the GPIO module:

sudo apt-get install python3-pip python3-setuptools python3-wheel python3-dev
sudo pip3 install RPi.GPIO==0.7 requests==2.2
The modules should install without error, and as can be seen from the script below both "input_pin" and "url" can be changed to reflect the gpio pin connected on the Pi and also the website address to call when the pin is triggered

#!/usr/bin/python3

import requests
import RPi.GPIO as GPIO

input_pin = 17
url = 'http://example.com/api/doorbell'

GPIO.setmode(GPIO.BCM)
GPIO.setup(input_pin, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)

def button_press(channel):
        state = GPIO.input(channel)
        if state == 1:
            req = requests.post(url, data = "on", headers = {"Content-type": "text/plain", "Accept-Encoding":""})
        return

GPIO.add_event_detect(input_pin, GPIO.BOTH, callback = button_press, bouncetime = 1)

try:
        while True:
                sleep(1);

finally:
        GPIO.cleanup()
Saving the script as ~/doorbell.py and making it executable with

chmod +x ~/doorbell.py
Allowed me to run the script directly, now that the wiring on my Pi was tested and the script working to a satisfactory level I began the process of making the script into a service by copying the file to the /usr/local/bin folder with the command:

sudo cp ~/doorbell.py /usr/local/bin/
Copying the file like this places it in a location that is accessible to systemd. I then set about creating the required systemd service file with the following contents

[Unit]
Description=doorbell service
After=network.target

[Service]
Type=simple
User=root
ExecStart=/usr/local/bin/doorbell.py
Restart=always

[Install]
WantedBy=multi-user.target
saving the file as /etc/systemd/service/doorbell.service I simply had to enable and start the service with

sudo systemctl enable --now doorbell.service
The next step was to clip the case back on the Pi and attach the breadboard to the Pi case, using elastic bands to hold the breadboard in place I set up the Pi in a convenient position and wired in my doorbell, unfortunately the use of a camera eluded me at this point so no pictures were taken during these steps, instead you will have to use your imagination to decide what it looked like.
After checking for errors I quickly ran outside to make sure that the doorbell did indeed work as I was expecting.
Note: This step would have been a great achievement but in my haste I neglected to setup the trigger and response actions in my home controller software (openHAB)
With the response actions finally setup I had a working doorbell that flashed the living room lights when pressed, albeit with a slight delay of around 2 seconds.
I tidied up the cabling and secured the Pi in its new location near the door then sat back and waited for a valid press of the doorbell.
Fortunately or unfortunately, depending on your point of view it was only around an hour that had passed before the lights flashed in my living room signalling that the button had been pressed.
I eagerly jumped up and checked outside to see who was at the door and was surprised to find that the space in front of the door was empty, slightly confused I went and sat back down when about two minutes later the lights flashed again.
Realising that it was probably an issue with my dodgy wiring I rechecked all the cables again making sure that the connections to the doorbell and back to the breadboard were good, at this point I also connected to the Pi from my laptop using SSH so I could monitor the state of the pin as the trigger fired.
To assist with the debugging process I added a few print statements to the python code to display the state of the pin and to tell me when the URL request was sent, restarting the service I monitored it using

sudo journalctl -f --unit=doorbell
I then sat back and waited for my phantom trigger to fire again, but after waiting for most of the day I finally gave up watching the pin and shutdown down my laptop.
That evening whilst watching TV the lights flashed, deciding that this definitely wasn't going to be an easy fix I shutdown the Pi until I would have more time to look at it.
Plenty of research ensured over the preceding few days and it all seemed to point to radio signals causing the interference, from what I was reading the recommended solution was to either; add another resistor to the circuit or introduce a capacitor both of which would reduce the false triggers.
After carefully weighing up the options I decided on the resistor (I didn't have a capacitor to hand so couldn't use that option anyway).
Following the recommendation of the internet at large I added another resistor to the circuit and after two failed attempts with different value resistors I finally chose one that was large enough to stop the interference, thus giving me a fully working doorbell that looked something like the circuit image below.

The doorbell stayed up for around two weeks and I never received a phantom trigger but as will probably become a common theme I did have other issues.
In fact this project had two issues one was with timing, which unluckily for me raised its ugly head once I added sound to the bell event. What I discovered in this instance was that the sound played as soon as the button event was received and if you pressed the button two or three times in quick succession the music would play over the top of the already playing tune, this sounded horrible at worst and had a weird sounding echo at its best, this definitely needed fixing!
Further to this I was also experiencing issues with the WiFi connection in my house which caused the wireless connection to drop and for some reason it would not automatically reconnect, the easy solution was to restart the Pi from the main plug but again this was not ideal as I wasn't around all the time during the day and night to restart the Pi.
As it turned out, fixing the WiFi disconnection issue was relatively easy as I was able to monitor the connection and restart the Pi through a bash script that looks like the below with a simple service file that was similar to the one above.

#!/bin/bash

echo "allowing network to activate"
sleep 10m

while true; do
        echo "checking network status"
        curl --silent http://example.com/index.html > /dev/null;
        if [[ $? == 0 ]]; then
                sleep 5m
        else
                echo "network lost, restarting!
                reboot
        fi
done
Note: I have recently reinstalled Raspbian on this Pi and since the reinstall the WiFi connection has had much better stability.
In the previous python script the "button_press" function is called over and over again all the time the button is being held down, so to fix the timing issue I added a simple sleep delay with a countdown that only checks the pin state when the countdown has expired. Once the doorbell is pressed the countdown is reset back to 5 and then it is reduced by one every second, when the countdown hits 0 the pin state is checked again if the pin is still being held high then the address pointed to by "url" is called and the countdown is set back to 5 again. Thus the cycle continues, the script below explains it much better than I ever could.

#!/usr/bin/python3

import requests
import RPi.GPIO as GPIO
from time import sleep

GPIO.setmode(GPIO.BCM)
countdown = 0
INPUT_PIN = 17
url = 'http://example.com/api/doorbell'

GPIO.setup(INPUT_PIN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

def button_press(channel):
        global countdown
        state = -1
        if countdown == 0:
     print("countdown is 0")
                state = GPIO.input(channel)
                print("reading pin state: %s"%(state))
        if state == 1:
                print('url: %s'%(url))
                req = requests.post(url, data = "on", headers={"Content-type": "text/plain", "Accept-Encoding""})
                if req.status_code != requests.codes.ok:
                        req.raise_for_status()
                countdown = 5
        return

GPIO.add_event_detect(INPUT_PIN, GPIO.BOTH, callback=button_press, bouncetime=1)

try:
        while True:
                if countdown > 0:
                        countdown = countdown - 1;
                sleep(1);

finally:
        GPIO.cleanup()
After a few weeks of testing I finally considered this part of the project a success, I added the ability to send tweets out once the bell was pressed and also added other lights in the house so that they flashed, I also changed the sound that played finally settling on a lions roar, because, well, why not!
I will of course write about these other attempts in upcoming posts but until then, happy tinkering :)

No comments:

Post a Comment