How to control Raspberry Pi GPIO via http web server

control RPi GPIO via http server

You bought your Raspberry Pi, and managed to create a python script to turn on/off an LED via GPIO. Then you are wonder “How can I control the GPIO via a web page”? This is a common questions been asked repeatedly on Raspberry Pi StackExchange and Raspberry Pi user groups. Some would say you need Flask web framework, other would suggest to install LAMP (Linux, Apache, PHP and MySQL), very few aware that you can run a simple HTTP web server using python’s build-in http.server library, without installation of Flask web framework or LAMP stacks.

Getting data from Raspberry Pi and control Raspberry Pi GPIO

Before we discuss http.server, let’s assumed that we have a little python script simple_gpio.py that read Raspberry Pi’s GPU temperature from the Raspberry Pi, and it also control an LED connected to the Raspberry Pi. Just like every new Raspberry Pi user has experienced when they got their Raspberry Pi the fist time.

Reading GPU temperature is used to demonstrate of getting some data from Raspberry Pi, I choose to read the GPU temperature because it is kind of unique to Raspberry Pi and is available on all versions of Raspberry Pi, whether it is early version of the Raspberry Pi or the latest Raspberry Pi zero W. Raspberry Pi’s GPU temperature can be read using a bash shell command:

/opt/vc/bin/vcgencmd measure_temp
temp=41.5'C

We will use python os.popen function to execute the shell system command within the python environment and display the result. The data return from the shell command is a string in the form of temp=41.5'C, and we are only interested in the actual numeric value of temperature reading, so we will use python slicing to get extract the value.

We will ask user to input either 1 or 0 to turn On or Off the LED that is connected to Raspberry Pi GPIO 18 (Raspberry Pi header P1 pin 12).

simple_gpio.py

import RPi.GPIO as GPIO
import os

# Read data from Raspberry Pi (specifically read GPU temperature)
temp = os.popen("/opt/vc/bin/vcgencmd measure_temp").read()
print("GPU temperature is {}".format(temp[5:]))

# GPIO setup
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(18,GPIO.OUT)

# Turn on/off LED based on user input
try:
    while True:
        user_input = input("Turn LED On or Off with 1 or 0 (Ctrl-C to exit): ")
        if user_input is "1":
            GPIO.output(18,GPIO.HIGH)
            print("LED is on")
        elif user_input is "0":
            GPIO.output(18,GPIO.LOW)
            print("LED is off")
except KeyboardInterrupt:
    GPIO.cleanup()
    print("")

Control Raspberry Pi GPIO using python http.server library

The python http.server library creates and listens at the HTTP socket, dispatching the requests to a handler. This allows user to create a simple web server without installing LAMP. Although http.server is not designed for handling heavy traffic in production environment, but it is sometime the only lightweight choice for IOT applications and hardware, such as micropython pyboard or ESP8266 Wifi microcontroller where memory and cpu power is very limited for large web framework or LAMP implementation. For many Raspberry Pi users, this is a simple, easy-to-implement and effective solution for serving a web page before the needs for installing full LAMP stacks.

An HTTPServer instance can be created using the following code:

if __name__ == '__main__':
    http_server = HTTPServer((host_name, host_port), MyServer)
    print("Server Starts - %s:%s" % (host_name, host_port))

    try:
        http_server.serve_forever()
    except KeyboardInterrupt:
        http_server.server_close()

The http.server allows user to create its own http request handler for handling the GET, POST requests through the implementation of BaseHTTPRequestHandler class. In our example, MyServer class is our HTTP request handler which inherits from BaseHTTPRequestHandler, and we we will need to implement our own GET and POST requests handler for serving up a web page.The do_GET() and do_POST() methods are where we integrate our GPU temperature reading and GPIO control interface codes.

simple_webserver.py

import RPi.GPIO as GPIO
import os
from http.server import BaseHTTPRequestHandler, HTTPServer


host_name = '192.168.0.114'    # Change this to your Raspberry Pi IP address
host_port = 8000


class MyServer(BaseHTTPRequestHandler):
    """ A special implementation of BaseHTTPRequestHander for reading data from
        and control GPIO of a Raspberry Pi
    """

    def do_HEAD(self):
        """ do_HEAD() can be tested use curl command 
            'curl -I http://server-ip-address:port' 
        """
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()

    def _redirect(self, path):
        self.send_response(303)
        self.send_header('Content-type', 'text/html')
        self.send_header('Location', path)
        self.end_headers()

    def do_GET(self):
        """ do_GET() can be tested using curl command 
            'curl http://server-ip-address:port' 
        """
        html = '''
            <html>
            <body style="width:960px; margin: 20px auto;">
            <h1>Welcome to my Raspberry Pi</h1>
            <p>Current GPU temperature is {}</p>
            <form action="/" method="POST">
                Turn LED :
                <input type="submit" name="submit" value="On">
                <input type="submit" name="submit" value="Off">
            </form>
            </body>
            </html>
        '''
        temp = os.popen("/opt/vc/bin/vcgencmd measure_temp").read()
        self.do_HEAD()
        self.wfile.write(html.format(temp[5:]).encode("utf-8"))

    def do_POST(self):
        """ do_POST() can be tested using curl command 
            'curl -d "submit=On" http://server-ip-address:port' 
        """
        content_length = int(self.headers['Content-Length'])    # Get the size of data
        post_data = self.rfile.read(content_length).decode("utf-8")   # Get the data
        post_data = post_data.split("=")[1]    # Only keep the value
        
        # GPIO setup
        GPIO.setmode(GPIO.BCM)
        GPIO.setwarnings(False)
        GPIO.setup(18,GPIO.OUT)

        if post_data == 'On':
            GPIO.output(18, GPIO.HIGH)
        else:
            GPIO.output(18, GPIO.LOW)
        print("LED is {}".format(post_data))
        self._redirect('/')    # Redirect back to the root url

Implement do_GET() to handle GET request

The do_GET() method create a simple HTML template and a HTML form to display the GPU temperature and for user to click either “On” or “Off” button. whenever the user points the browser to the server’s (i.e. Raspberry Pi) IP address with a GET request, The HTML will be served, when user click on the “On” or “Off” buttons, the form data (i.e. the user input used to control the GPIO in this example) will be sent as a POST request by the browser to the server.

Implement do_POST() to handle POST request

The post request will be processed by do_POST() method on the server side, which will set the GPIO pin 18 according to user’s input. Once it is done of setting the GPIO output, the private method _redirect() will be called, which generates a 303 See Other http header and redirect back to the root URL ‘/’.

Data transmit over http.server by default are in binary, it is therefore need to be encoded for sending and be decoded for receiving from a python string using str.encode("utf-8") and str.decode("utf-8") methods.

Run the python script with the following command:

python3 simple_webserver.py

Launch your browser and point the URL to the IP address and port of your Raspberry Pi, and you should see the web page, click on On/Off buttons to control the LED that connected to your Raspberry Pi.

If you don’t know your Raspberry Pi’s IP address, run the following command to find out:

ip -4 address|grep inet

Handling multiple GET requests

The example we discussed above demonstrates how to use do_GET() and do_POST() to handle GET and POST requests. But for our simple LED control application, there is no indication on the web page whether the LED is currently on or off.

For example, we could use different URL endpoints as the GET requests to control the LED:

http://192.168.0.114:8000/       # Display the webpage without LED status
http://192.168.0.114:8000/on     # Turn on the LED and display LED is On
http://192.168.0.114:8000/off    # Turn off the LED and display LED is off

With this approach, we may not need do_POST() to handle POST request, and what we need is for do_GET() to handle multiple GET requests based on URL endpoints. We also no longer need the _redirect() method.

We replace the HTML form and the on/off buttons with two links. In order to display the LED status, we use a little bit JavaScript code to inject the LED status based on URL endpoints to the HTML page. Our new code is a little bit simpler without do_POST() method.

simple_webserver2.py

import RPi.GPIO as GPIO
import os
from http.server import BaseHTTPRequestHandler, HTTPServer


host_name = '192.168.0.114'    # Change this to your Raspberry Pi IP address
host_port = 8000


class MyServer(BaseHTTPRequestHandler):
    """ A special implementation of BaseHTTPRequestHander for reading data from
        and control GPIO of a Raspberry Pi
    """

    def do_HEAD(self):
        """ do_HEAD() can be tested use curl command 
            'curl -I http://server-ip-address:port' 
        """
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()

    def do_GET(self):
        """ do_GET() can be tested using curl command 
            'curl http://server-ip-address:port' 
        """
        html = '''
            <html>
            <body style="width:960px; margin: 20px auto;">
            <h1>Welcome to my Raspberry Pi</h1>
            <p>Current GPU temperature is {}</p>
            <p>Turn LED: <a href="/on">On</a> <a href="/off">Off</a></p>
            <div id="led-status"></div>
            <script>
                document.getElementById("led-status").innerHTML="{}";
            </script>
            </body>
            </html>
        '''
        temp = os.popen("/opt/vc/bin/vcgencmd measure_temp").read()
        self.do_HEAD()
                status = ''
        if self.path=='/':
            GPIO.setmode(GPIO.BCM)
            GPIO.setwarnings(False)
            GPIO.setup(18, GPIO.OUT)
        elif self.path=='/on':
            GPIO.output(18, GPIO.HIGH)
            status='LED is On'
        elif self.path=='/off':
            GPIO.output(18, GPIO.LOW)
            status='LED is Off'
        self.wfile.write(html.format(temp[5:], status).encode("utf-8"))

Summary

As you can see, it is quite easy to use http.server to create a simple web server and integrate your plain Raspberry Pi code with the http.server implementation. Our customised BaseHTTPRequestHandler implementation MyServer class can be easily modified to integrate whatever GET or POST requests that you need to get information from Raspberry Pi, or control GPIO via a web site based on your Raspberry Pi project. No Flask web framework is required and no installation of LAMP is required, at least, not for simple IoT project like this one.

The complete codes two python examples available at my github.

Leave a Reply

Your email address will not be published. Required fields are marked *