DIY Pan and Tilt Network Security Cam with Raspberry Pi

Joe Coburn 02-08-2016

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.


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.



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:


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:


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 Getting Started With Arduino: A Beginner's Guide Arduino is an open-source electronics prototyping platform based on flexible, easy-to use hardware and software. It's intended for artists, designers, hobbyists, and anyone interested in creating interactive objects or environments. Read More .

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

void loop() {
    for(int i = servoMin; i < servoMax; ++i) { 1
        // Move servos from minimum to maximum 
        delay(100); // Wait 100ms 
    for(int i = servoMax; i > servoMin; --i) {
        // Move servos from maximum to minimum
        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

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

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

        if (singleChar == 'P') {
            // Move pan servo
            data = ""; // Clear buffer
        else if (singleChar == 'T') {
            // Move tilt servo
            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 How to Install an Operating System on a Raspberry Pi Here's how to install an OS on your Raspberry Pi and how to clone your perfect setup for quick disaster recovery. Read More . 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 Build a Motion Capture Security System Using a Raspberry Pi Of the many projects that you can build with the Raspberry Pi, one of the most interesting and permanently useful is the motion capture security system. Read More ). 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:


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? What Is a Static IP Address? Here's Why You Don't Need One A static IP address is one that never changes. Dynamic IP addresses do change. We explain why you don't need a static IP address. Read More ), 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:

Where 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
        arduino = serial.Serial('/dev/ttyUSB0', 9600)
        arduino = serial.Serial('/dev/ttyUSB1', 9600)

class MoveServo(Resource):
        isLeaf = True
        def render_GET(self,request):
                		# Send value over serial to the Arduino
                        return 'Success'
                        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>
		<title>Make Use Of DIY Security Camera</title>
		<style type="text/css">
			#container {
				/* center the content */
				margin: 0 auto;	
				text-align: center;
		<div id="container">
			<img src="[Image URL]" />
			<script src=""></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>
		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);

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:


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 5 Dangers to Consider When Pointing Your Home Security Cameras It is important to carefully consider where you position your cameras, and what parts of your home you point them at. Keeping things secure is important, but so is maintaining your privacy. Read More  – then look into port forwarding What Is Port Forwarding & How Can It Help Me? [MakeUseOf Explains] Do you cry a little inside when someone tells you there’s a port forwarding problem and that’s why your shiny new app won’t work? Your Xbox won’t let you play games, your torrent downloads refuse... Read More , so your router knows where to send incoming requests. You could add an external power supply 3 Raspberry Pi Battery Packs for Portable Projects A Raspberry Pi battery can make a regular Pi into a portable computer. You'll need one of these battery solutions to get started. Read More 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!

Related topics: Home Security, Raspberry Pi, Webcam.

Affiliate Disclosure: By buying the products we recommend, you help keep the site alive. Read more.

Whatsapp Pinterest

Leave a Reply

Your email address will not be published. Required fields are marked *

  1. Deve
    August 27, 2017 at 4:41 pm

    This is wonderful work and I am using it and very excited about it, however, there is ONE problem that is killing all of the joy....

    When I click on one of the buttons, it works flawlessly, but I get THIS error in Chromes Inspect/Console window:
    XMLHttpRequest cannot load No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '' is therefore not allowed access.

    This means I cannot use it outside of my LAN. When I try to do that, the buttons do not work at all. Do you happen to have a solution for CORS violations in Python Twisted? The port has to be something other than 80 to gain access to the outside. Changing the rpy so it reflects :81 does not work.

  2. ThreeDogs
    July 25, 2017 at 12:44 pm


    I installed the motion only.
    Sometimes I get no picture over the Firefox Browser and sometimes it works for a while then stops.

    I want a real time video fom the camera display on a browser in shich I can change the Pan/Tilt over the screen with a mouse. I am wondering id Motion is the right tool to use.

    Any help/tips would be gratefully received.

  3. Deve
    May 28, 2017 at 8:51 pm

    I am following your great instructions, and everything is fine except the frame rate is terrible. I imagine it is about 2 frames per second. I do not understand why it is so slow. I did everything according to your instruction even listening to the comment about changing the motion detection threshold to 204800 (640x320). Nothing seems to make the frame rate livable. ANY ideas? Thanks again!

    • Joe Coburn
      May 29, 2017 at 9:54 am

      You can adjust the framerate and quality in the motion config -- having done another project recently, I was able to achieve 100fps (likely capped to 24fps due to the camera), but only at lower resolutions. 1080p was horribly laggy.

      • Deve
        May 29, 2017 at 12:24 pm

        In researching this, this seems to be a real problem but only for some. There is nothing in motion.config that will change the at least 1 second per frame (more like 2 seconds per frame that I am getting (that I can figure out). I spent an entire day fidgeting with the motion.conf with no results. I am beginning to wonder if Motion does not like the Logitech C270. I run OctoPrint on my 3D printers which uses MPGStreamer/Pi with those same cameras with great result. Something isnt right.

  4. Jim
    April 7, 2017 at 12:54 am

    Hi Joe,
    Outstanding tutorial, worked outside my firewall after I changed "$.get('http://PI_IP_ADDRESS/servos.rpy?value=' + value);" to "$.get('/servos.rpy?value=' + value);" on line 56 of index.html.

    On pressing either a pan or tilt button on the first visit to the webpage, the servo rapidly slews to a limit, then responds properly to button pushes. I would like to read the present values of both the pan and tilt positions, and show them in the page, perhaps to the right of the buttons. When a button is pushed for the first time upon navigating to the page, rather than slew to a limit, the servo command will add or subtract 10 from the value read from the Arduino. Can you suggest a method from index.html to query the Arduino for the present pan and tilt positions? I know how to get the Arduino to send the positions via serial commands, but I need an example of how to request those values from the index.html page, and how to display the present pan and tilt positions. Learning how to do this one simple addition to your example opens up all kinds of possibilities for moving solar panels, to moving 2.4GHz Yagi arrays, and reporting RSSI using wavemon. Thanks for the great instruction, and please consider showing how to request and display a value from the Arduino. Thank you.

  5. Johnny
    February 28, 2017 at 7:05 pm

    Hello Joe,
    At first thank you for this project.
    I tried to do all steps same like you explained.
    But I have a problem, i wrote the webpage code like you explained. But I dont know which address I have to put into the webbrowser to be on the website with the buttons and video.
    If i put 'my-Pi-address:8081' I only get the videostream without the buttons. If i write only 'my-Pi-address" or 'my-Pi-address/servos.rpy?value=' into the webbrowser I get only the Information 'website is not avaible' .
    I would be happy if you could help me.


  6. gregskey
    November 10, 2016 at 3:00 pm

    Hi Joe, Nice project, however, some of the lines of webpage code are truncated on the far right side due to indentation. Since I am not an HTML coder it is hard for me to try to imagine what is not being displayed. Is there anyway to repost the code so all of it can be seen? Or perhaps provide the code for download, perhaps via GitHub? Thanks

    • Joe Coburn
      November 17, 2016 at 9:00 am

      Thanks for stopping by!

      There is a scrollbar at the bottom of the code segment, which will let you scroll horizontally to see the rest of the code.


      • Greg Key
        November 17, 2016 at 3:12 pm

        Hi Joe, No scrollbar was displayed at the bottom of the code segment, but, I did manage to figure it out by placing the cursor on the code segment then pressing the left/right cursor control buttons. Thanks for your feedback.

  7. Todd
    September 11, 2016 at 6:20 am

    Just curious on why a Pi, and Arduino is needed. Couldn't this be done with one or the other?

  8. Vivek
    August 28, 2016 at 5:25 pm

    Resolved the issue with the servo not working.
    the problem was the addressing of the port of the Arduino Uno in the PI.

    Changed the lines in the Servio.rpy from

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


    # Setup Arduino at correct speed
    arduino = serial.Serial('/dev/ttyACM0', 9600)
    arduino = serial.Serial('/dev/ttyACM1', 9600)

    Still working on the reason for the video dropping out after a few seconds.

    • Joe Coburn
      August 31, 2016 at 9:55 am

      Glad you got it working!

      You might want to try updating the Pi ("sudo apt-get update").

      You could also try using a powered USB hub for the webcam.

    • Alex Mitra
      November 1, 2016 at 7:49 pm

      Hi. Your problem with the video is due to the fact that Motion was originally designed to be a motion detection software. This means that the video is cutting out whenever your webcam is detecting a change in a certain number of pixels. To remedy this open the Motion config file and under motion detection change the pixel change tolerance to a number bigger than the video feed's height multiplied by the video feed's width. You could also increase the audio tolerance to be on the safe side. This should prevent the video from cutting out (it worked for me).

  9. Vivek
    August 26, 2016 at 3:39 pm

    Tried this set up as per above. The servo's move when give a command xxP or xxT from the Arduino serial monitor but not from the web server of the PI.

    Also after about 15 to 20 seconds the web cam picture goes off the screen and i am left only with the buttons for servo movement that again does not work.

    Any idea where the problem could be?

    Thanks in advance


  10. Gonçalo Ferreira
    August 9, 2016 at 7:09 pm

    Hello there,

    Can it record on a disc or send it to a server for recording?


    Gonçalo Ferreir

  11. Sean
    August 3, 2016 at 6:20 pm

    I am wondering how smooth something like this would be for sports type application where you are following

    • Joe Coburn
      August 4, 2016 at 8:09 am

      I'm not too sure - I would think you will want a fluid head video tripod, or at the very least precise stepper motors. These servos are too cheap!

  12. Jonas K
    August 3, 2016 at 2:17 pm

    How do you connect the RPI to the Arduino physically? Could you post a picture?

    • Joe Coburn
      August 3, 2016 at 4:17 pm

      Hey Jonas,

      Connect the two using USB -- sorry that was not very clear!

  13. Ray Moore
    August 3, 2016 at 12:14 am

    And, why the arduino? This could be done from the rPI alone.

    • Joe Coburn
      August 3, 2016 at 7:34 am

      Hi Ray,

      You are correct, you can do this 100% from the Pi.

      The reason for the Arduino is that the Pi only has one (hardware) Pulse Width Modulation (PWM) capable pin. This means using more than one servo requires software PWM, which is not great!

      Arduinos have several PWM pins.

      Thanks for stopping by