Learn how to make a remotely viewable pan and tilt security camera with a Raspberry Pi. This project can be completed in a morning with only the simplest of parts. Here's the end result:

What you Need

  • Raspberry Pi 2 or 3 with Micro SD card
  • Arduino UNO or similar
  • 2 x micro or mini hobby servos
  • USB webcam
  • Male to male hookup wires
  • Male to female hookup wires
  • Assorted zip ties

Building the Security Camera

Attach a servo horn (the little plastic "shapes") to each servo using the provided screw. The particular shape does not really matter, although the larger the better. Do not over-tighten the screw.

Now use zip ties to attach one servo to the other at a right angle. One of these will be pan (left to right), whilst the other will be tilt (up and down). It does not matter which one does what, it can be adjusted in the code.

Servo-Hardware
Servo-Hardware

Finally, attach your webcam to one of the servos. You could use zip-ties for this, although my webcam came with a clip screwed to the bottom -- I removed this and used the screw to hold it to the horn. For stability, you may want to mount the whole rig to a case or box. A simple cardboard box does the trick quite nicely. You could cut a neat square hole and mount one servo flush to the surface, however a zip tie will be sufficient.

Pi-Pan-Tilt-Camera
Pi-Pan-Tilt-Camera

A Word About Webcams

Not all USB webcams are created equally. Connect your webcam to the USB port of your Pi and run this command:

        lsusb
    

This command displays information about all USB devices connected to the Pi. If your webcam is not listed here, you may want to try a powered USB hub and repeating the command. If the webcam is still not recognised you may have to purchase a compatible webcam.

Servo Setup

Whilst servos may seem scary and complex, they are really quite simple to connect. Servos operate on Pulse Width Modulation (PWM), which is a way for digital systems to imitate analog signals. PWM signals are essentially a rapid ON - OFF signal. A signal that is ON or HIGH is described using duty cycle. Duty cycle is expressed as a percentage, and describes how long the signal is ON for. A PWM signal of 25% duty cycle will be ON for 25% of the time, and OFF for the remaining 75%. The signal is not ON at the start and then OFF forever, it is pulsed regularly over a very short period of time.

Servos listen for these pulses and act accordingly. Using a duty cycle of 100% would be the same as "regular" 5v, and 0% would be the same as ground. Don't worry if you do not fully understand how PWM works, you can still control servos (Extreme Electronics is a good place to learn more).

There are two main ways to use PWM -- hardware or software. Hardware PWM often provides lower latency (how long between the servo receiving the command and moving) than software PWM, however the Pi only has one hardware PWM capable pin. External circuits are available to provide multiple channels of hardware PWM, however a simple Arduino can also handle the task, as they have multiple hardware PWM pins.

Here is the circuit:

Pi-Servo-Connection
Pi-Servo-Connection

Double-check the pinout for your Pi, they vary slightly between models. You need to figure out how your servos are wired. Servos require three wires to control them, however the colours vary slightly:

  • Red is positive, connect this to Pi +5v
  • Brown or black is negative, connect this to GND on the Pi
  • Orange or white is signal, connect this to Arduino pins 9 and 10

Arduino Setup

New to Arduino? Get started here.

Once the servos are connected, open the Arduino IDE on your computer and upload this test code. Don't forget to select the correct board and port from the Tools > Board and Tools > Port menus

        #include <Servo.h> // Import the library

Servo servoPan, servoTilt; // Create servo objects
int servoMin = 20, servoMax = 160; // Define limits of servos

void setup() {
    // Setup servos on PWM capable pins
    servoPan.attach(9);
    servoTilt.attach(10);
}

void loop() {
    for(int i = servoMin; i < servoMax; ++i) { 1
        // Move servos from minimum to maximum
        servoPan.write(i);
        servoTilt.write(i);
        delay(100); // Wait 100ms
    }
    for(int i = servoMax; i > servoMin; --i) {
        // Move servos from maximum to minimum
        servoPan.write(i);
        servoTilt.write(i);
        delay(100); // Wait 100ms
    }
}

All being well you should see both servos slowly move back and forth. Notice how "servoMin" and servoMax" are defined as 20 and 160 degrees (instead of 0 and 180). This is partially because these cheap servos are unable to accurately move the full 180 degrees, and also because of the physical size of the webcam prevents the full range being used. You may need to adjust these for your setup.

If they are not working at all double-check the circuit is wired correctly. Breadboards can sometimes vary in quality as well, so consider investing in a multimeter to verify.

The servos are almost too powerful for the Arduino to power, so they will be powered by the Pi. The 5v rail on the Pi is limited to 750mA provided to the whole Pi, and the Pi draws approximately 500mA, leaving 250mA for the servos. These micro servos draw approximately 80mA, meaning the Pi should be able to handle two of them. If you wish to use more servos or larger, higher powered models you may need to use an external power supply.

Now upload the following code to the Arduino. This will listen to incoming serial data (serial as in Universal Serial Bus, or USB). The Pi will send this data over USB to the Arduino, telling it where to move the servos.

        #include <Servo.h> // Import the library

Servo servoPan, servoTilt; // Create servo object
String data = ""; // Store incoming commands (buffer)

void setup() {
    // Setup servos on PWM capable pins
    servoPan.attach(9);
    servoTilt.attach(10);

    Serial.begin(9600); // Start serial at 9600 bps (speed)
}

void loop() {
    while (Serial.available() > 0)
    {
        // If there is data
        char singleChar = Serial.read(); // Read each character

        if (singleChar == 'P') {
            // Move pan servo
            servoPan.write(data.toInt());
            data = ""; // Clear buffer
        }
        else if (singleChar == 'T') {
            // Move tilt servo
            servoTilt.write(data.toInt());
            data = ""; // Clear buffer
        }
        else {
            data += singleChar; // Append new data
        }
    }
}

You can test this code by opening the serial monitor (top right > Serial Monitor) and sending some test data:

  • 90P
  • 0P
  • 20T
  • 100T

Notice the format of the commands -- a value and then a letter. The value is the position of the servo, and the letter (in caps) specifies the pan or tilt servo. As this data is transmitted from the Pi serially, each character comes through one at a time. The Arduino has to "store" these until the whole command has been transmitted. The final letter not only specifies the servo, it also lets the Arduino know there is no more data in this command.

Finally, disconnect your Arduino from the computer, and plug it into the Raspberry Pi via the usual USB port connection.

Pi Setup

Now it's time to setup the Pi. First, install an operating system. Connect the webcam and the Arduino to the Pi USB.

Update the Pi:

        sudo apt-get update
sudo apt-get upgrade

Install motion:

        sudo apt-get install motion
    

Motion is a program made to handle webcam streaming. It handles all the heavy lifting, and can even perform recording and motion detection (try building a motion capture security system). Open the Motion configuration file:

        sudo nano /etc/motion/motion.conf
    

This file provides lots of options to configure Motion. Setup as follows:

  • daemon on -- Run the program
  • framerate: 100 -- How many frames or images/second to stream
  • stream_localhost off -- Allow access across the network
  • width 640 -- Width of video, adjust for your webcam
  • height 320 -- Height of video, adjust for your webcam
  • stream_port 8081 -- The port to output video to
  • output_picture off -- Don't save any images

This is quite a big file, so you may want to use CTRL + W to search for lines. Once finished, press CTRL + X and then confirm to save and exit.

Now edit one more file:

        sudo nano /etc/default/motion
    

Set "start_motion_daemon=yes". This is needed to ensure Motion runs.

Now find out your IP Address:

        ifconfig
    

This command will show the network connection details for the Pi. Look at the second line, inet addr. You may want to set a static IP address (what is a static IP?), but for now make a note of this number.

Now start Motion:

        sudo service motion start
    

You can stop or restart Motion by changing "start" to "stop" or "restart".

Switch over to your computer and navigate to the Pi from a web browser:

        http://xxx.xxx.x.xx:8081
    

Where xxx.xxx.x.xx is the Pi IP address. The colon followed by a number is the port that was setup earlier. All being well you should see the stream from your webcam! Try moving around and see how things look. You may need to adjust brightness and contrast settings in the config file. You may need to focus the webcam -- some models have a small focus ring around the lens. Turn this until the image is the sharpest.

Back on the Pi, create a folder and navigate into it:

        mkdir security-cam
cd security-cam/

Now install Twisted:

        sudo apt-get install python-twisted
    

Twisted is a webserver written in Python, which will listen for commands and then act accordingly.

Once installed, create a Python script to execute commands (move the servos).

        sudo nano servos.rpy
    

Notice how the file extension is ".rpy" instead of "py". Here is the code:

        # Import necessary files
import serial
from twisted.web.resource import Resource

# Setup Arduino at correct speed
try:
        arduino = serial.Serial('/dev/ttyUSB0', 9600)
except:
        arduino = serial.Serial('/dev/ttyUSB1', 9600)

class MoveServo(Resource):
        isLeaf = True
        def render_GET(self,request):
                try:
                 # Send value over serial to the Arduino
                        arduino.write(request.args['value'][0])
                        return 'Success'
                except:
                        return 'Failure'

resource = MoveServo()

Now start the webserver:

        sudo twistd -n web -p 80 --path /home/pi/security-cam/
    

Lets break it down -- "-p 80" specifies the port (80). This is the default port for webpages. "--path /home/pi/security-cam/" tells Twisted to start the server in the specified directory. If you make any changes to the scripts inside the "security-cam" folder you will need to restart the server (CTRL + X to close, then run the command again).

Now create the webpage:

        sudo nano index.html
    

Here's the webpage code:

            <!doctype html>
<html>
	<head>
		<title>Make Use Of DIY Security Camera</title>
		<style type="text/css">
			#container {
				/* center the content */
				margin: 0 auto;	
				text-align: center;
			}
		</style>
	</head>
	<body>
		<div id="container">
			<img src="[Image URL]" />
			<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script><br />
			<button onclick="servos.move('P', 10)">Left</button>
			<button onclick="servos.move('P', -10)">Right</button>
			<button onclick="servos.move('T', -10)">Up</button>
			<button onclick="servos.move('T', 10)">Down</button>
		</div>
	</body>
	<script>
		var servos;
		$( document ).ready(function() {
			servos = moveServos();
		});
		function moveServos() {
			// Store some settings, adjust to suit
			var panPos = 70, 
				tiltPos = 90, 
				tiltMax = 170, 
				tiltMin = 45, 
				panMax = 170, 
				panMin = 20;
			return {
				move:function(servo, adjustment) {
					var value;
					if(servo == 'P') {
						if(!((panPos >= panMax && adjustment > 0) || (panPos <= panMin && adjustment < 0))) {
							// Still within allowed range, "schedule" the movement
							panPos += adjustment;
						}
						value = panPos + 'P';
					}
					else if(servo == 'T') {
						if(!((tiltPos >= tiltMax && adjustment > 0) || (tiltPos <= tiltMin && adjustment < 0))) {
							// Still within allowed range, "schedule" the movement
							tiltPos += adjustment;
						}
						value = tiltPos + 'T';
					}
					// Use AJAX to actually move the servos
					$.get('http://PI_IP_ADDRESS/servos.rpy?value=' + value);
				},
			}
		}
	</script>
</html>
    

Change "PI_IP_ADDRESS" (used twice) to the real IP address of your Pi (raspberrypi.local should also work if you're running the latest Raspian). Restart the webserver and then navigate to the Pi from your computer, no need to specify the port. You should be able to pan left and right, and see the video stream:

Pi-Pan-Tilt-Camera-Web-View

There you have it. Your very own Pan and Tilt Network Camera. If you want to expose your webcam to the internet, remember to consider the dangers – then look into port forwarding, so your router knows where to send incoming requests. You could add an external power supply and Wi-Fi adaptor for a really portable rig.

Have you made something cool with a webcam and a Pi? Let me know in the comments, I'd love to see!