MicroPython Web Server Guide | ESP32, RPi Pico Examples

In this guide, learn how to control GPIOs and read sensor data remotely using a MicroPython web server. Examples here are explained using Raspberry Pi Pico W and ESP32 development boards. The web server code discussed here is robust and can automatically make reconnection attempts in case of connection failure. The code can be easily adapted for other development boards such as the ESP8266.

Your microcontroller has to be connected to the same network as your computer or mobile phone to access the web server from a browser.

This article explains a synchronous web server. To create an asynchronous web server, visit our guide – Raspberry Pi Pico W Asynchronous Web Server – MicroPython Code.

Brief Overview of a Web Server

In the context of embedded systems and IoT, a web server is a specialized software application running on a microcontroller or microprocessor that enables the device to respond to HTTP (Hypertext Transfer Protocol) requests. HTTP is a protocol used for transmitting data on the World Wide Web.

So, a web server running on a microcontroller helps to communicate and exchange information with other devices, typically through a web browser. This can be used for remote monitoring and control. For example, you might access the settings of a smart thermostat or a network-connected sensor through a web browser. Behind the scenes, a web server will handle the communication and serve the requested web pages.

Prerequisites

The following components will be used in this guide:

  • Raspberry Pi Pico W development board.
  • ESP32 development board. ESP-WROOM-32 is used here, but you can select any alternative.
  • LEDs and current limiting resistors.

You will also have to flash MicroPython firmware on your development board. If you are using ESP32, read these guides:

To flash and run MicroPython firmware on Raspberry Pi Pico / Pico W, read:

MicroPython Web Server Example 1: Control GPIOs in ESP32

This web server example will display a graphical user interface (GUI) in a browser which will give control of the GPIOs of ESP32.

The schematic below shows how to connect two LEDs to the GPIOs of ESP32. I have used an ESP-WROOM-32 board in this tutorial, but any other ESP32 board will also work.

Connect two LEDs to ESP32 as shown:

Schematic of ESP32 with LEDS connected to GPIO22 and GPIO23

Make sure to use proper current limiting resistors with the LEDs.

Uploading Code – ESP32 MicroPython Web Server

After the connections are done and the MicroPython .bin file has been uploaded to ESP32, you can proceed with uploading the code to ESP32. The code will run a web server in ESP32 and listen for HTTP requests from clients.

The steps below explain how to upload web server MicroPython code to ESP32 using Thonny IDE.

In Thonny IDE, set the interpreter to ESP32 by navigating to Tools>Options>Interpreter.

Create a new file by clicking on File>New and paste the following MicroPython code for the web server.

from machine import Pin
import network
import socket
import time

# Define GPIO pins
GPIO1 = Pin(22, Pin.OUT)
GPIO2 = Pin(23, Pin.OUT)

# Initialize GPIO states
GPIO1.value(0)  # OFF
GPIO2.value(0)  # OFF

GPIO1_state = "GPIO 1 is OFF"
GPIO2_state = "GPIO 2 is OFF"

#WiFi credentials
ssid = 'YOUR_SSID'
password = 'YOUR_PASSWORD'

wlan = network.WLAN(network.STA_IF)

#function to connect to Wi-Fi network
def cnctWifi():
    wlan.active(True)
    print('Attempting to connect to the network...')
    wlan.connect(ssid, password)        
    max_wait = 10
    while max_wait > 0 and not wlan.isconnected():
        max_wait -= 1
        print('waiting for connection...')
        time.sleep(1)
    
    # Manage connection errors
    if not wlan.isconnected():
        print('Network Connection has failed')
    else:
        print('Connected to the network successfully.')
        status = wlan.ifconfig()
        print( 'Enter this address in browser = ' + status[0] )

#HTML + CSS for webpage
html = """<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>MicroPython Web Server</title>
  <style>
    html {
      font-family: Arial;
      display: inline-block;
      margin: 0px auto;
      text-align: center;
    }
    
    h1 {
      font-family: Arial;
      color: #2551cc;
    }
    
    .button1,
    .button2 {
      -webkit-border-radius: 10;
      -moz-border-radius: 10;
      border-radius: 10px;
      font-family: Arial;
      color: #ffffff;
      font-size: 30px;
      padding: 10px 20px 10px 20px;
      text-decoration: none;
      display: inline-block;
      margin: 5px;
    }
    
    .button1 {
      background: #339966;
    }
    
    .button2 {
      background: #993300;
    }
  </style>
</head>

<body>
  <h1>MicroPython Web Server</h1>
  <p>%s</p>
  <p>
    <a href="/GPIO1/on"><button class="button1">GPIO 1 ON</button></a>
    <a href="/GPIO1/off"><button class="button2">GPIO 1 OFF</button></a>
  </p>
  <p>%s</p>
  <p>
    <a href="/GPIO2/on"><button class="button1">GPIO 2 ON</button></a>
    <a href="/GPIO2/off"><button class="button2">GPIO 2 OFF</button></a>
  </p>
</body>
</html>
"""
# Connect to Wi-Fi
cnctWifi()
    
# Set up socket for web server
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.setblocking(0)
s.bind(addr)
s.listen(1)

print('listening on', addr)

# Main loop for handling client requests
while True:
    if not wlan.isconnected():
        print("Connection failed. Trying to reconnect")
        wlan.disconnect()
        cnctWifi()
    if wlan.isconnected():
        try:
            cl, addr = s.accept()
            print('client connected from', addr)
            request = cl.recv(1024)
            print(request)

            request = str(request)
            GPIO1_on = request.find('/GPIO1/on')
            GPIO1_off = request.find('/GPIO1/off')
            GPIO2_on = request.find('/GPIO2/on')
            GPIO2_off = request.find('/GPIO2/off')

            if GPIO1_on == 6:
                print("GPIO 1 is on")
                GPIO1.value(1)
                GPIO1_state = "GPIO 1 is ON"

            if GPIO1_off == 6:
                print("GPIO 1 is off")
                GPIO1.value(0)
                GPIO1_state = "GPIO 1 is OFF"

            if GPIO2_on == 6:
                print("GPIO 2 is on")
                GPIO2.value(1)
                GPIO2_state = "GPIO 2 is ON"

            if GPIO2_off == 6:
                print("GPIO 2 is off")
                GPIO2.value(0)
                GPIO2_state = "GPIO 2 is OFF"

            response = html % (GPIO1_state, GPIO2_state)
            cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
            cl.send(response)
            cl.close()

        except:
            pass
    time.sleep(0.1)
Code language: Python (python)

The code is explained shortly below. You will have to change the Wi-Fi credentials to your own in the code by replacing YOUR_SSID and YOUR_PASSWORD.

ssid = 'YOUR_SSID'
password = 'YOUR_PASSWORD'Code language: JavaScript (javascript)

Save the file by clicking on File>Save as or by using the shortcut Ctrl+Shift+S. Select MicroPython Device on the window that appears to save the code to ESP32.

save to MicroPython device on Thonny

Set the filename as webserver.py in the next dialog box. You can save it as main.py if you wish your code to run automatically when ESP32 boots.

Control GPIOs from any Browser

In Thonny IDE, click the shortcut F5 or the Run button to run the web server script that you saved to ESP32.

run-button-Thonny-1

If you face an error while running the script, refer to the troubleshooting section below.

Thereafter, you must see some information in the shell of Thonny IDE. The output messages will help you to know if a proper connection is established.

When a connection is successfully established, you will be presented with an IP address. This IP address has to be entered in a browser connected to the same network.

When you enter the IP address in a browser, you should see the webpage as shown below.

When we click a button on the webpage, an HTTP request will be made. For example, if we click the button to switch on GPIO1, the request will be like – 192.168.xx.xx//GPIO1/on. This can be seen below in the screenshot taken after clicking on the button ‘GPIO 1 ON’.

Similarly, each button click will send a unique request, and in response, the status of the toggled GPIO will also be displayed on the webpage.

MicroPython Code Explanation

Firstly, import the necessary libraries/modules The Pin class from the machine module is used to interact with GPIO. The network module provides functionality related to network connections via Wi-Fi. The socket module is used for working with sockets, which facilitate the sending and receiving of data over a network. The time module is used for introducing delays in the code.

from machine import Pin
import network
import socket
import timeCode language: JavaScript (javascript)

We then initialize two GPIO pins GPIO1 and GPIO2 with ESP32 PINs 22 and 23, respectively. Both pins are configured for output (Pin.OUT). The initial state of both pins is set to “OFF” (logic level 0), and corresponding state variables (GPIO1_state and GPIO2_state) are defined with their initial states as strings.

# Define GPIO pins
GPIO1 = Pin(22, Pin.OUT)
GPIO2 = Pin(23, Pin.OUT)

# Initialize GPIO states
GPIO1.value(0)  # OFF
GPIO2.value(0)  # OFF

GPIO1_state = "GPIO 1 is OFF"
GPIO2_state = "GPIO 2 is OFF"Code language: PHP (php)

You should replace YOUR_SSID with the actual name of your Wi-Fi network and YOUR_PASSWORD with the actual password for that network. These credentials are necessary for the device to successfully connect to the Wi-Fi network.

ssid = 'YOUR_SSID'
password = 'YOUR_PASSWORD'Code language: JavaScript (javascript)

The next line of code creates a WLAN interface in Station mode (STA_IF) and assigning it to the variable wlan

wlan = network.WLAN(network.STA_IF)

There are two Wi-Fi interfaces available: one for the station and one for the access point. In station mode, ESP32 can connect to a Wi-Fi router. In the Access Point mode, it can act as a WiFi hotspot.

Connecting to Wi-Fi

Next, define a function cnctWifi() to make robust connections to the Wi-Fi network. The function can make multiple attempts to connect and print an IP address which can be used to access the web server.

def cnctWifi():
    wlan.active(True)
    print('Attempting to connect to the network...')
    wlan.connect(ssid, password)        
    max_wait = 5
    while max_wait > 0 and not wlan.isconnected():
        max_wait -= 1
        print('waiting for connection...')
        time.sleep(1)
    
    # Manage connection errors
    if not wlan.isconnected():
        print('Network Connection has failed')
    else:
        print('Connected to the network successfully.')
        status = wlan.ifconfig()
        print( 'Enter this address in browser = ' + status[0] )Code language: Python (python)

Thereafter, define a multiline string (html) containing both HTML and CSS code to create a simple web page. The code contains the HTML structure, CSS styling, Buttons with hyperlinks, and placeholders for dynamic content. The %s within code acts as a placeholder for dynamic content that will be inserted at those positions. The dynamic content here is the status of the GPIOs that will be changed.

Socket Setup

Network socket diagram. Image credit: IBM

socket is one endpoint of two-way communication. We set up a basic server socket with the following lines of code.

addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.setblocking(0)
s.bind(addr)
s.listen(1)Code language: JavaScript (javascript)

Here is a brief explanation:

  1. addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]:
    • Retrieves address information for ‘0.0.0.0’ (which typically means listen on all available interfaces) on port 80 and stores it in the addr variable.
  2. s = socket.socket():
    • Creates a new socket and assigns it to the variable s.
  3. s.setblocking(0):
    • Sets the socket to non-blocking mode, meaning it won’t block the program’s execution while waiting for operations.
  4. s.bind(addr):
    • Binds the socket to the address specified in the addr variable (in this case, ‘0.0.0.0’ on port 80).
  5. s.listen(1):
    • Configures the socket to listen for incoming connections with a backlog of 1, allowing the server to accept one connection at a time.

An infinite while loop maintains a connection to Wi-Fi and does client request handling. Each time the loop runs, it first checks if the Wi-Fi connection is active. If not, it makes attempts to reconnect.

if not wlan.isconnected():
        print("Connection failed. Trying to reconnect")
        wlan.disconnect()
        cnctWifi()Code language: Python (python)

In a try block, the code handles incoming client requests, interprets them to control GPIO pins, and sends back an HTTP response. Let’s break down the key parts:

Accept a Client Connection

        cl, addr = s.accept()
        print('client connected from', addr)Code language: PHP (php)
  • Accepts an incoming connection (s.accept()).
  • cl is a new socket object representing the connection.
  • addr is the address of the client.

Receive and Process Request

        request = cl.recv(1024)
        print(request)Code language: PHP (php)

Receives the client’s request data (up to 1024 bytes) and prints it in the shell of the IDE.

Parsing the Request for GPIO Control

        request = str(request)
        GPIO1_on = request.find('/GPIO1/on')
        GPIO1_off = request.find('/GPIO1/off')
        GPIO2_on = request.find('/GPIO2/on')
        GPIO2_off = request.find('/GPIO2/off')Code language: JavaScript (javascript)
  • Converts the request to a string.
  • Uses the find method to locate specific patterns in the request string, indicating GPIO control commands.

GPIO State Changes

        if GPIO1_on == 6:
            print("GPIO 1 is on")
            GPIO1.value(1)
            GPIO1_state = "GPIO 1 is ON"
            # ... (similar blocks for other GPIO states)Code language: PHP (php)
  • Checks if specific patterns indicating GPIO state changes are found.
  • If found, it prints a corresponding message, updates the GPIO states, and sets state-related variables.

Preparing an HTTP Response

response = html % (GPIO1_state, GPIO2_state)
cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
cl.send(response)
Code language: JavaScript (javascript)
  • Prepares an HTTP response using the HTML template (html) with dynamic content replaced by GPIO states. These variables will update the %s placeholders as discussed earlier.
  • Sends an HTTP header indicating a successful response and the content to the client.

Closing the Connection

Finally, cl.close() closes the connection after processing the client’s request. A delay of 0.1 seconds ( time.sleep(0.1) ) is added at the end of the loop to prevent high CPU usage and to ensure stability while working with an IDE.

Advertisement

MicroPython Web Server Example 2: Read Sensor using Raspberry Pi Pico W

In the first example, we learned how to control GPIOs and view their status via a browser. In this example, the internal temperature sensor in Raspberry Pi Pico W is monitored using a MicroPython web server.

RASPBERRY PI PICO W ONBOARD INTERNAL TEMPERATURE SENSOR

Upload Web Server Code to Raspberry Pi Pico W

The steps to upload code are explained using Thonny IDE.

Connect Raspberry Pi Pico W to the computer. Open Thonny IDE and set the interpreter to Raspberry Pi Pico by navigating to Tools>Options>Interpreter.

Create a new file by clicking on File>New and paste the following MicroPython code for the web server.

from machine import ADC
import network
import socket
import time

# WiFi credentials
ssid = 'YOUR_SSID'
password = 'YOUR_PASSWORD'

wlan = network.WLAN(network.STA_IF)

adc = ADC(4)  # ADC pin on Pico W for internal temperature sensor

# Function to connect to Wi-Fi network
#function to connect to Wi-Fi network
def cnctWifi():
    wlan.active(True)
    print('Attempting to connect to the network...')
    wlan.connect(ssid, password)        
    max_wait = 10
    while max_wait > 0:
        if wlan.isconnected():
            break
        max_wait -= 1
        print('waiting for connection...')
        time.sleep(1)
    
    # Manage connection errors
    if not wlan.isconnected():
        print('Network Connection has failed')
    else:
        print('Connected to the network successfully.')
        status = wlan.ifconfig()
        print( 'Enter this address in browser = ' + status[0] )

# HTML + CSS for webpage
html = """<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>MicroPython Web Server</title>
  <style>
    html {
      font-family: Arial;
      display: inline-block;
      margin: 0px auto;
      text-align: center;
    }
    
    h1 {
      font-family: Arial;
      color: #2551cc;
    }
  </style>
</head>

<body>
  <h1>MicroPython Web Server</h1>
  <p>Temperature:</p>
  <p> %s &#8451; </p>
  <p> %s &#8457; </p>
  
</body>
</html>
"""

# Connect to Wi-Fi
cnctWifi()

# Set up socket for web server
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.settimeout(0.5)
s.bind(addr)
s.listen(1)

print('Listening on', addr)

# Main loop for handling client requests
while True:
    if not wlan.isconnected():
        print("Connection failed. Trying to reconnect")
        wlan.disconnect()
        cnctWifi()
    try:
        cl, addr = s.accept()
        print('Client connected from', addr)
        request = cl.recv(1024)
        print(request)

        ADC_voltage = adc.read_u16() * (3.3 / (65535))  # Convert ADC reading to voltage
        temp_celcius = 27 - (ADC_voltage - 0.706)/0.001721  # Convert voltage to temperature
        temp_celcius= round(temp_celcius, 2)
        temp_celcius=temp_celcius-15
        temp_fahrenheit=32+(1.8*temp_celcius)
        
        response = html % (temp_celcius, temp_fahrenheit)
        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
        cl.send(response)
        cl.close()

    except:
        pass
    time.sleep(0.1)Code language: Python (python)

You will have to change the Wi-Fi credentials to your own in the code by replacing YOUR_SSID and YOUR_PASSWORD. The code is almost similar to our previous MicroPython web server example with a few modifications.

We create an adc instance to read the voltage from the fifth ADC channel of Raspberry Pi Pico W (ADC4 refers to the fifth channel).

adc = ADC(4)

The following code reads the ADC voltage and converts it into temperature data.

        ADC_voltage = adc.read_u16() * (3.3 / (65535))  # Convert ADC reading to voltage
        temp_celcius = 27 - (ADC_voltage - 0.706)/0.001721  # Convert voltage to temperature
        temp_celcius= round(temp_celcius, 2)
        temp_celcius=temp_celcius-15
        temp_fahrenheit=32+(1.8*temp_celcius)Code language: PHP (php)

There is a temperature sensor in the Raspberry Pi Pico W connected to the 5th ADC channel. You can learn more by reading:

Save the web server MicroPython file by clicking on File>Save as or using the shortcut Ctrl+Shift+S. Select Raspberry Pi Pico on the dialog box that appears to save the code to your Pico W.

Thonny Save to

Set the filename as webserver.py in the next dialog box. You can save it as main.py if you wish your code to run automatically when Pico W boots.

Read Temperature Sensor Data on Browser

In Thonny IDE, click the shortcut F5 or the Run button to run the web server script that you saved to Raspberry Pi Pico W.

run-button-Thonny-1

If you face any error while running the script, refer to the troubleshooting section below.

Some information will be printed in the shell of the IDE to assist you in determining whether a proper connection has been made. You will be shown an IP address after a connection is successfully made, which you must enter into a browser that is also connected to the same network.

When you enter the IP address, you should see the webpage below. The webpage should display the temperature data in the Celcius and Fahrenheit scale.

Troubleshooting: “OSError: [Errno 98] EADDRINUSE” & Others

In some instances such as re-running the web server script shortly after stopping it, you may be presented with an error such as “OSError: [Errno 98] EADDRINUSE”. The image below shows the error screenshot in the shell of Thonny IDE.

This is due to how TCP/IP network stack connections are managed. A socket is a combination of an IP address and a port number. There is a timeout of around 30 and 120 seconds for reuse of a particular socket. This helps in keeping the connection robust to mitigate packet-related problems. You can read more about this error in this thread in the MicroPython forum.

When this error shows up, you can try to reset Raspberry Pi Pico or ESP32(by pressing the reset button), or disconnect and reconnect the USB cable physically.

After entering the IP address in a browser, if you do not see any webpage, then recheck that you are connected to the same network on both devices.

Wrapping Up

In this guide, we discussed two examples of creating a MicroPython web server. An ESP32 MicroPython web server to control GPIOS was demonstrated. Using Raspberry Pi Pico W, we saw how we can monitor sensors remotely via a web browser. Connecting to the web server from anywhere via the internet requires “port forwarding” and is out of the scope of this article.

You can further experiment by displaying the IP address in an OLED display so that you can easily connect to the web server. Instead of the internal temperature sensor in Raspberry Pi Pico, you can use sensors such as the BME280, BMP280, DHT22, or DS18B20.

I hope you found this tutorial helpful. Please leave your queries and suggestions in the comments below.

Also read: Control relays using a web server on Raspberry Pi Pico W


Posted

in

by

Comments

One response to “MicroPython Web Server Guide | ESP32, RPi Pico Examples”

  1. Totto-R Avatar
    Totto-R

    Hello Abhilekh Das, you have put together a really impressive website with lots of ideas. Really good. I have a question or request regarding the current post. Currently your program always waits for the website to be called with try:
    cl, addr = s.accept()” I have a digital counter in the background on a GPIO port that is supposed to provide additional input. However, this only works if the website has been activated. Do you have an idea for this, how i can implement a additional counter im the Background too ? thanks a lot Totto-R

Leave a Reply

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