A Web Listener in Python with Flask

Flask is a "microframework" for writing web applications in Python. The example Flask application looks like this:

Download: file
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.

Installing Flask

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.

Download: file
sudo apt-get install python-pip

Next, do a system-wide install of Flask:

Download: file
sudo pip install Flask

This should be all that's required, although you might see error messages to the following effect:

Download: file
==========================================================================
WARNING: The C extension could not be compiled, speedups are not enabled.
Plain-Python installation succeeded.
==========================================================================

...don't worry about these.

flask_listener.py

Open up flask_listener.py and have a look. It's a pretty short program:

Download: file
nano flask_listener.py
Download: file
#!/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:

Download: file
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.

Line by Line

Download: file
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 imports 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.

Download: file
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.

Download: file
@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:

  1. Looks in the templates/ directory for a file called index.html
  2. Loads this file as a Jinja template
  3. Passes the RasPipe instance in rp into the template
  4. 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:

Download: file
nano templates/index.html
Download: file
<!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:

Download: file
@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:

Download: file
  <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.

Download: file
@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.

Download: file
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.)

Testing with cURL

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:

Download: file
curl --data "line=hello there" 'http://192.168.1.4:5000/display'
This guide was first published on Mar 26, 2015. It was last updated on Mar 26, 2015. This page (A Web Listener in Python with Flask) was last updated on Nov 21, 2019.