Controlling a Raspberry Pi’s GPIO over the network
The first step in playing with a Raspberry Pi’s GPIO interface is to turn an LED on and off on command, the Hello World of digital electronics. As fun as that is, it would be more fun to do from my mobile phone using my home wireless network, this article runs through the software that I’ve used to do just that. I haven’t said that these techniques allow real-time web control of GPIO pins because they do not, but I get a response time of about 100ms using wireless ethernet with my Pi which does the job for me.
The steps can be roughly broken down into:
- Hardware and environment set up
- Python and GPIO
- Python, FastCGI and a web server
Hardware and environment set up
This article intends to focus on the software side of things, so I’m going to assume that:
- you’ve got a fully functioning Raspberry Pi running a reasonably recent build of Raspbian Linux
- that is connected to your home network through wired or wireless
- that your home network has internet access through a router that uses NAT
- that you’re comfortable using your Pi’s command line interface
With those out of the way, you will also need an LED and a resistor (I recommend a 330 ohm resister, but any value between 220 ohms and 3k3 ohms will do for this exercise) and some way of wiring those up to the Pi’s GPIO pins. One of the best ways is to use a breadboard and Adafruit’s Pi Cobbler, but you can also just twist the wires together with insulating tape.
Update 19th May 2013: After a conversation with Thomas, it’s worth noting that you need to be careful not to introduce Windows style line endings if copying and pasting text into your Raspberry Pi. Python itself isn’t bothered by line endings, but the Linux shell interpreter is and it can often fail to find Python if the #!/usr/bin/python line has a Carriage Return as well as a Line Feed on it. If something isn’t quite working as you’d expect, it’s worth a quick check with the Linux ‘file’ utility to see that’s the issue before spending time troubleshooting:
This is what 'file' says for a file with Windows line endings pi@raspberrypi /tmp $ file foo.py foo.py: Python script, ASCII text executable, with CRLF line terminators I've already installed a tool called dos2unix using sudo apt-get install dos2unix pi@raspberrypi /tmp $ dos2unix foo.py dos2unix: converting file foo.py to Unix format ... Check it again, this time it's UNIX line endings and it's reported like this: pi@raspberrypi /tmp $ file foo.py foo.py: Python script, ASCII text executable pi@raspberrypi /tmp $
Connect the LED and resistor in series between the Pi’s ground pin and one of its GPIO pins, I’ve used 18.
Python and GPIO
A version of the RPi.GPIO python library is bundled with Raspbian Linux, so we’ll just use that for now to keep things simple. The following Python code will configure GPIO pin 18 as an output and then turn it on. This code has to run as root, so you need to save it to a file and then run it with:
sudo python myFile.py
#!/usr/bin/python import RPi.GPIO as G # reference the GPIO library G.setmode(G.BCM) # use the 'BCM' numbering scheme for the pins G.setup(18, G.OUT) # Set pin 18 as an Output G.output(18, True) # Turn it on raw_input('Press return to exit') G.cleanup() # Tidy up after ourselves so we don't generate warnings next time we run this
To take this to its next level, we can allow for some user input by reading something from the terminal:
#!/usr/bin/python import RPi.GPIO as G # reference the GPIO library G.setmode(G.BCM) # use the 'BCM' numbering scheme for the pins G.setup(18, G.OUT) # Set pin 18 as an Output while (True): # keep going around this loop until we're told to quit key = raw_input("Enter 'w' for On, 's' for Off and any other key to quit. You'll need to press enter after each character: ") if key == "w": G.output(18, True) # Turn it on elif key == "s": G.output(18, False) # Turn it off else: break # leave our loop G.cleanup() # Tidy up after ourselves so we don't generate warnings next time we run this
Python, FastCGI and a web server
This is great, we’ve now got a small program running on the Pi that waits for a human to command it to turn the LED on and off, and then it does as its told, but we can take the next step of hooking it up to a web browser. To do this, we need to turn the Raspberry Pi into a web server and then create a small website on it.
One of the great features of Raspbian Linux is that it’s got a really good repository of software that easy to install, we’ll be calling on that repository to install the lighttpd Web Server and the Flup Python library to help us talk to that Web Server using FastCGI.
Install the web server and start it running using:
sudo apt-get install lighttpd sudo service lighttpd start
By default, lighttpd runs with a single website that lives in the directory /var/www. We can set up our own website in there by creating a new file /var/www/index.html and putting some HTML in it:
<html> <head> <title>Hello from the Pi</title> </head> <body> <h1>Hello world from the Raspberry Pi</h1> </body> </html>
Now when we point a web browser at the Raspberry Pi’s IP address, such as http://192.168.1.104/ (substituting your own IP address into that URL), you should get the hello world message appearing
To hook up our Python to the web server, we need to install a library that helps with the FastCGI protocol. FastCGI is slightly harder to configure than CGI, but it leaves the python script running in memory all of the time, whereas CGI has to start up a new Python interpreter for every request which is slow.
sudo apt-get -no-install-recommended install python-flup
and then change our python script to take its input from Flup instead from raw_input() and so that it runs as the root user. Save the following script as /var/www/doStuff.py and then chmod it to 755 so that the webserver can execute it.
#!/usr/bin/pythonRoot # bring in the libraries import RPi.GPIO as G from flup.server.fcgi import WSGIServer import sys, urlparse # set up our GPIO pins G.setmode(G.BCM) G.setup(18, G.OUT) # all of our code now lives within the app() function which is called for each http request we receive def app(environ, start_response): # start our http response start_response("200 OK", [("Content-Type", "text/html")]) # look for inputs on the URL i = urlparse.parse_qs(environ["QUERY_STRING"]) yield (' ') # flup expects a string to be returned from this function # if there's a url variable named 'q' if "q" in i: if i["q"] == "w": G.output(18, True) # Turn it on elif i["q"] == "s": G.output(18, False) # Turn it off #by default, Flup works out how to bind to the web server for us, so just call it with our app() function and let it get on with it WSGIServer(app).run()
By default, the /usr/bin/python executable runs as the user that calls it. That’s likely to be either the ‘pi’ user if you’re interactively using it, or the ‘www-data’ user if it’s being run by the web server. An easy to configure way of breaching this security is to user linux’s setuid feature. This is a potentially dangerous technique, so needs a bit of careful thought, but it is easy to set up.
We find out which version of python is being used by default, then make a copy of it named /usr/bin/pythonRoot and then make it run setuid root:
pi@raspberrypi /var/www $ ls -l /usr/bin/python lrwxrwxrwx 1 root root 9 Jun 6 2012 /usr/bin/python -> python2.7 pi@raspberrypi /var/www $ sudo cp /usr/bin/python2.7 /usr/bin/pythonRoot pi@raspberrypi /var/www $ sudo chmod u+s /usr/bin/pythonRoot pi@raspberrypi /var/www $ ls -l /usr/bin/pythonRoot -rwsr-xr-x 1 root root 2674528 Mar 17 18:16 /usr/bin/pythonRoot
The final step is to configure our web server with its FastCGI module and to tell it where to find our Python script. Do this by editing /etc/lighttpd/lighttpd.conf:
Add a line to the server.modules = () list at the top of the file: "mod_fastcgi", Then add the following block of code to the end of the file: fastcgi.server = ( ".py" => ( "python-fcgi" => ( "socket" => "/tmp/fastcgi.python.socket", "bin-path" => "/var/www/doStuff.py", "check-local" => "disable", "max-procs" => 1) ) ) Now restart your web server, sudo service lighttpd restart
If all has gone perfectly first time (and what ever does?), then you should now be able to turn your LED on and off by calling URLs like this: http://192.168.1.104/doStuff.py?q=w and http://192.168.1.104/doStuff.py?q=s (again, swapping in your Pi’s IP address)
So now when you point your browser at http://192.168.1.104/, the LED should turn on and off when you push the appropriate button!
Outstanding issues and ways of doing this better
The RPi.GPIO python library needs to run as the root user, which opens up all kinds of security hazards for accidents or foul play, one way of dealing with this is to seperate the user input code from the doing stuff code. I did this myself using two Python scripts, or you could use Quick2Wire’s Python API.
Another note on security, it’s always important to not trust any user inputs by checking that they’re what you’re expecting, which is why I mentioned a NAT router at the start. NAT means that you have to go out of your way to allow internet connections directly to your Raspberry Pi, which protects it against casual abuse. Running that script as root means that we’re trusting the web server and our helper libraries to not have their own security vulnerabilities. I’ve not mentioned local security because anybody with a terminal session to your Pi as the ‘pi’ user has got full access to it by default and anybody with physical access can do anything they please.
I’ve used FastCGI because it’s fairly concise to explain, but there are newer and better ways of connecting server scripts to the web these days, such as WSGI and Websockets. If you can assume an internet connection for both your web browser and the Raspberry Pi then you could use a service like Pusher which makes it all very easy.