Turning a Raspberry Pi and a webcam into a home surveillance system with email notification

François
11 min readFeb 27, 2020

--

The other day a colleague showed me a live stream of his garden at home. He wanted to check if everything was ok due to the strong wind we were experiencing here in Switzerland and other parts of Europe. He told me that he got a whole security system package for about 120 USD. In my opinion a pretty good deal since the experience was quite good.

Remembering that I had a PI and an old webcam laying around somewhere in a box in my basement, I thought that I could build that myself — just for fun.

Googling “raspberry pi webcam” returned a lot of useful results. As expected, I wasn’t the only one thinking about using a webcam with a Raspberry PI. What the tutorials however did not show was how to also alert you.

So let’s build our own simple security system.

What we want to do is that as soon as our system registers movement, it should take a video (or pictures) and alert us via email.

First step: connect your webcam to your PI and make sure you can use the command line (terminal) on your PI. Either by connecting a keyboard or via remote ssh:

ssh pi@LOCAL_IP

Use this excellent guide if you don’t know how to use ssh: https://www.raspberrypi.org/documentation/remote-access/ssh/

Motion library

Luckily, instead of having to code the motion detection ourselves, we can simply use a library called “motion”.

Before we start let’s make sure your PI is up to date. Execute this in your terminal:

sudo apt-get update
sudo apt-get upgrade

This may take a while.

Install the motion library:

sudo apt-get install motion

After that we can configure the settings of the motion lib we just installed:

sudo nano /etc/motion/motion.conf

In the config file, look for those settings and make sure to edit their values:

# Runs motion as a background process
daemon on
# If you want to access the webcam from a remote computer you can leave it off. Otherwise 'on'
webcontrol_localhost off
# TCP/IP port for the http server to listen on (default: 0 = disabled)
webcontrol_port 8087
# Output media size
width 640
height 480
# Picture with most motion of an event is saved when set to 'best'.
output_pictures best
# Codec to used by ffmpeg for the video compression.
ffmpeg_video_codec mp4
# The mini-http server listens to this port for requests (default: 0 = disabled)
stream_port 8088
# Restrict stream connections to localhost only (default: on)
stream_localhost off

Be careful not to use ports which are already in use by other processes. You can check for busy ports with:

netstat -tulpn

If everything is set up correctly, we can start motion:

sudo service motion start# To stop it again use
sudo service motion stop

Watch your webcam stream

Your webcam lights should now be on and you can access the webcam stream via your browser and navigate to http://PI_IP_ADDRESS:PORT, if you are not working on your PI directly — otherwise open http://localhost:PORT. To find out your PI’s local network IP address use:

ifconfig

This will print a couple of network information and you may see a local ip such as 192.168.0.12 — which means you can access the webcam at:

// The port is the one you used in the motion.conf: webcontrol_port
http://192.168.0.12:8080/

Make sure you are in the same network as your PI!

As soon as you walk into the webcam’s view, the motion library will register those changes and start taking a video or pictures and save them in the folder, which is defined in target_dir in the motion.conf (Default: /var/lib/motion).

# Check if any media has been recorded
ls /var/lib/motion

Alert on motion detection

Before you consider using email to send you the video files: I actually ended up uploading the videos to a drive folder instead. This has been done by using service accounts. You can read more about it here: https://medium.com/@abhimanyuPathania/google-drive-service-accounts-and-nodejs-a038b8ec482f

Our genius plan

Now for the case we are not at home we want the PI to talk to us and let us know if there was any activity. To do this, we will write a small python script which will send an email to us including a video of what happened. I mean, your toys could be alive and having a party while you are gone! You will finally find out!

We could go more fancy and use some kind of service (like Twilio) and send a SMS or even WhatsApp message, but let’s keep it simple for now.

First we will change the output directory in which the motion library saves our media data to.

Open the motion.conf again via:

sudo nano /etc/motion/motion.conf

Now look for the output directory variable “target_dir” and change it to /home/YOUR_USER/motion

# Target base directory for pictures and films
# (Replace USER with your system user name, e.g. "pi")
target_dir /home/USER/motion

The “motion” directory doesn’t exist yet, thus we have to create it and set its permissions:

mkdir /home/USER/motion
chmod -R 777 /home/USER/motion/

Next we create the python script which will send us an email via GMAIL:

cd /home/USER/motion
touch alert.py

Since we use GMAIL to send us an email we have to set some permissions so that Google allows us to actually do that:

  1. Go to https://myaccount.google.com/lesssecureapps
  2. Enable the permission for “less secure apps”

I strongly recommend you to create a new gmail account just for those kind of things.

If you want to move fast, the complete code of this guide for alert.py is available here: https://gist.github.com/francois-n-dream/8e7bc5772b8a252d7b51fb23dedc4de6

Now open the alert.py with e.g. “nano alert.py” and lets start defining some variables:

# Include some libs (some of them we will use later)
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.utils import formatdate
from email import encoders
SMTP_SERVER = "smtp.gmail.com"
SMTP_PORT = 587
# Sender GMAIL data
SENDER_GMAIL_USERNAME = "from@gmail.com"
SENDER_GMAIL_PASSWORD = "yourpw"
SENDER_FROM_NAME = "PI"
# Receiver mail data (your email address)
RECEIVER_EMAIL = "receiver@host.com"
# Directory in which we move our sent files
TARGET_SENT_DIR = "sent"
# The format of your video. Change if you set a different one
VIDEO_FILE_FORMAT = ".mp4"

Now to the function which actually sends the email:

# Generic function to send a mail to a recipient
def sendMail(recipient, subject, content, attachment_file=None):
session = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
session.ehlo()
session.starttls()
session.ehlo()
session.login(SENDER_GMAIL_USERNAME, SENDER_GMAIL_PASSWORD)
msg = MIMEMultipart()
msg['Subject'] = subject
msg['From'] = SENDER_FROM_NAME
msg['To'] = recipient
msg.attach(MIMEText(content, 'plain'))
# Later we send an attachment
if attachment_file:
part = MIMEBase('application', "octet-stream")
part.set_payload(open(attachment_file, "rb").read())
encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="motio.mp4"')
msg.attach(part)
# Send Email
session.sendmail(SENDER_GMAIL_USERNAME, recipient,msg.as_string())
session.quit
# As soon as we execute this script, we send an email to us:
sendMail(RECEIVER_EMAIL, "Test", "Easy as PI!")

Before we go on, save the file and lets see what happens if we execute the alert.py:

python alert.py

If everything is set up correctly, you should have received an email from GMAIL_USERNAME to the account you have defined in RECEIVER_EMAIL.

Now we have to think about how we detect if there is a new media recording and only then send us an email. Otherwise our PI is gonna spam us with messages.

To do this we will simply rely on checking if there are any files ending with “.mp4” in the same directory. Meaning our python script will read the directory, get all files and check each one if the file name ends with “mp4”. If there is a file, we attach it to the email and send it. Now that the file has been sent, we need to mark it as “sent”. For this we can simply move the file to another directory called “sent”.

# Our directory with all sent files
mkdir sent

Our python script will now include the code to check for all files, send an email and move the file to the “sent” directory:

def checkUpdatesAndSend():    
files = os.listdir(".")
video_file = None

# Go through all files in this directory
for file in files:
if file.endswith(VIDEO_FILE_FORMAT):
video_file = file
break

# New video file found
if video_file and os.path.isdir(TARGET_SENT_DIR):
# Send it by mail
sendMailAlertWithAttachment(video_file)

# Mark video file as sent by copying it into the TARGET_SENT_DIR
shutil.copy2(video_file, TARGET_SENT_DIR)

# Remove file so we don't send it again
os.remove(video_file)
# Function to send the alert mail
def sendMailAlertWithAttachment(attachment_file):
emailSubject = "PI made a video for you :D"
emailContent = "Seems there was motion detected"
sendMail(RECEIVER_EMAIL, emailSubject, emailContent, attachment_file)

As said before, the whole code is in this gist

Check if everything works. Start the motion capturing:

sudo service motion start

Walk into your webcam and move like jagger. The motion library should have registered motion and saved a video mp4 file in /home/USER/motion. Now execute the python script:

python alert.py

You should receive an email with the mp4 file attached. The video file should have been copied to the sent folder:

# Should not include any mp4 files anymore
ls -al .
# Should print the video file name
ls -al sent/

Run alert.py automatically after a video has been recorded

As you remarked we had to manually execute the alert.py. This is of course not what we want. We want the script to run automatically.

Thanks for Gilles for pointing out in the comments that motion itself has a “on_movie_end” event in the “/etc/motion/motion.conf” file:

I had a few issues with the cronjob however: if it runs while the file is still being written, a partial/corrupt file will be sent and copied to the ‘sent’ directory, and you won’t be able to open it.

To fix this, remove the cronjob and use the “on_movie_end” parameter in “/etc/motion/motion.conf”, this allows you to specify the command to be executed when a movie file is closed.

I used the following in motion.conf:

on_movie_end /usr/bin/python /path/to/alert.py >> /path/to/alert.log 2>&1

Open the “/etc/motion/motion.conf” and search for “on_movie_end” and put this as value:

on_movie_end /usr/bin/python /home/USER/motion/alert.py >> /home/USER/motion/log.txt 2>&1

Now every time motion has recorded a video, it will execute the alert.py script and log useful information.

[OUTDATED] Using a cronjob

(You can skip this part if you are using the “on_movie_end” above, but learning about cronjobs can be useful for other tasks)

We want the alert.py script to run automatically every x seconds or minutes. For that to work we can use cronjobs. A cronjob is basically just a task which will get executed in a specified time. To manage cronjobs you can use crontab:

crontab -e

Add this line so we can run the alert.py every 2 minutes:

*/2 * * * * /home/USER/motion/alert.py >> /home/USER/motion/log.txt 2>&1

Our alert.py will now run every 2 minutes automatically.

We also told the cronjob to write any logs to a log.txt file:

# Create the file so the cronjob can write a book:
touch /home/USER/motion/log.txt
# To read it
tail /home/USER/motion.log.txt

Improving the experience

So far we have finished our basic surveillance system. But the user experience is not quite yet the best since a video will always be recorded as soon as someone walks in front of the camera and that someone could just be you or your doggy.

The first idea which came into my mind was that the alert.py could be extended to only run on certain days and between certain hours. Let’s say you leave for work at 9 am and return mostly around 5 pm from Monday to Friday. Now in the alert.py you could check for the current time and only send an email if it is within the defined timeframe. Or simply turn on/off motion. The script to do latter could look like this:

#! /usr/bin/python3import os
from datetime import datetime
cmd_on = 'sudo service motion start'
cmd_off = 'sudo service motion stop'
is_on = False
# Check if the motion service is already running
is_running = os.system('service motion status') == 0
now = datetime.now()
hour = now.hour # is 24 hrs
minute = now.minute
# 0 = Monday, 6 = Sunday
weekday = datetime.today().weekday()
TIME_START_HR = 8
TIME_END_HR = 18
if weekday >= 0 and weekday <= 4:
if hour >= TIME_START_HR and hour <= TIME_END_HR:
is_on = True
print "Service is: " + is_running
print "Turn on?: " + is_on
if is_on and !is_running:
os.system(cmd_on)
elif !is_on && is_running:
os.system(cmd_off)

Its a quick fix but doesn’t work if you leave late for work or come back earlier. And what if you do home office from time to time and no surveillance on the weekend?

You could simply put something in front of the webcam, so that motion wouldn’t register any movement, but before you put it and after you remove it, it would register this as movement and alert you. Plus you would have to do this every time you leave or come back home.

I was thinking about face recognition, but the problem is that my webcam’s resolution is pretty bad (no CSI image enhancer either) and what if my face is not on the picture? If your webcams resolution however is pretty good you may have a look into tensorflow with which you can recognize people in an image. So before sending an email you try to detect a body of a human and if there is one, well OMG SEND AN ALERT!!

At last you could build a simple web app in which you could turn the functionality on and off. The PI would simply use the same remote database and check if “enabled” is true or false. This way you could not only remote control the motion detection, but request an image too. On the other hand, you would have to use that switch again all the time whenever you come and go. Furthermore you would have to build the web app and host it somewhere with a database access.

How about a physical button, which is connected via bluetooth to your PI and you place it next to your door (maybe on the inside and not too obvious)? Every time before you leave your castle you press the button. Very simple and you could add more functionality to it.

You see there are plenty of options and I am sure there are even more I haven’t thought of so far.

Additional ideas

  • You could define another cronjob, which clears the old videos in the sent folder after some time to save space on your PI or just delete them directly instead of moving them to the “sent” folder.
  • You could add some fun by building a Lego construction around your webcam. So it’s not even obvious that an intruders is getting recorded.
  • You should definitely secure your PI by changing its default ssh password (See: https://www.raspberrypi.org/documentation/linux/usage/users.md) and also make sure you can only access the webcam stream by entering a password (defined in the /etc/motion/motion.conf “webcontrol_authentication”).
  • For people who live alone, you could reverse the whole mechanism and send an alert if there hasn’t been detected any motion since x hours. Basically: if my PI hasn’t seen me, it is gonna send out mails to my family and friends, so they can check up on you. Or you could be as cool as in the movies and implement something like “If my PI hasn’t seen me, its gonna send out this email!

Happy coding!

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

François
François

Written by François

Serial founding engineer of gaming platforms, Game dev & founder of dibulo.com

Responses (3)

Write a response