From reading the Raspberry Pi forum, I see many are wanting to control their hardware projects from the web. Here I will show a method of integrating Python with the Apache web server, and allowing the Apache server access to the GPIO and UART ports without having to run it as root, if you have PHP already installed, both will run in the same environment. For this I am using am using Debian from here: http://www.raspberrypi.org/archives/1435, on a freshly installed SD card.
Part One: Installation, configuration and testing
As root, install the following packages:
# apt-get update
# apt-get upgrade
# apt-get install apache2 libapache2-mod-wsgi python-setuptools python-flask
edit your virtual host in /etc/apache2/sites-enabled, here I have added the wsgi components to the default host 000-default.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
<VirtualHost *:80> ServerAdmin webmaster@localhost #WSGIDaemonProcess pywsgi:80 processes=2 threads=15 display-name=%{GROUP} #WSGIProcessGroup pywsgi:80 #WSGIScriptAlias /wsgi/ /var/www/ DocumentRoot /var/www <Directory /> Options FollowSymLinks AllowOverride None </Directory> <Directory /var/www/> Options Indexes FollowSymLinks MultiViews ExecCGI AllowOverride None Order allow,deny allow from all WSGIScriptReloading On Addhandler wsgi-script .py </Directory> TypesConfig /etc/apache2/mime.types DirectoryIndex index.php index.htm index.html index.py AccessFileName .htaccess ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ <Directory "/usr/lib/cgi-bin"> AllowOverride None Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch Order allow,deny Allow from all </Directory> ErrorLog ${APACHE_LOG_DIR}/error.log # Possible values include: debug, info, notice, warn, error, crit, # alert, emerg. LogLevel warn CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> |
Parts easily missed are setting ExecCGI on the Options and adding index.py to the DirectoryIndex. Note that I have set the wsgi to identify python by the extension .py, normally .wsgi is used, I think this is some kind of tradition, or old charter, or something.
Set permissions:
Apache on Debian runs as the user www-data, for access to the serial port and GPIO on the Raspberry Pi you will need to add that user to the relevant groups. Do not run apache as root, while this is not so important when on the Pi it is good practice to avoid.
Edit /etc/rc.local and add these two lines to the end of the file:
chgrp -R dialout /sys/class/gpio
chmod -R g+rw /sys/class/gpio
when you reboot the computer, group permissions will be set to dialout on the gpio
add www-data to the dialout group
# usermod -a -G dialout www-data
Update: the /etc/rc.local method does not work, instead I have found found a tool called GPIO Admin and they have also written a Python GPIO API for it. Currently it is under development, but once installed add apache to the gpio group:
# usermod -a -G gpio www-data
and the web server will have access to the gpio pins
enable the module (it may of been enabled when installed) and restart apache
# a2enmod wsgi
# /etc/init.d/apache2 restart
To test your wsgi installation place the following file into your apache root directory /var/www as myinfo.py and load it using your web browser, http://<pi’s ip address>/myinfo.py .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
## ## display environment variables in mod_wsgi - shows if wsgi, flask etc is working ## from flask import Flask, request, make_response import sys application = Flask(__name__) def showEnv(environ): version = sys.version o = "<table>" o += "<tr>" o += "<td>Python version</td>" o += "<td>"+str(version)+"</td>" for key in environ: o += "<tr>" o += "<td>"+str(key)+"</td>" o += "<td>"+str(environ[key])+"</td>" o += "</tr>\n" o += "</table>" return o def WSerrorTextHTML(txt): err = "<p>ERROR!</p>" err += "<pre>"+ txt + "</pre>" return err @application.route('/', methods=['POST', 'GET']) def indexApp(): try: env = request.environ output = 'Hello World!<hr>' if not env['mod_wsgi.process_group']: output += 'EMBEDDED MODE<hr>' else: output += 'DAEMON MODE<hr>' output += showEnv(env) + "<hr>" output += str(request.method) + "<hr>" output += str(__name__) return output except: import traceback response = make_response(WSerrorTextHTML(traceback.format_exc())) response.headers['Content-Type'] = 'text/html' return response return "<p>something wrong!</p>" if __name__ == "__main__": application.run() |
Part 2: Using The Flask Framework
Here I have made a simple demonstration of how to use Flask, it consists of a wrapper, demo.py, the actual program coreDemo.py and some html to process, demo.html. It does not show how to use the GPIO or UART. The wrapper is used to capture error messages and conveniently display them in the web browser. Again save these files into your apache root directory, /var/www and load it using your web browser, http://<pi’s ip address>/demo.py
demo.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
## ## filename: demo.py ## ## this is a wrapper for your main program, its primary purpose is to ## manage error messages and output your HTML, XML or JSON as required ## from flask import Flask, request, make_response application = Flask(__name__) import traceback import os import sys CURRENTDIR = os.path.dirname(os.path.abspath(__file__)) if CURRENTDIR not in sys.path: sys.path.append(CURRENTDIR) def wsErrorTextHTML(txt): ## displays the python error on the web page - useful for debugging ## but on a live system would have it save the error to a database and give the user an error number err = "<p>ERROR!</p>" err += "<pre>"+ txt + "</pre>" return err @application.errorhandler(500) def internalServerError(error): err = "<p>ERROR! 500</p>" err += "<pre>"+ str(error) + "</pre>" err += "<pre>"+ str(traceback.format_exc()) + "</pre>" return err @application.route('/', methods=['POST', 'GET']) def indexApp(): try: ## ## ...your code goese here... ## I normally create a seperate 'core' application, in this case demoCore.py ## from demoCore import DemoCore demoCore = DemoCore() htmlData = demoCore.index(request) ## output your html code response = make_response(htmlData) response.headers['Content-Type'] = 'text/html' return response except: ## for when you get an error message response = make_response(wsErrorTextHTML(traceback.format_exc())) response.headers['Content-Type'] = 'text/html' return response response = make_response("unkown!") response.headers['Content-Type'] = 'text/html' return response if __name__ == "__main__": application.debug = True application.run() |
demoCore.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
## ## filename: demoCore.py ## import os CURRENTDIR = os.path.dirname(__file__) BASEDIR = os.path.dirname(CURRENTDIR) import datetime class DemoCore(): def index(self,request): ## reads an html file and does things with it ## there are better ways, but they are more complicated if request.form.get('submitBtn', None) == "submit": data = request.form else: data = "<p>no output recieved</p>" html = self.showDemoHTML(data) return html def showDemoHTML(self,data): ## reads an html file and does things with it ## there are better ways, but they are more complicated today = datetime.datetime.today() aDate = datetime.datetime.strftime(today,"%Y-%m-%d %H:%I:%S") f = open(CURRENTDIR +"/demo.html") html = f.read() html = html.replace("%TodaysDate%",aDate) html = html.replace("%PythonOutput%",str(data)) return html if __name__ == "__main__": print "Hello World"; </pre> |
demo.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>Python/Flask/mod_wsgi Form Demo</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> </head> <!-- filename: demo.html --> <body> <form action="" method="post" name="form1" id="form1"> <p>Python/Flask/mod_wsgi Form Demo</p> <p>Showing how python can work with apache</p> <table> <tr> <td style="text-align: right;">Date:</td> <td>%TodaysDate%</td> </tr> <tr> <td style="text-align: right;">Select:</td> <td> <select name="select"> <option value="a1">One</option> <option value="a2">Two</option> <option value="a3">Three</option> </select> </td> </tr> <tr> <td style="text-align: right;" valign="top">Text</td> <td> <textarea name="text" style="width: 220px; height: 100px;"></textarea> </td> </tr> <tr> <td colspan="2" style="text-align: right;"> <input type="submit" name="submitBtn" value="submit" /> </td> </tr> </table> <hr /> <p>Python Output:</p> <div><pre>%PythonOutput%</pre></div> </form> </body> </html> |
Now all you have to do is open the port 80 forwarding on your router, and everyone in the world can make your little buzzer project make noises at two in the morning.