Python on the Pi — How to run a Flask + GPIO web service

TL,DR; Forget WSGI on the Pi ... use Supervisor.

If you know Python and use the Rapsberry Pi platform, chances are that you somehow ended up coding a web app in Python that you needed to run on the Pi.

Chances are, too, that you used the GPIO since this is one of the major feature one looks for in an embedded platform like this one.

And GPIO on the Pi needs sudo — this actually derives from the Linux security model.

There are ways around using sudo in a script for the GPIO, and I'll cover them later in this article, but for now let's assume you just want to do use the straight Python GPIO lib

A simple GPIO script

Let's start with this for instance :

# main.py
import RPi.GPIO as GPIO

ledPin = 4

GPIO.setmode(GPIO.BCM)
GPIO.setup(ledPin, GPIO.OUT)

while 1:
  GPIO.output(ledPin, GPIO.HIGH)
  time.sleep(0.2)
  GPIO.output(ledPin, GPIO.LOW)
  time.sleep(0.2)

You absolutely need to run your script with sudo, like that :

sudo python main.py

And that works perfectly.

Adding a web framework

Now suppose that you want to use a Web Framework like Bottle or Flask to control your led (in this example) with a web page, an API, or something like that.

A very simple program (using Flask) could look like that :

# main.py
import RPi.GPIO as GPIO
from flask import Flask
app = Flask(__name__)

ledPin = 4
GPIO.setmode(GPIO.BCM)

@app.route('/on')
def turn_led_on():
    GPIO.output(ledPin, GPIO.HIGH)
    return "OK"

@app.route('/off')
def turn_led_off():
    GPIO.output(ledPin, GPIO.LOW)
    return "OK"

if __name__ == '__main__':
    app.run()

It turns the led on if you GET /on and off for /off. Pretty straightforward.

Now, running this from the command line, this looks perfect. sudo works, Flask works.

But it's not really handy if you want to run the Pi headless... in this case either you have to let a screen run (but it won't survive a reboot or a crash), or you have to use some kind of service runner to take care of crashes, reboots, etc for you.

This is where WSGI, the generally proven solution, enters the game.

WSGI : Not a solution

WSGI is just an interface specification by which server and application communicate. And since Python obeys the spec, you can talk to your Python app via a WSGI compliant server.

Python 2.5 and later comes with a standalone WSGI server but we will not use it. We'll go with Apache and mod_wsgi which is the industry proven standard.

After a very simple :

sudo apt-get install apache2 libapache2-mod-wsgi

.. you should be able to configure a virtual host and use WSGI to run your app. A simple vhost file could look like this :

<VirtualHost *:80>
    WSGIDaemonProcess yourapplication user=www-data group=group1 threads=5
    WSGIScriptAlias / /var/www/yourapplication/yourapplication.wsgi

    <Directory /var/www/yourapplication>
        WSGIProcessGroup yourapplication
        WSGIApplicationGroup %{GLOBAL}
        Order deny,allow
        Allow from all
    </Directory>
</VirtualHost>

And you need that wsgi file too :

# /var/www/yourapplication/yourapplication.wsgi
import sys
sys.path.insert(0, '/path/to/the/application')

from yourapplication import app as application

Restart Apache and see what happens : Failure.

Since we need to be root to use the GPIO, our Python script complains. and the problem here is that neither Apache child worker processes nor mod_wsgi daemon processes can run as root.

Even if we tried to add : WSGIDaemonProcess yourapplication user=root group=group1 threads=5 in the vhost, we would add a warning :

« WSGI process blocked from running as root. »

And this is true as well if you try nginx's uWSGI solution too.

Sooo ... in a nutshell, we're f*cked with WSGI.

Well, at least you're f*cked if you want to use sudo. If not, then it is a perfectly valid solution !

init.d

init.d seems to be good alternatives, but I always find it hard to work with since it generally forks daemons twice and even though I haven't investigated a lot, I think this leads to problems when using Flask or Bottle.

Moreover, The init.d script is quite complex to make right, and various examples here and there on the net are not always helping.

Supervisor

Ok, now for something serious : Supervisord.

This is a solution I've tested and works well with ridiculously less effort than the others.

sudo apt-get install supervisor

The configuration files usually resides in /etc/supervisor/conf.d/ :

[program:my_python_app]
command = sudo /usr/bin/python /var/www/yourapplication/main.py
stdout_logfile = /var/log/yourapp.log
stderr_logfile = /var/log/yourapp-err.log

sudo supervisor reload will add the new config file in memory.
And then :

sudo supervisor start my_python_app 

You can't get any simpler than that. Your script will properly start with root credentials, and will restart if crashed, as well as starting automatically on boot, all of that with 4 lines of configuration file.

BOOM, done.

Other solutions I haven't tried

Upstart — much like init.d, but available on Ubuntu, and with simpler configuration scripts. Could be a winner.

Gunicorn — Not sure if it allows to run the python code as root though, and could be a pain if you already have a webserver running for other things on your Pi, like Apache or Nginx.


Alternatives to Rpi.GPIO

If the Rpi.GPIO lib needs us to be sudo to work, there are alternatives to consider to bypass this and not use this library. For instance, you could use wiringPi's gpio command :

gpio -g write 4 0 # Write LOW to pin 4

So in Python directly, something along those lines :

from subprocess import call
call(["gpio", "-g", "write", "4", "0"])

As Gordon Henderson, the creator of wiringPi, puts it :
The gpio command is designed to be installed as a setuid program and called by a normal user without using the sudo command (or logging in as root).

So this would work with the normal Apache + mod_wsgi solution. But personally, I find it less handy than the GPIO library.