How to turn your Raspberry Pi and its camera board into a constantly refreshing webcam with text overlay. Feedback welcomed at the Raspberry Pi Forum.
Install a web server and PHP including the php_gd extension.
Use nohup
to start raspistill
in time-lapse mode.
Create an index page that embeds the webcam image
and a PHP script that generates the webcam image.
Upon page request, grab the latest buffered image.
Optionally overlay text before outputting.
Wireless adapters seem flaky on Linux; sometimes it is hard to get them to even work and in my experience they are often prone to drop the connection. My advice is to buy a Raspberry Pi model B with integrated USB-LAN adapter (ethernet port) and run a network cable to your modem or router. That way, the network connection will be stable and almost certainly a lot faster. Also, twice the amount of RAM in the Model B will not hurt wile having a camera attached and running a web server with PHP. Although I am sure this will work with a Model A.
Install the camera by following the official instructions. Be careful while handling bare circuit boards like the camera! Static electricity can easily ruin them and the flat cable and connectors are fairly fragile. Before turning on the computer, now is a good time to try and think of a clever way to fix the camera with a good view of its surroundings and maybe protect it from the elements and (further?) careless handling. Two 2 mm bolts will fit the holes. For my first try, I improvised by sticking one sturdy twist-tie through them, front to back, and wrapping the ends around a small stick.
Amendment to the official instructions: to get the latest firmware and camera
code, don't just run apt-get upgrade
, but also rpi-update
:
sudo apt-get update sudo apt-get dist-upgrade sudo rpi-update sudo reboot
In the latest Raspbian OS release, the rpi-update package is included. In an older
release, should apt-get dist-upgrade
not fetch it automatically, try
sudo apt-get install rpi-update
before running rpi-update
.
After updating, make sure you enable the camera by running
sudo raspi-config
and pressing Enter on the appropriate option:
After confirmation and return to the main screen, press Tab to get to the Select / Finish options, right arrow to move to Finish, Enter, and confirm reboot. Test to see if everything works so far:
raspistill -o ~/Desktop/cam.jpg
which should turn the camera on (check the light), display a preview if you are running the graphical desktop, wait 5 seconds and save a photo called cam.jpg on your Desktop. To disable the camera light, for stealth operation, edit config.txt:
sudo nano /boot/config.txt
and add this on a new line: disable_camera_led=1
. Save and exit by typing Ctrl-x, y, Enter.
Next, install the lighttpd web server and PHP:
sudo apt-get install lighttpd php5-cgi php5-gd
Apache would also work but is more of a resource hog. Enable PHP:
sudo lighty-enable-mod fastcgi sudo lighty-enable-mod fastcgi-php sudo service lighttpd force-reload echo '<?php phpinfo(); ?>' | sudo tee /var/www/phpinfo.php
and check to see if it works by pointing the Midori browser on your RPi to http://localhost/phpinfo.php
.
From another computer on the LAN, use the IP address of your Raspberry Pi (check by running ifconfig
)
or its hostname if your router understands that (probably requires installing avahi-daemon on the RPi).
E.g. http://192.168.1.65/phpinfo.php
or http://raspberrypi/phpinfo.php
.
If your modem / router provides IPv6 connectivity, try enabling it on the RPi:
sudo modprobe ipv6 sudo nano /etc/modules # add a new line with: ipv6
Normally, having an IPv6 address would immediately make your computer accessible
from the web, without having to configure port forwarding on your modem / router.
This is good and bad. Good, because no configuration and people may surf to your
webcam. Bad, because hackers. Whenever your computer is visible from the outside,
make sure to at least change your standard password! Use the password option in
raspi-config
or run passwd
. Enable your router's IPv6
firewall but open port 80 for general web access or another port (1024 or higher)
for semi-private access. Because this is a direct connection without any port
translation (forwarding), if you use a port other than 80, reconfigure lighttpd
to listen to that port.
Similar for the "normal" IPv4 connection: to make your webcam page accessible from
the outside, configure port forwarding in the router from any address on the outside
(all traffic for port 80 or otherwise 1024+) to port 80 on your RPi's LAN address.
Change the outside port for semi-private access. If you change the inside port,
you'll have to reconfigure your web server to listen to that port. Check your router
manual for details :-). If that all went well, your RPi web server should be
accessible to anyone surfing the web as
http://your-public-internet-address:the-outside-port-you-opened/,
e.g. http://93.184.216.119:8080/
(which is not a webcam, just the
IP address of www.example.com and port 8080).
Now to get the webcam up and running. You need to create two files in the web server
root directory (/var/www) and copy text to them from the two code blocks below.
In general, I find the easiest way to do that is to open an SSH session to the RPi
from my Mac, run an editor on the RPi: sudo nano /var/www/index.html
(sudo is needed because the /var/www directory is owned by user root) and on the Mac
copy-paste the text from the web browser to Terminal where the editor is running via SSH.
In this case I also provided download links so you may do:
wget http://www.dronkert.net/rpi/webcam-html-1.0.0.txt wget http://www.dronkert.net/rpi/webcam-php-1.0.3.txt sudo cp webcam-html-1.0.0.txt /var/www/index.html sudo cp webcam-php-1.0.3.txt /var/www/webcam.php
If wget is not installed on your RPi, run this first: sudo apt-get install wget
.
Save this as /var/www/index.html:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!-- Version : 1.0.0 Date : 2013-08-11 File : /var/www/index.html Info : http://www.dronkert.net/rpi/webcam.html --> <html xmlns="http://www.w3.org/1999/xhtml" lang="en-gb" xml:lang="en-gb"> <head profile="http://www.w3.org/2005/10/profile"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>My Webcam</title> <meta name="keywords" content="raspberry pi, camera, webcam, raspistill, php, javascript" /> <meta name="description" lang="en-gb" content="This is my webcam. There are many like it, but this one is mine." /> <meta name="author" content="Ewoud Dronkert" /> <style type="text/css"> img#view { display: block; } </style> <script type="text/javascript"> var shotinterval = 5; // Seconds between shots var shottimer = null; // Countdown timer to the next shot var shootingnow = false; // To avoid parallel execution // Start the timer function start() { shottimer = window.setTimeout(shoot, 1000 * shotinterval); } // Stop the timer // Return previous state (was it running? true/false) function stop() { if (shottimer) { window.clearTimeout(shottimer); shottimer = null; return true; } return false; } // Stop the timer when running, start when not running // Avoid undefined state by checking for image refresh in progress function toggle() { if (!shootingnow) { var wasrunning = stop(), node; if (node = document.getElementById("ctrl")) node.innerHTML = wasrunning ? "Start" : "Stop"; if (!wasrunning) shoot(); } } // Refresh the webcam image by re-setting the src attribute function shoot() { shootingnow = true; var img; if (img = document.getElementById("view")) img.src = "webcam.php?" + Date.now(); start(); shootingnow = false; } </script> </head> <body onload="start()"> <img id="view" src="webcam.php" alt="[webcam]" title="Webcam" /> <a id="ctrl" href="#" onclick="toggle()">Stop</a> </body> </html>
Save this as /var/www/webcam.php. It is very important to not have any blank lines
or spaces before the first <?php
or after the last ?>
.
<?php /* Version : 1.0.3 * Date : 2013-08-14 * File : /var/www/webcam.php * Info : http://www.dronkert.net/rpi/webcam.html */ $now = time(); // Current time on the server clearstatcache(); // To get fresh results for all file-info functions $cam = '/dev/shm/webcam.jpg'; // Where raspistill will save every image $buf = '/dev/shm/buffer.jpg'; // Buffer to avoid read/write collisions, also added text $err = '/dev/shm/error.jpg'; // Error message if reading $cam or $buf failed // Get file modification time of the camera image $t_cam = 0; if (file_exists($cam)) if (is_readable($cam)) $t_cam = filemtime($cam); // Get file modification time of the image buffer $t_buf = 0; if (file_exists($buf)) if (is_readable($buf)) $t_buf = filemtime($buf); // If camera image not found or not accessible if (!$t_cam) $t_cam = $now; // For the file name time-stamp // JPEG image headers, time-stamped file name header('Content-Type: image/jpeg'); header('Content-Disposition: inline; filename="webcam_' . date('Ymd_His', $t_cam) . '.jpg"'); // If the camera image is newer than the buffer and can be read if ($t_cam > $t_buf && $im = @imagecreatefromjpeg($cam)) { // Save a new buffer $black = @imagecolorallocate($im, 0, 0, 0); $white = @imagecolorallocate($im, 255, 255, 255); $font = 1; // Small font $text = date('r', $t_cam); // ISO format date/time @imagestring($im, $font, 2, 1, $text, $black); // Bottom-right shadow @imagestring($im, $font, 1, 0, $text, $white); @imagejpeg($im, $buf); // Save to disk @imagejpeg($im); // Output to browser (avoid another disk read) @imagedestroy($im); exit(); // End program early } // We are here if camera image not newer than buffer (or both not found) // or reading the camera image failed // If buffer not found or not accessible // or reading+outputting the old buffer fails // or reading+outputting existing error image fails if (!$t_buf || !@readfile($buf) || !@readfile($err)) { // Create an error image $width = 800; // Should ideally be the same size as raspistill output $height = 600; if ($im = @imagecreatetruecolor($width, $height)) { $white = @imagecolorallocate($im, 255, 255, 255); $black = @imagecolorallocate($im, 0, 0, 0); @imagefilledrectangle($im, 0, 0, $width - 1, $height - 1, $white); // Background @imagerectangle($im, 0, 0, $width - 1, $height - 1, $black); // Border $font = 3; // Larger font $text = 'Read Error'; // Message $tw = @imagefontwidth($font) * strlen($text); // Text width $th = @imagefontheight($font); $x = (int)round(($width - $tw) / 2); // Top-left text position for centred text $y = (int)round(($height - $th) / 2); @imagestring($im, $font, $x, $y, $text, $black); // Write message in image @imagejpeg($im, $err); // Save to disk @imagejpeg($im); // Output to browser @imagedestroy($im); } } ?>
The last piece of the puzzle is to have the raspistill
tool continuously
save camera pictures in the background. When I first tried to build this webcam, I let every new page request
run raspistill for a single new image. That was a bad idea for a number of reasons, foremost
the fact that concurrent invocations of raspistill apparently made the camera board or the video processor crash,
requiring a reboot of the RPi. So I separated the two file transfer paths as much as possible.
There is only one process accessing the camera and saving camera images to disk. The webcam.php
script on the server polls if there is an image newer than the previously saved buffer, and if so,
copies the latest camera image to the buffer. That should happen only once, no matter how many
clients are viewing your webcam.
If reading the camera image or the buffer fails, the script falls back to the old buffer image or an error image. This means that sometimes you won't see the image refresh for a while. I have never seen it skip more than one time period, though.
Start the automated capture in a terminal window, at the console or via SSH:
nohup raspistill -n -e jpg -w 800 -h 600 -q 70 -t 2147483647 -tl 5000 -o /dev/shm/webcam.jpg &
where
nohup
lets the process continue even after you log off,
-n
prevents previews (I don't have a monitor plugged in),
-t 2147483647
makes it run for almost 25 days (largest number possible in this version of raspistill),
-tl 5000
has it take a picture every 5 seconds,
-o /dev/shm/webcam.jpg
saves the image on RAM disk and
&
makes it all run in the background. Press Enter again to get the prompt back.
The console will look like this:
pi@raspberrypi ~ $ nohup raspistill -n -e jpg -w 800 -h 600 -q 70 -t 2147483647 -tl 5000 -o /dev/shm/webcam.jpg & [1] 3027 pi@raspberrypi ~ $ nohup: ignoring input and appending output to ‘nohup.out’ pi@raspberrypi ~ $
When you get back on the console or start a new SSH session, you can check if the raspistill process
is still going by running ps ux
or, if for some reason you used sudo, ps aux
.
To stop it: killall raspistill
.
Last tip: three things have to be synchronised, in order of importance:
Copy this or download it and save as /etc/init.d/webcam:
#!/bin/sh ### BEGIN INIT INFO # Provides: webcam # Required-Start: $remote_fs $syslog # Required-Stop: $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Control raspistill # Description: Control raspistill camera time lapse # and save images to RAM disk. ### END INIT INFO BIN="raspistill" EXE="/usr/bin/$BIN" [ -x "$EXE" ] || exit 0 SCR="webcam" # script / service name ARG="-n -w 800 -h 600 -t 2147483647 -tl 5000 -o /run/shm/$SCR.jpg" LCK="/var/run/$SCR.pid" # lock file location update_lockfile() { PID=$(pgrep "$BIN") if [ -z "$PID" ]; then [ -f "$LCK" ] && [ -w "$LCK" ] && rm -f "$LCK" else [ -e "$LCK" ] || touch "$LCK" [ -f "$LCK" ] && [ -w "$LCK" ] && echo "$PID" > "$LCK" fi } do_start() { $EXE $ARG & update_lockfile } do_stop() { killall -q "$BIN" update_lockfile } case "$1" in status) echo -n "Service $SCR is " update_lockfile if [ -z "$PID" ]; then echo "not running." >&2 exit 3 else echo "running." fi ;; start) echo -n "Starting service $SCR..." update_lockfile if [ -n "$PID" ]; then echo "already started." >&2 exit 4 else do_start echo "done." fi ;; stop) echo -n "Stopping service $SCR..." update_lockfile if [ -z "$PID" ]; then echo "already stopped." >&2 exit 5 else do_stop echo "done." fi ;; restart|force-reload) echo -n "Restarting service $SCR..." do_stop if [ -n "$PID" ]; then echo "failed to stop." >&2 exit 6 else do_start if [ -z "$PID" ]; then echo "failed to start." >&2 exit 7 else echo "done." fi fi ;; *) echo "Usage: $0 {status|start|stop|restart|force-reload}" >&2 exit 8 ;; esac exit 0
Install as a service that will start at boot time:
sudo chmod 755 /etc/init.d/webcam sudo update-rc.d webcam defaults 99 01 sudo reboot
Manual start/stop:
sudo service webcam start sudo service webcam stop
Sorry, I will probably not respond to feedback. Try the Raspberry Pi Forum.
raspistill
command changed concordantly.
Source code:
html |
php