Learn how to build a web server to control the ESP32 or ESP8266 outputs using MicroPython framework. As an example we’ll build a web server with ON and OFF buttons to control the on-board LED of the ESP32/ESP8266. We’ll use sockets and the Python socket API.

Prerequisites
To program the ESP32 and ESP8266 with MicroPython, we use uPyCraft IDE as a programming environment. Follow the next tutorials to install uPyCraft IDE and flash MicroPython firmware on your board:
- Install uPyCraft IDE: Windows PC, MacOS X, or Linux Ubuntu
- Flash/Upload MicroPython Firmware to ESP32 and ESP8266
If this is your first time dealing with MicroPython you may find these next tutorials useful:
- Getting Started with MicroPython on ESP32 and ESP8266
- MicroPython Programming Basics with ESP32 and ESP8266
- MicroPython with ESP32 and ESP8266: Interacting with GPIOs
Parts required
For this tutorial you need an ESP32 or ESP8266 board:
- ESP32 DEVKIT DOIT board – read ESP32 Development Boards Review and Comparison
- ESP8266-12E NodeMCU Kit – read Best ESP8266 Wi-Fi Development Board
Preparing the Files
Connect the ESP32 or ESP8266 board to your computer. Open uPyCraft IDE, and go to Tools > Serial and select the serial port.

You should see the files on the ESP32/ESP8266 board on the device folder. By default, when you burn MicroPython firmware, a boot.py file is created.
For this project you’ll need a boot.py file and a main.py file. The boot.py file has the code that only needs to run once on boot. This includes importing libraries, network credentials, instantiating pins, connecting to your network, and other configurations.
The main.py file will contain the code that runs the web server to serve files and perform tasks based on the requests received by the client.
Creating the main.py file on your board
1. Press the “New file” button to create a new file.


2. Press the “Save file” button to save the file in your computer.
3. A new window opens, name your file main.py and save it in your computer:

4. After that, you should see the following in your uPyCraft IDE (the boot.py file in your device and a new tab with the main.py file):

5. Click the “Download and run” button to upload the file to your ESP board:

6. The device directory should now load the main.py file. Your ESP has the file main.py stored.

boot.py
Copy the following code to the ESP32/ESP8266 boot.py file.
# WELCOME TO gND_tO_vCC!!
try:
import usocket as socket
except:
import socket
from machine import Pin
import network
import esp
esp.osdebug(None)
import gc
gc.collect()
ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'
station = network.WLAN(network.STA_IF)
station.active(True)
station.connect(ssid, password)
while station.isconnected() == False:
pass
print('Connection successful')
print(station.ifconfig())
led = Pin(2, Pin.OUT)
As mentioned previously, we create our web server using sockets and the Python socket API. The official documentation imports the socket library as follows:
try:
import usocket as socket
except:
import socket
We need to import the Pin class from the machine module to be able to interact with the GPIOs.
from machine import Pin
After importing the socket library, we need to import the network library. The network library allows us to connect the ESP32 or ESP8266 to a Wi-Fi network.
import network
The following lines turn off vendor OS debugging messages:
import esp
esp.osdebug(None)
Then, we run a garbage collector:
import gc
gc.collect()
A garbage collector is a form of automatic memory management. This is a way to reclaim memory occupied by objects that are no longer in used by the program. This is useful to save space in the flash memory.
The following variables hold your network credentials:
ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'replace_with_your_password'
You should replace the words highlighted in red with your network SSID and password, so that the ESP is able to connect to your router.
Then, set the ESP32 or ESP8266 as a Wi-Fi station:
station = network.WLAN(network.STA_IF)
After that, activate the station:
station.active(True)
Finally, the ESP32/ESP8266 connects to your router using the SSID and password defined earlier:
station.connect(ssid, password)
The following statement ensures that the code doesn’t proceed while the ESP is not connected to your network.
while station.isconnected() == False:
pass
After a successful connection, print network interface parameters like the ESP32/ESP8266 IP address – use the ifconfig() method on the station object.
print('Connection successful')
print(station.ifconfig())
Create a Pin object called led that is an output, that refers to the ESP32/ESP8266 GPIO2:
led = Pin(2, Pin.OUT)
main.py
Copy the following code to the ESP32/ESP8266 main.py file.
# Welcome to gnd_To_Vcc!!
def web_page():
if led.value() == 1:
gpio_state="ON"
else:
gpio_state="OFF"
html = """<html><head> <title>ESP Web Server</title> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,"> <style>html{font-family: Helvetica; display:inline-block; margin: 0px auto; text-align: center;}
h1{color: #0F3376; padding: 2vh;}p{font-size: 1.5rem;}.button{display: inline-block; background-color: #e7bd3b; border: none;
border-radius: 4px; color: white; padding: 16px 40px; text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}
.button2{background-color: #4286f4;}</style></head><body> <h1>ESP Web Server</h1>
<p>GPIO state: <strong>""" + gpio_state + """</strong></p><p><a href="/?led=on"><button class="button">ON</button></a></p>
<p><a href="/?led=off"><button class="button button2">OFF</button></a></p></body></html>"""
return html
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 80))
s.listen(5)
while True:
conn, addr = s.accept()
print('Got a connection from %s' % str(addr))
request = conn.recv(1024)
request = str(request)
print('Content = %s' % request)
led_on = request.find('/?led=on')
led_off = request.find('/?led=off')
if led_on == 6:
print('LED ON')
led.value(1)
if led_off == 6:
print('LED OFF')
led.value(0)
response = web_page()
conn.send('HTTP/1.1 200 OK\n')
conn.send('Content-Type: text/html\n')
conn.send('Connection: close\n\n')
conn.sendall(response)
conn.close()
The script starts by creating a function called web_page(). This function returns a variable called html that contains the HTML text to build the web page.
def web_page():
The web page displays the current GPIO state. So, before generating the HTML text, we need to check the LED state. We save its state on the gpio_state variable:
if led.value() == 1:
gpio_state="ON"
else:
gpio_state="OFF"
After that, the gpio_state variable is incorporated into the HTML text using “+” signs to concatenate strings.
html = """<html><head> <title>ESP Web Server</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" href="data:,"> <style>html{font-family: Helvetica; display:inline-block; margin: 0px auto; text-align: center;} h1{color: #0F3376; padding: 2vh;}p{font-size: 1.5rem;}.button{display: inline-block; background-color: #e7bd3b; border: none; border-radius: 4px; color: white; padding: 16px 40px; text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;} .button2{background-color: #4286f4;}</style></head><body> <h1>ESP Web Server</h1> <p>GPIO state: <strong>""" + gpio_state + """</strong></p><p><a href="/?led=on"><button class="button">ON</button></a></p> <p><a href="/?led=off"><button class="button button2">OFF</button></a></p></body></html>"""
https://tpc.googlesyndication.com/safeframe/1-0-37/html/container.html
Creating a socket server
After creating the HTML to build the web page, we need to create a listening socket to listen for incoming requests and send the HTML text in response. For a better understanding, the following figure shows a diagram on how to create sockets for server-client interaction:

Create a socket using socket.socket(), and specify the socket type. We create a new socket object called s with the given address family, and socket type. This is a STREAM TCP socket:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Next, bind the socket to an address (network interface and port number) using the bind() method. The bind() method accepts a tupple variable with the ip address, and port number:
s.bind(('', 80))
https://tpc.googlesyndication.com/safeframe/1-0-37/html/container.html
In our example, we are passing an empty string ‘ ‘ as an IP address and port 80. In this case, the empty string refers to the localhost IP address (this means the ESP32 or ESP8266 IP address).
The next line enables the server to accept connections; it makes a “listening” socket. The argument specifies the maximum number of queued connections. The maximum is 5.
s.listen(5)
In the while loop is where we listen for requests and send responses. When a client connects, the server calls the accept() method to accept the connection. When a client connects, it saves a new socket object to accept and send data on the conn variable, and saves the client address to connect to the server on the addr variable.
conn, addr = s.accept()
Then, print the address of the client saved on the addr variable.
print('Got a connection from %s' % str(addr))
The data is exchanged between the client and server using the send() and recv() methods.
The following line gets the request received on the newly created socket and saves it in the request variable.
request = conn.recv(1024)
The recv() method receives the data from the client socket (remember that we’ve created a new socket object on the conn variable). The argument of the recv() method specifies the maximum data that can be received at once.
The next line simply prints the content of the request:
print('Content = %s' % str(request))
Then, create a variable called response that contains the HTML text returned by the web_page() function:
response = web_page()
Finally, send the response to the socket client using the send() and sendall() methods:
conn.send('HTTP/1.1 200 OK\n')
conn.send('Content-Type: text/html\n')
conn.send('Connection: close\n\n')
conn.sendall(response)
https://tpc.googlesyndication.com/safeframe/1-0-37/html/container.html
In the end, close the created socket.
conn.close()
Testing the Web Server
Upload the main.py and boot.py files to the ESP32/ESP8266. Your device folder should contain two files: boot.py and main.py.
After uploading the files, press the ESP EN/RST on-board button.

After a few seconds, it should establish a connection with your router and print the IP address on the Shell.

Open your browser, and type your ESP IP address you’ve just found. You should see the web server page as shown below.

https://tpc.googlesyndication.com/safeframe/1-0-37/html/container.html
When you press the ON button, you make a request on the ESP IP address followed by /?led=on. The ESP32/ESP8266 on-board LED turns on, and the GPIO state is updated on the page.
Note: some ESP8266 on-board LEDs turn on the LED with an OFF command, and turn off the LED with the ON command.

https://tpc.googlesyndication.com/safeframe/1-0-37/html/container.html
When you press the OFF button, you make a request on the ESP IP address followed by /?led=off. The LED turns off, and the GPIO state is updated.

Note: to keep this tutorial simple, we’re controlling the on-board LED that corresponds to GPIO 2. You can control any other GPIO with any other output (a relay, for example) using the same method. Also, you can modify the code to control multiple GPIOs or change the HTML text to create a different web page.
Wrapping Up
This tutorial showed you how to build a simple web server with MicroPython firmware to control the ESP32/ESP8266 GPIOs using sockets and the Python socket library. If you’re looking for a web server tutorial with Arduino IDE, you can check the following resources:
If you’re looking for more projects with the ESP32 and ESP8266 boards, you can take a look at the following:
- 20+ ESP32 Projects and Tutorials
- 30+ ESP8266 Projects
Great help, thank you!
I also tried externalizing the html to main.html and then import it with
with codecs.open(“main.html”, ‘r’) as f:
html = f.read()
Works fine… BUT: gpio_state is not evaluated as variable but as a string.
Could not get rid of this behaviour. Can anyone help?
LikeLike