In this tutorial I'm going to teach you the basics of controlling your Raspberry Pi from a web browser using Python, Flask and RPi.GPIO. We'll do this in two parts.

  • Part 1: Will cover how to make a Python/Flask server that will turn an LED on/off when you visit a URL
  • Part 2: Will cover how to hide these URL visits behind a funky, friendly Javascript User Interface

You'll be able to use this example to control the LEDs on Explorer HAT or Pibrella remotely, and you could easily expand it to control other things too!

Part 0: LEDs

Before you start, you're going to need some LEDs attached to your Pi to light up. If you have an Explorer HAT or a Pibrella then you're good to go- otherwise you'll need:

  • 3 LEDs, the colours really don't matter but Red, Blue and Green would be best
  • 3 resistors, anything from 150Ohm to 680Ohm should be fine for now
  • Some wires to hook it all up- our Jumper Jerky Junior is great for this
  • A mini breadboard to set it all out on

Part 1: Creating your RGB LED API

We're going to be putting together a very simple API which uses special URLs to control the state of each of three LEDs; Red, Blue and Green.

Visiting one of these URLs will set the desired LED to the desired state, to keep things simple we're only going to deal with On and Off. Here are some examples of the URLs involved, which should hopefully give you an idea how simple this can be:

  • /led/red/on
  • /led/red/off
  • /led/green/on
  • /led/all/off

The Code

We'll start with some very simple and very direct code:

LED_RED = 22

from flask import Flask
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setup(LED_RED, GPIO.OUT)
app = Flask(__name__)

@app.route("/led/red/on")
def turn_red_led_on():
  GPIO.output(LED_RED, 1)
  return 'LED On: red'

@app.route("/led/red/off")
  def turn_red_led_off():
    GPIO.output(LED_RED, 0)
    return 'LED Off: red'

app.run(host='0.0.0.0')

In this example I have provided the very basic building blocks of an RGB LED Web App. First we pick a GPIO pin for LED_RED, our red LED:

LED_RED = 22

We use all caps in the variable name to indicate that it's a "constant", and that its value shouldn't ever change.

Required Modules

Next we import the stuff we need to make it all work:

from flask import Flask
import RPi.GPIO as GPIO

Flask is a simple web framework for Python. It lets you very quickly build a Python App which will act like a web server and respond to URLs.

RPi.GPIO is the best way to access your Raspberry Pi's GPIO pins in Python- and I'm sure you'll be familliar with it by now!

Initialising

Next, let's set everything up:

GPIO.setmode(GPIO.BCM)
GPIO.setup(LED_RED, GPIO.OUT)
app = Flask(__name__)

GPIO gets initialised with GPIO.BCM because we want to use nice, recognisable BCM pin numbers in our code. We also want to make sure our LED_RED is set up as an output. An instance of Flask is created as our App, it's pretty standard to pass __name__ into Flask when initialising but there's no need to worry why for now.

Handling Routes

Finally we get to the meat of a Flask app; the route handling. Routes in Flask are handled using a very clever Python construct called a "decorator".

Decorators are always prefixed with an @ symbol and at a very basic level they're just a pretty way of writing a function which takes another function as an argument.

In this case, we're using the @app.route decorator, which takes our route as its first argument and then is also passed the function immediately beneath it- turn_red_led_on.

This is equivilent to writing:

def turn_red_led_on():
  GPIO.output(LED_RED, 1)
  return 'LED On: red'

app.add_url_rule("/","index",turn_red_led_on)

But it's generally accepted as tidier to write it like this, which keeps everything together:

@app.route("/led/red/on")
def turn_red_led_on():
  GPIO.output(LED_RED, 1)
  return 'LED On: red'

Either way you write it, the logic is simple. A route means that any URL requested that matches it ( in this case /led/red/on ) will result in the corresponding function being run. So every time to visit the url /led/red/on in your browser, the function turn_red_led_on will be called.

But all this code is ugly, so let's make it cleaner

Right now the code might not look too bad, but what if you add Green and Blue LEDs to the mix? You now have 6 routes and 6 functions when it's easy enough to have just one:

LEDS = {
  'red':  22,
  'blue': 23,
  'green':24
}

from flask import Flask
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
app = Flask(__name__)

for color in leds.keys():
    GPIO.setup(leds[color], GPIO.OUT)

@app.route("/led/<color>/<state>")
def set_led(color, state):
    if color in leds.keys():
      if state == 'on':
          GPIO.output(leds[color], 1)
          return 'LED On: {}'.format(color)
      else:
          GPIO.output(leds[color], 0)
          return 'LED Off: {}'.format(color)

    return 'Invalid LED: {}'.format(color)

app.run(host='0.0.0.0')

So, what's changed?

Collecting our LEDs

Because we now want to deal with 3 LEDs, instead of one, we'll collect them together into a dictionary. This will let us relate the name of the LED to the GPIO pin we want it to use:

LEDS = {
  'red':  22,
  'blue': 23,
  'green':24
}

Making them all outputs

Now we have 3 LEDs, and a handy-dandy dictionary, we can iterate through it to make them all outputs:

for color in leds.keys():
    GPIO.setup(leds[color], GPIO.OUT)

Handling the on/off URLs

And, finally, we can handle their on/off states with one route. This time, however, we've introduced parameters into the route which look like <color> or <led>. Anything placed in angle brackets in your route will become a parameter which is then passed to the handler function.

@app.route("/led/<color>/<state>")

Because we've put both <color> and <state> in our URL, we'll need to give our new set_led function two parameters. It doesn't actually matter what these are called, but keeping the names consistent is generally a good idea.

Now we have a shiny new function for turning LEDs on and off. We need only check to see if a LED exists in the collection, and if it does get its GPIO pin number and toggle it on or off depending on the value of state

def set_led(color, state):
    if color in leds.keys():
      if state == 'on':
          GPIO.output(leds[color], 1)
          return 'LED On: {}'.format(color)
      else:
          GPIO.output(leds[color], 0)
          return 'LED Off: {}'.format(color)

    return 'Invalid LED: {}'.format(color)

Run, run, run!

The final line makes your Web App start running:

app.run(host='0.0.0.0')

The host of 0.0.0.0 has a special meaning hee- it makes sure that Flask doesn't only serve locally, so you can access the resulting website at: http://:5000/

If you want something to show up at '/' ( the root of the website ), you can add this little snippet right before app.run:

app.add_url_rule("/", "index", lambda: 'Hello World')

So, what about toggling all the LEDs?

I think you can figure that out by yourself! You an opt to create a new handler to catch /led/all/<on/off> or you can add a special case to the set_led handler. It's up to you!

Getting it running.

Save this code to a file- rgb.py- and run with:

sudo python rgb.py

You should see something like:

* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

Replace the 0.0.0.0 with the IP Address or Hostname of your Raspberry Pi, and stick the resulting URL into your web browser. Don't forget, you'll need to visit /led/red/onor/led/red/off` for something to happen!

Part 2: Adding a GUI

At this point you've probably realised that visiting URLs isn't exactly the best way to control LEDs in your browser. It's tedious, it's error prone and its ugly! Let's add a basic GUI to make the process simpler.

To do this we're going to use Javascript, and a library called jQuery which adds a whole host of functionality and expressiveness to Javascript that we're going to need. We'll only touch lightly upon these things, and this certainly wont be a useful introduction to either, but at the end of this section you should have a working GUI!

Buttons!

Before we start writing any code, we're going to need some sort of user-interface elements. For this example, we'll use some buttons!

Create a new folder called "static" alongside your rgb.py, then make a new file "ui.html" and paste in this HTML:

<!doctype html>
<html>
  <head>
    <title>RGB Web GUI</title>
  </head>
  <body>
    <a href="/led/red/on">Turn Red LED ON</a>
    <a href="/led/red/off">Turn Red LED OFF</a>
  </body>
</html>

What's magical about this HTML is that it uses a practise called "graceful degredation". The idea is that, even without Javascript to help it, this interface will work perfectly fine- it's just basic links to those URLs we created earlier.

Fire up your app ( sudo python ./rgb.py ) and add /ui.html to the end of the URL your used before. You should see two very plain and very boring links, one will turn your LED on and one will turn it off.

A couple of problems with this approach should jump out at you- first: it's ugly, really ugly, second: every time you turn an LED on or off you need to click back in your browser to get back to the GUI.

Making Your Buttons Clever

This is where jQuery comes in. jQuery can "intercept" your button click and turn it into a completely different scripted action that runs behind the scenes- you'll never leave the page!

<!doctype html>
<html>
  <head>
    <title>RGB Web GUI</title>
  </head>
  <body>
    <a href="/led/red/on">Turn Red LED ON</a>
    <a href="/led/red/off">Turn Red LED OFF</a>
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
    <script type="text/javascript">
      jQuery(document).ready(function(){ // Triggers when the document is ready

        jQuery('a').on('click',function(e){ // Bind to the "Click" event of all a tags
          e.preventDefault(); // Prevent leaving the page
          jQuery.ajax({
            url: jQuery(this).attr('href'),
            cache: false
          });
        });

      })
    </script>
  </body>
</html>

Magic! If you paste the above code into your ui.html, replacing what you had before you should find that the Click event we've created will override the links default behaviour, replacing it with a behind-the-scenes jQuery.ajax of the same URL. cache is set to false, since we want to make sure the URL is requested!

Making Your Buttons Pretty

Clever is all well and good, but what we really want is something that looks pretty. CSS to the rescue!

If you haven't used CSS before, don't worry we're only touching upon it lightly to make things pretty. The following CSS will make our buttons into red rectangles 200x100px in size, with the text vertically ( line-heigt ) and horizontally ( text-align ) centered.

a {
  display: block;
  float: left;
  text-align: center;
  color: white;
  background: red;
  line-height: 100px;
  width: 200px;
  height: 100px;
  font-size: 22px;
  margin: 5px;
  text-decoration: none;
}

RGB LED Web UI

That's all folks!

Search above to find more great tutorials and guides.

Plasma 2040

Swathe everything in rainbows with this all-in-one, USB-C powered controller for WS2812/Neopixel and APA102/Dotstar addressable LED strip.