Build a Security Camera with a Raspberry Pi

How to build a robust theftproof security camera with a Raspberry Pi and Raspbian

🕒 11 min read

Category: Computers

Tags: raspberry pi, security, camera

I have owned for a long time a Heden IP security camera. I had been very content with it until the moment it stopped sending emails, for no reason. I was still working though, I could access a live stream from the Internet, but it just would send emails. I decided it was time to build something myself with a Raspberry Pi. The only thing that was cool but I won't miss about my old security camera was its ability to rotate and controlled remotely, so that I could have a pretty large view of my apartment. But I wasn't making any use of it since I was not actively watching the live stream. I was however using the email on alert feature.

Requirements

Let's start off with listing the requirements for this project.

Basic features

Advanced features (from most important to least)

Now let's try to address these bullet points one by one. But first, the big picture.

Setting up the Raspberry Pi

For this project, I used an old Rasberry Pi 1 model B revision 2, an Ethernet cable and the camera module version 2.1. You should consider buying the Pi NoIR camera with infrared LEDs to enable night vision (daylight pictures however look red-ish and bad quality).

Setting it up was fairly straightforward, I simply followed a tutorial I had already written. However I did not set max_usb_current=1 cause my power supply cannot output more than 1A. When running raspi-config, make sure to:

You can stop reading the tutorial at the end of the section "Configuration". Make sure to install ntpdate otherwise you'll get wrong dates in some emails.

I also recommend setting it so that it reboots automatically, periodically:

sudo su
crontab -e
15 */48 * * * /sbin/shutdown -r

Fine tuning

sudo tune2fs -c -1 -i 0 /dev/mmcblk0p2 # no check when booting
sudo tune2fs -O ^has_journal /dev/mmcblk0p2 # no journalling, must be done from a PC on mmcblk0p2 unmounted

In /etc/fstab:

/dev/mmcblk0p2 / ext4 defaults,noatime 0 0 (final zero means never run fsck
tmpfs /tmp tmpfs defaults,noatime,size=34m 0 0
tmpfs /var/log tmpfs defaults,noatime,size=30m 0 0

Also disable swaping to extend your SD card lifetime:

sudo swapoff --all # Temporary
sudo update-rc.d -f dphys-swapfile 
sudo apt remove dphys-swapfile # Permanently
sudo rm /var/swap

Camera

Now has come the time to turn off the Raspberry Pi, unplug the power supply, ground yourself, and plug in the camera model in the CSI port (next to the HDMI one). The camera module is very vulnerable to static eletrictiy that's why we have to be that cautious.

Then turn it back on and make sure it is correctly detected:

sudo vcgencmd get_camera

Software for motion detection and video/image capture

Now, we've got two options.

  1. Build our own solution based on Python scripts. Tedious and far from perfect.
  2. Use an existing software program. Seems to be the best solution.

There are plenty of existing programs to do motion detection on a Raspberry Pi. The two most interesting ones I found are these two:

The first one seems to be quite a big project, with a large community. On the other hand, the second one is more of a personal side-project and seems to be lacking features Motion has. So I decided to give Motion a try.

Also note that Motion comes with a web frontend, MotionEye. Since our camera has no motor to rotate, I thought there was no need for a web interface with controls. If there ever was to be motion, I would get emails with pictures anyway. Therefore I did not install MotionEye.

Motion

Install

Although installation through apt is possible, you will most likely get an outdated version of Motion. I decided to build it from their Github repo. Here is the official turorial I followed. Make sure to also install ffmpeg with apt (only useful to make videos out of pictures though, but you'll probaly want to try this feature out).

We want Motion to be able to send emails, so let's install Exim4 by reading the relevant section on my tutorial here. Or you might want to use a simpler solution with mpack.

Here is the configuration file I personally use (motion -c motion-dist.conf):

-flip_axis none
+flip_axis horizontal

-width 320
+width 1440

-height 240
+height 1080

-framerate 2
+framerate 25

-; mmalcam_name vc.ril.camera
+mmalcam_name vc.ril.camera

-; mmalcam_control_params -hf
+mmalcam_control_params -hf

 # Since our resolution is quite big, we must avoid false alarms and thus have a big number here
-threshold 1500
+threshold 15000

 # Avoid unnecessary emails and CPU usage 
-event_gap 60
+event_gap 2

-max_movie_time 0
+max_movie_time 300

-output_pictures off
+output_pictures on

-quality 75
+quality 80

-ffmpeg_output_movies on
+ffmpeg_output_movies off

-locate_motion_mode off
+locate_motion_mode on

-locate_motion_style box
+locate_motion_style redbox

-text_scale 1
+text_scale 2

-#target_dir /tmp/motion
+target_dir /home/pi/pics_and_vids

+# No need to change it since the Raspberry Pi is not powerful enough to stream at more than 1FPS while taking pictures
 stream_maxrate 1

-stream_localhost on
+stream_localhost off

-; on_event_start value
+on_event_start echo "There was a motion, %D pixels changed" | mail -s "Webcam Alert %v - Start" me@domain

-; on_event_end value
+on_event_end echo "There is no more motion" | mail -s "Webcam Alert %v - End" me@domain

-; on_picture_save value
+on_picture_save echo "%D pixels changed" | mail -A %f -s "Webcam Alert %v - Picture" me@domain

-; on_motion_detected value
+; on_motion_detected

-; on_movie_end value
+on_movie_end echo 'webcam alert' | mail -A %f -s "Webcam Alert - Video" me@domain

-; on_camera_lost value
+on_camera_lost echo 'webcam was lost' | mail -s "Webcam LOST" me@domain

-text_right %Y-%m-%d\n%T-%q
+text_right %Y-%m-%d\n%T

Meeting the initial requirements

Now that we just installed Motion, let's address the above-mentioned requirements one by one.

Basic features

First, copy the SystemD file and enable the service:

sudo cp motion/motion.service /etc/systemd/system/
sudo systemctl enable motion.service

Add these two lines below [Service]:

Restart=always
RestartSec=3
ExecStart=/usr/local/bin/motion -n -c /home/pi/motion-dist.conf

Replace the existing line ExecStart with the one above. -n is to force non-daemon mode.

Then run:

sudo systemctl daemon-reload

Advanced features (from most important to least)

Create /home/pi/alive-script.sh:

#!/bin/bash

set -u

DATE="$(date)"

args=("$@")

if [ $# -gt 0 ] && [ "${args[0]}" = "--daily" ]; then 
    # echo 'Daily alive script running...'
    echo -e "I am alive. - $DATE\n\n$(df -H /)\n\n$(systemctl status motion | cat)" | mail -s "Raspberry Pi is alive" me@domain
    # Gives it time to finish sending emails, just in case
    sleep 15
    # echo 'Daily report sent'
    exit 0
fi

FILENAME="$(date +%s).tar.gz"
PICS_AND_VIDS="/home/pi/pics_and_vids"
PICS_AND_VIDS_BASENAME="pics_and_vids"
PICS_AND_VIDS_PARENT_DIR="/home/pi"

delete_pics_vids () {
    rm $PICS_AND_VIDS/*.jpg -f
    rm $PICS_AND_VIDS/*.mkv -f
}

# Deletes previous backups, if any
rm $PICS_AND_VIDS_PARENT_DIR/*.tar.gz -f
# sync for df to return the correct values
sync

DISK_SPACE_THRESHOLD=6
#DISK_SPACE_THRESHOLD=35
DISK_SPACE_THRESHOLD_CANT_BACKUP=$((DISK_SPACE_THRESHOLD + 10))

PERCENT_DISK_USAGE=$(df -H / | grep -vE '^Filesystem|tmpfs|cdrom' | awk '{ print $5 }' | cut -d'%' -f1)

if [ $PERCENT_DISK_USAGE -le $DISK_SPACE_THRESHOLD ]; then
    # Less than $DISK_SPACE_THRESHOLD% of disk space used, doing nothing...
    exit 0
fi

if [ $PERCENT_DISK_USAGE -ge $DISK_SPACE_THRESHOLD_CANT_BACKUP ]; then
    echo "More than $DISK_SPACE_THRESHOLD_CANT_BACKUP% of disk space used, deleting pics and vids and notifying via email..."
    echo "Little disk space left ($PERCENT_DISK_USAGE% used). CANT BACKUP. - $DATE" | mail -s "Raspberry Pi - DISK USAGE ALERT" me@domain
    # Give time to finish sending email
    sleep 15
    delete_pics_vids
    exit 0
fi

# 2>/dev/null to suppress the output error "No such file or directory"
if [ -n "$(ls -A $PICS_AND_VIDS 2>/dev/null)" ]; then
    # Contains files (or is a file)"
    cd $PICS_AND_VIDS_PARENT_DIR
    tar -cvzf $PICS_AND_VIDS_PARENT_DIR/$FILENAME $PICS_AND_VIDS_BASENAME
    echo "Pics and vids were deleted. Here is a backup of them. - $DATE" | mail -A $FILENAME -s "Raspberry Pi - Backup" me@domain
    delete_pics_vids
    # Give time to finish sending email
    sleep 30
    # Delete backup we just created
    rm $PICS_AND_VIDS_PARENT_DIR/$FILENAME -f
else
    # Empty (or does not exist)
    echo "Little disk space left ($PERCENT_DISK_USAGE% used). No pics or vids found though. - $DATE" | mail -s "Raspberry Pi - DISK USAGE ALERT" me@domain
    # Give time to finish sending email
    sleep 15
fi

Then run crontab -e as pi and:

0    12 * * * /home/pi/alive-script.sh --daily
*/30 *  * * * /home/pi/alive-script.sh

Add the following in /etc/rc.local, right above exit 0:

echo "So you know... ($(date))" | mail -s "Rpi turned on" me@domain &
sleep 2
echo -e "So you know... ($(date))\n\n$(tail -n 500 /var/log/syslog)" | mail -s "Rpi turned on (with syslog)" me@domain &
sleep 15
exit 0

Another alternative is to add a cronjob that runs at every reboot, sleeps for two minutes (in case network is not immediately available) and sends an email:

vim boot-email.sh

#!/bin/bash

/bin/sleep 120
# Takes a picture and send it over email
/usr/bin/curl -s -o /dev/null http://127.0.0.1:8080/0/action/snapshot
/bin/echo -e "So you know... ($(/bin/date))\n\n$(/usr/bin/tail -n 500 /var/log/syslog)" | /usr/bin/mail -s "Rpi turned on (with syslog) - after 120s" me@domain &
/bin/sleep 25

chmod +x boot-email.sh
sudo su
crontab -e
@reboot /home/pi/boot-email.sh

Hope this helps.

Further reading