FULL POST

Internet of things with Python and Flask

I'm an Internet-of-things person. I like physical software, I like Arduino, I love Raspberry Pi... So I'm constantly searching new ways to do whatever I can think of. I started long time ago with Arduino as it is, making projects with no connection to the web, and then I moved to some "tandems" like Arduino+PHP, Arduino+Rails, Raspberry Pi+Rails...

You could check an example of the Raspberry Pi+Rails combination in this post that I made some time ago, but surfing the web I found a new way to approach the IoT thing, a new combination that I want to try: Python and Flask. So that's what we are going to do in this post. Let's do it!

First of all, what do we need?

On the hardware side, we're gonna need some Raspberry Pi to host the web application and the interaction with the physical elements (for this example, I used an old Raspberry Pi B+, a LED to interact with, and an Infrared proximity sensor to read some data from the real world). The final schema that I'm going to use in this example looks like this:

IoT example schema

On the software side, we need some Python background (not too much, but enough to follow the example), and a little of HTML/CSS/Javascript knowledge.

Ok, I know what's Python, but... what about Flask?

Flask it's a little Python microframework based on Werkzeug and Jinja 2 for web development. We're gonna cover the very basics of Flask in order to be able to do our example, but maybe in the future I'll post some advanced examples. It may seem that it is not as powerful as Rails, but for the task we have fits perfectly. Some of the best libraries to use the Raspberry Pi's GPIO are developed in Python, so Flask FTW!

First step: Raspberry Pi setup.

We're gonna use the Raspberry Pi as web server as well as "communication point" with the outside world, so we need to install some things.

Obviously, we have to install some Linux flavour, the one I use it's the most popular one available for Raspberry Pi: Raspbian. If you don't have experience on how to do that, you could check the official Raspberry Pi NOOBS setup guide.

Then, we need to check if the RPi.GPIO library it's installed (is installed by default in Raspbian). To make sure that it is at the latest version:

pi@raspberrypi:~ $ apt-get update  
pi@raspberrypi:~ $ apt-get install python-rpi.gpio python3-rpi.gpio  

or using pip:

pi@raspberrypi:~ $ pip install RPi.GPIO  

And finally, we have to install Flask, just by executing:

pi@raspberrypi:~ $ pip install Flask  

Ok, let's code some Python!

Starting our web application

On our Raspberry Pi, we're going to create a folder where we'll have all of our code. We can create it wherever we want, e.g., on the user folder:

pi@raspberrypi:~ $ mkdir flask-internet-of-things-app  
pi@raspberrypi:~ $ cd flask-internet-of-things-app  

Let's start with the "Hello world!" of Flask, so we're going to create a file named main.py:

pi@raspberrypi:~/flask-internet-of-things-app $ touch main.py  

and put the following code there:

from flask import Flask  
app = Flask(__name__)

@app.route("/")
def hello():  
    return "Hello World!"

if __name__ == "__main__":  
    app.run(host="0.0.0.0", debug=True)

Save the file, and then execute it from the terminal like so:

pi@raspberrypi:~/flask-internet-of-things-app $ python main.py  

You can see that something is going on here, and if you go to your web browser and enters the URL http://your-raspberrypi-ip:5000 you'll see what is going on: it shows the text "Hello world!". Let's stop here for a moment to understand how it works:

With from flask import Flask we're importing the main object Flask which is the one that give us all the power. We initialize one Flask instance with app = Flask(__name__), having on the app variable the instance that we're going to use on the entire file.

@app.route it's the decorator that Flask give us to be able to do all our routing to bind a function to an URL. In this case, we're binding the "/" route (the index) to the hello method.

And finally, the last piece of code on this "Hello world!" thing ensures that we're executing the Flask object properly. We can import the content of the main.py file on other files by doing the from whatever import * thing, and in this case we don't want to be able to start a server. That's the reason of the if __name__ == "__main__" condition, it checks that the file it's invoked by executing python main.py from the terminal, and only in this case it will start the server.

app.run starts the server, and we can pass some parameters, like in our case host and debug. We need to specified the parameter host to 0.0.0.0 to be able to access to the web application outside the Raspberry Pi (from our computer), and debug=True for obvious reasons.

Ok, once we understand this little things, we're going to do some fancy things to make our web application looks good (render text may be enough beautiful, but it's much better to render some pretty good looking HTML :D).

Using templates and styling things

Flask gives us a lot of useful methods, but the most important ones in order to use HTML templates and be able to load some CSS are render_template and url_for. But before we see in detail both methods, let's see a couple of important concepts. The structure of a Flask application works kinda like the Rails way of Convention over configuration, this means that Flask expects that we store all the HTML templates in a special directory called templates, and all our public content (like stylesheets, javascripts, images...) in a special directory called static (like the public folder in Rails).

With that in mind, let's create both folder templates and static on the root of our application.

pi@raspberrypi:~/flask-internet-of-things-app $ mkdir templates  
pi@raspberrypi:~/flask-internet-of-things-app $ mkdir static  

To do some quick styling, I'm going to use Bootstrap with the Jumbotron Narrow example HTML, so we only have to download the files and put it into our static folder.

Once we got all the Bootstrap files on the static folder, we're going to integrate it with the application. Firstly, let's open the main.py file and change our index method with this lines:

@app.route("/")
def index():  
    return render_template('index.html')

As we could see, we have replaced the return "Hello World!" thing for return render_template('index.html'). Flask will look for index.html template in the templates folder, and render it.

Now it's turn for the template. Create an index.html file in our templates folder with this code:

<!DOCTYPE html>  
<html lang="en">

<head>  
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="">
  <meta name="author" content="">
  <link rel="icon" href="{{ url_for('static', filename='images/favicon.ico') }}">

  <title>Flask Internet of Things App</title>

  <link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">

  <link href="{{ url_for('static', filename='css/jumbotron-narrow.css') }}" rel="stylesheet">

  <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
  <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
</head>

<body>

  <div class="container">

    <!-- A bunch of magic HTML code :) -->

  </div>
  <!-- /container -->
</body>

</html>

The only special thing that you could see here other than HTML code it's the {{ url_for }} method that enables us to get the URL for our static content, passing as parameter the name of the file we want to load. Well, and the fact that to be able to execute this method, we need to do it inside {{ }}, that's the Jinja 2 syntax that we're going to see right now.

Template

Yeah, this is good stuff, but... Can't we make it less static?

Of course! Now it's when Jinja 2 enters in the equation. It's a full featured template engine for Python, and the syntax is similar to Handlebars. You could check the complete Jinja2 documentation here since we're only going to use one main thing: {{ }} to access to the variables we're going to pass through the render_template method.

Wait a minute... When we go to the part of the Internet of things?

Right now. Once we have more or less clear the basics of Flask, let's go into the interaction with the real world. For this, we're going to create a very simple class in the root of our application called raspi.py. With the help of the RPi.GPIO library, we're going to code some very simple methods to be able to read the sensor value and modify the state of the LED. The content of the class will looks like this:

import RPi.GPIO as GPIO

SENSOR_PIN  = 22  
LED_PIN     = 23

class Raspi(object):

    def __init__(self):
      GPIO.setmode(GPIO.BCM)
      GPIO.setup(SENSOR_PIN, GPIO.IN)
      GPIO.setup(LED_PIN, GPIO.OUT)

    def read_sensor(self):
      return GPIO.input(SENSOR_PIN)

    def change_led(self, value):
      GPIO.output(LED_PIN, value)

I'm not going into details here, you could check the RPi.GPIO documentation, but basically here we're declaring a class with two basic methods: read_sensor reads the value of the PIN number 22 of the Raspberry Pi GPIO, and change_led changes the state of the PIN number 23of the Raspberry GPIO (where our LED is connected).

Ok, with this done, let's modify our main.py file to use this new class and be able to interact with the GPIO via web. At the end we'll have something like this:

import raspi  
from flask import *

app = Flask(__name__)  
raspi = raspi.Raspi()

# Index route
@app.route("/")
def index():  
  # Read the value of the sensor
  value = raspi.read_sensor()
  # Render the index.html template passing the value of the sensor
  return render_template('index.html', sensor_value=value)

# About route
@app.route("/about")
def about():  
  # Render the about.html template
  return render_template('about.html')

# Change LED value POST request.
@app.route("/change_led_status/<int:status>", methods=['POST'])
def change_led_status(status):  
  # Check the value of the parameter
  if status == 0:
    raspi.change_led(False)
  elif status == 1:
    raspi.change_led(True)
  else:
    return ('Error', 500)
  return ('', 200)

# Starts the app listening to port 5000 with debug mode
if __name__ == "__main__":  
  app.run(host="0.0.0.0", debug=True)

There is some new things here:

First we import our new class and create an instance with:

import raspi

...

raspi = raspi.Raspi()  

A new /about route with his method that renders the about template:

# About route
@app.route("/about")
def about():  
  # Render the about.html template
  return render_template('about.html')

About

A modified index method that with the help of the raspi object reads the sensor value and pass it to the index.html template to be able to print it:

# Index route
@app.route("/")
def index():  
  # Read the value of the sensor
  value = raspi.read_sensor()
  # Render the index.html template passing the value of the sensor
  return render_template('index.html', sensor_value=value)

Index

And a new change_led_status method which is the one we're gonna use to change the status of the LED.

# Change LED value POST request.
@app.route("/change_led_status/<int:status>", methods=['POST'])
def change_led_status(status):  
  # Check the value of the parameter
  if status == 0:
    raspi.change_led(False)
  elif status == 1:
    raspi.change_led(True)
  else:
    return ('Error', 500)
  return ('', 200)

There is one more new thing in this method. On the route definition, we could specify if the route it's going to have parameters, and the HTTP verb used to invoke the route. In this case, with /change_led_status/<int:status> we're defining that the route will have a parameter (an Integer) received as status. And by passing the methods=['POST'] parameter to the route method we're defining that this route will only be accessible via POST (by default all routes are accessible via GET). Passing the route parameter status to the corresponding method we could use the information in order to change the status of the LED properly.

To send the value of this parameter we're gonna use some jQuery to do some POST requests to the route depending of the button we click. Something like this:

$(document).ready(function() {
  $('#set_on').click(function() {
    $.post('/change_led_status/1');
  });
  $('#set_off').click(function() {
    $.post('/change_led_status/0');
  });
});

Ok, after what it seems like an interminable post with a thousand explanations, we're almost done! :_) Putting all this code together (at the end of the post I'll give you the Github repository so you can check with calm), and executing our main file by doing python main.py, all should be working fine (if not, let me know in the comments).

Try to go to your web browser, enter the URL http://your-raspberrypi-ip:5000 and play with the buttons to turn off and on the LED, and refresh the page to see how the value of the sensor changes... It's alive!

Alive

Any place where I can see the result?

Yeah! Here you have the Github repo for this example, so you can clone it, change it, play with it... whatever you want! :)

COMMENTS