Flask is a "microframework" for writing web applications in Python. The example Flask application looks like this:
from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello World!' if __name__ == '__main__': app.run()
Run that, and you'll get a web server on your local machine which returns "Hello world!" when you visit http://localhost:5000
in your browser.
We'll do something a little more interesting: A simple web app that controls an instance of RasPipe.
The Flask documentation on installation is good, and you should probably follow it if you're planning to do a Flask project on a robust desktop system. Unfortunately, it's a bit heavy for the Raspberry Pi.
First, make sure you have pip, the recommended utility for installing Python packages.
sudo apt-get install python-pip
Next, do a system-wide install of Flask:
sudo pip install Flask
This should be all that's required, although you might see error messages to the following effect:
========================================================================== WARNING: The C extension could not be compiled, speedups are not enabled. Plain-Python installation succeeded. ==========================================================================
...don't worry about these.
Open up flask_listener.py
and have a look. It's a pretty short program:
nano flask_listener.py
#!/usr/bin/env python from flask import Flask from flask import request from flask import render_template from flask import redirect, url_for from raspipe import RasPipe app = Flask(__name__) rp = RasPipe(None) rp.input_lines.append('starting up...') rp.render_frame() @app.route('/') def index(): return render_template('index.html', rp=rp) @app.route('/display', methods=['POST']) def display(): rp.input_lines.append(request.form['line']) rp.render_frame() return redirect(url_for('index')) @app.route('/quit') def quit(): func = request.environ.get('werkzeug.server.shutdown') func() return "Quitting..." if __name__ == '__main__': # app.debug = True app.run(host='0.0.0.0')
You should now be able to run this from the console of your Pi with:
export SDL_FBDEV=/dev/fb1 ./flask_listener.py
And visit http://[YOUR PI'S ADDRESS HERE]:5000/
in a web browser. For example, I did http://192.168.1.4:5000/
:
Let's look at what's going on here in more detail.
from flask import Flask from flask import request from flask import render_template from flask import redirect, url_for from raspipe import RasPipe
The first important thing to notice here is a set of import
s for parts of the Flask framework. request
is used to model different parts of the request sent by a browser (or other client). render_template()
is a function we'll call to turn a template file into some HTML (more about that in a bit). redirect
and url_for
will be used to have Flask send a client off to a different web page.
Next, we import the RasPipe
class defined in raspipe.py
. Python knows where to look for this because it's in the same directory as flask_listener.py
.
app = Flask(__name__) rp = RasPipe(None) rp.input_lines.append('starting up...') rp.render_frame()
This assigns an instance of the Flask
class to app
, and an instance of the RasPipe
class to rp
.
Next, we give rp
a line of input ("starting up..."), and ask it to display a single frame of the animation with rp.render_frame()
. By now, the PiTFT should be showing that line of text.
@app.route('/') def index(): return render_template('index.html', rp=rp)
This tells Flask that a URL, /
, should return the result of calling the function index()
.
Running render_template('index.html', rp=rp)
actually does quite a bit of magic. It:
- Looks in the
templates/
directory for a file calledindex.html
- Loads this file as a Jinja template
- Passes the
RasPipe
instance inrp
into the template - Turns the template into HTML
To see what the home page of our application is made of, you can open templates/index.html
with Nano:
nano templates/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>RasPipe</title> </head> <body> <h1>Display things on your PiTFT!</h1> <form action="display" method="post"> <input name="line"> <button>display this</button> </form> <ul> {% for line in rp.input_lines %} <li>{{ line }}</li> {% endfor %} </ul> <p><a href="/quit">(quit)</a></p> </body> </html>
Most of this is just a simple HTML document, which is a bit out of the scope of this guide to explain in detail. Right now, note that things inside {% %}
brackets, like {% for line in rp.input_lines %}
, are directives to the template engine, while things inside {{ }}
brackets are the names of variables to include.
Back to flask_listener.py
:
@app.route('/display', methods=['POST']) def display(): rp.input_lines.append(request.form['line']) rp.render_frame() return redirect(url_for('index'))
This creates a URL, /display
, which we can use to send lines of text to the display. Remember the little input form on the front page? It's defined in the template like so:
<form action="display" method="post"> <input name="line"> <button>display this</button> </form>
This tells the web browser that when "display this" is pressed, we want to send whatever's in the input to display
as an HTTP POST request. Inside our function, the contents of the form are available in request.form
, so we append the line to the end of rp.input_lines
, and render another frame of animation so that it'll show up right away.
Finally, we return the result of redirect(url_for('index'))
to let Flask know that it should send the client back to the home page.
@app.route('/quit') def quit(): func = request.environ.get('werkzeug.server.shutdown') func() return "Quitting..."
Here, we define a URL, /quit
, we can use to quit the program. You obviously wouldn't want to expose this in a public-facing web application, but here it's useful.
if __name__ == '__main__': # app.debug = True app.run(host='0.0.0.0')
With app.run(host='0.0.0.0')
, we tell Flask it should start up and act like a web server. The host='0.0.0.0'
bit is important because otherwise it'll only listen for local connections on 127.0.0.1, and won't be visible to the rest of the network.
If you're running into weird behavior, uncomment app.debug(True)
to get nicely-formatted stack traces in the browser when something breaks. (Be careful with this - it can allow a remote user to execute arbitrary code, so you definitely don't want to leave it running in debug mode on an untrusted network.)
The web browser is all well and good, but what if you want to talk to your shiny new web application from the command line?
curl
is a command-line client for talking to things that have URLs.
Normally, when you give curl
a URL, it just fetches whatever's at that URL and prints it to stdout.
With the --data="..."
option, however, it'll act like your browser does when you submit a form. Try something like the following with your Pi's address:
curl --data "line=hello there" 'http://192.168.1.4:5000/display'
Text editor powered by tinymce.