Install and configure Apache2 for Python CGI on Raspberry Pi.

Mission:

I want to control my robot from a web browser, such as Chrome or Firefox. From the browser I want to issue simple commands such as ‘move forward‘ or ‘turn right‘. Also I want to be able to view images taken from the on-board Raspberry Pi camera. I can currently control it from the on-board Raspberry Pi running a Python script. This script can command the robot to move, and take pictures using raspistill. But I have to download the images manually to view them, which is a pain. Using a web browser would be much easier.

Tools:

The Raspberry Pi can have a web server running on it and I plan to use Apache2 because I am already familiar with it. The web server can use CGI (common gateway interface) to run programs written in pretty much any language, so I should be able to use it to run a modified version of my Python script.

Step 1: Install Apache

This is easy. First make sure the Pi is up to date, and then use apt-get to download and install Apache2.

sudo apt-get update
sudo apt-get upgrade
sudo apt-get install apache2

Browse to the address of your raspberry pi using your favourite browser and you should get the standard ‘It Works...‘ apache webpage.

Step 2: Configure Apache2 for CGI

After much messing about trying to follow instructions from apache.org I discovered that CGI works out of the box. You just need to know where to put the CGI scripts. They need to go in;

/usr/lib/cgi-bin

Step 3: Write a python script and place in the cgi-bin directory

The output of this script needs to consist of two sections separated by a blank line. The first section consists of a number of header lines informing the browser of the type of data that follows. The second section will contain the HTML that we want the browser to render for us.

As always the first line of my script is the shebang line that describes where the python interpreter is found. Then I want a few libraries to make my life a little easier. I want the cgi library for reading the form data from the browser, the serial library for writing to the serial port, and subprocess for executing raspistill to take pictures.

#! /usr/bin/env python

import cgi
import serial
from subprocess import call

Next I declare a couple of procedures that I can call later. The first is to send commands to my Arduino via the serial port, the second is to call the raspistill command to take pictures.

def arduino(cmd):  
    ser = serial.Serial('/dev/ttyAMA0', 9600)
    ser.write(cmd + "\n")
    ser.close

def picture():
    call("/usr/bin/raspistill -o /var/www/brianpic.jpg -t 1s -w 800 -h 600", shell=True)
    print "<p>picture taken</p>"

Then we output a single header line indicating that HTML content is to follow (known as the mime type), then a blank line indicating the end of the header section.

# Output the headers.
print "Content-Type: text/html" # html content to follow
print                           # blank line, end of headers

Next I start to output the html content which constists of the HTML head and the start of the body containing a very simple form.

# Output the content.
print """
<html>
    <head>
        <title>Brian the robot</title>
    </head>
    <body>
        <h1>Brian the robot</h1>
        <p>Enter a command.</p>
        <form method="post" action="robot.py">
            <p>command: <input type="text" name="command"/></p>
        </form>
"""

Now I make use of the FieldStorage class in the cgi library to retrieve the data passed in from the web form.

form = cgi.FieldStorage()
if "command" in form:
    command = form["command"].value
    if command != "" :
        print "<p>You gave command: " + command + "</p>"
    if command == "take pic":
        picture()
    if command == "light on":
        arduino("led_on")
    elif command == "light off":
        arduino("led_off")
    elif command == "left":
        arduino("turn_left")
    elif command == "right":
        arduino("turn_right")
    elif command == "forward":
        arduino("forward")
    elif command == "reverse":
        arduino("reverse")
    elif command == "look forward":
        arduino("look_forward")
    elif command == "look left":
        arduino("look_left")
    elif command == "look right":
        arduino("look_right")
    elif command == "stop":
        arduino("halt")

This probably requires a little explaination. First I check if the parameter called "command" exists in the form‘s data. When the web page is first requested via a HTTP GET command, there will be no data in the form, so this if statement will return false. Then later when the form data is filled in and the page is requested again via a HTTP POST command this ‘if‘ statement will return true.

We then grab the value of the command parameter and store it in a variable called command. Then I have a bunch of messy ‘if‘ and ‘elif‘ (else if) statements deciding what action should be taken. Provided a valid command was given, these will call one of the two procedures I declared earlier, either outputting a command to the serial port, or taking a picture.

If a picture is taken it needs to be stored somewhere the web browser can access it. For example in apache‘s document root folder. Which by default is /var/www

However the apache process cannot write to this folder, so I modified the permissions on this folder using;

sudo chmod 666 /var/www

This feels a little bit like a security risk, but as I‘m the only person using the pi, I guess it is fine for now. Please add a comment if there is a better way.

Finally I finish up my python script by outputting the remainder of the html body which contains an img tag to display the picture.

print """
        <img src="/brianpic.jpg" width="400">
    </body>
</html>
"""

We‘re done right? Not quite, we need to make sure our python cgi script is executable.

sudo chmod 777 /usr/lib/robot.py

Step 4: Open the web page in my browser

Now pointing my browser to;

http://192.168.84.124/cgi-bin/robot.py

Brings up

Now if I type ‘take pic‘ in the input box and hit the enter key, the page reloads showing the first picture that is taken by the pi camera.

Ok it‘s a little dark on the floor in the corner of my room, but I‘m happy the concept is working.

I did run into one last problem. The user that the apache process runs under, www-data, does not have permission to use the serial port. So I had to add this user to the dialout group with this command;

adduser www-data dialout

And then restart the Apache process with this command;

sudo /etc/init.d/apache2 restart

That‘s it. Mission accomplished. There are probably better, faster and more secure ways of doing this but I found it a good learning exercice on how CGI works with Apache on my Raspberry Pi

comments powered by Disqus