HTTP controlled LEDs and Relays with MicroPython and an ESP8266

MicroPython makes it easy to create remote control or remote sensing devices.  We're going to expose two pins to web-based remote control with just a few lines of code. The code here is available on GitHub

Simple GUI

Our GUI is a simple set of on/off buttons that toggle two pins.  The page shows the current state of the pins taking into account any pin inversion. 
Click on image to enlarge

My ESP8266 board has an LED on Pin 2 and a Relay on Pin 16.

API is simple GET calls

The device API is exposed as a set of simple GET calls.  You can see these in the URL bar when you click on any of the buttons.

Yeah, these URLs are kind of funky.  The code is dumb so they need to match exactly.  The server ignores any parameters other than these.  The server may be susceptible to buffer overrun attacks.  

Execution Flow

This assumes that we are connecting to the web server from a browser of some type.  The browser makes an HTTP GET call.  The ESP8266 looks for control query parameters.  It adjusts the associated pins if there were control query parameters.  The ESP8266 MicroPython server returns the HTML for the control page and a 200.

This mermaid.js diagram is available at

The YouTube Video

The main program configures and starts the web server with just two lines of code.  The first line instantiates and configures a single WebServer object. The second line just runs the server.

from config import ssid, password, hostname
""" copies of the variables"""
from connectwifi import WIFI
from webserver import WebServer
from toggle import toggle_pin

def main():
toggle_pin(2, 200, 2)
conn = WIFI(ssid, password, hostname)
toggle_pin(2, 200, 2)

server = WebServer("LED (Pin 2)", 2, False, "RELAY (Pin 16)", 16, True)

if __name__ == "__main__":

Device Configuration

We store our wi-fi credentials and hostname in a configuration file that is not checked into version control. 

ssid = "yorussid"
password = "yourpassword"
hostname = "yourhostname"

Configurable Web Server

The Web server is configured via dependency injection.  It supports two output pins.  Each pin is configurable for
  • The text name for the pin to be shown in the HTML
  • The GPIO pin number using the ESP8266 numbering.  Does not use Arduino naming
  • Definition of pin state for on.  This supports pin output for cases where turning on an LED is done by pushing the pin LOW.

# Complete project details at
import socket
import machine
import gc

class WebServer(object):
"""webserver that can change output pins"""

def __init__(
self.dev1_label = dev1_label
self.dev1_pin_number = dev1_pin_number
self.dev1_on_is_high = dev1_on_is_high
self.dev2_label = dev2_label
self.dev2_pin_number = dev2_pin_number
self.dev2_on_is_high = dev2_on_is_high
self.dev1_pin = machine.Pin(self.dev1_pin_number, machine.Pin.OUT)
self.dev2_pin = machine.Pin(self.dev2_pin_number, machine.Pin.OUT)

def _web_page_html(self):

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><strong>"""
+ self.dev1_label
+ """</strong> Currently: """ +
str(bool(self.dev1_pin.value()) == self.dev1_on_is_high)+"""</p>"""
+ """<p><a href="/?dev1=on"><button class="button button">ON</button></a>"""
+ """<a href="/?dev1=off"><button class="button button2">OFF</button></a></p>
+ """<p><strong>"""
+ self.dev2_label
+ """</strong> Currently: """ +
str(bool(self.dev2_pin.value()) == self.dev2_on_is_high)+"""</p>"""
+ """<p><a href="/?dev2=on"><button class="button button">ON</button></a>"""
+ """<a href="/?dev2=off"><button class="button button2">OFF</button></a></p>
return html

def _handle_request(self, request):

dev1_on = request.find("dev1=on")
dev1_off = request.find("dev1=off")
dev2_on = request.find("dev2=on")
dev2_off = request.find("dev2=off")

# fixed index because content has referrer uri
if dev1_on == 8:
print("DEV1 ON: ", dev1_on)
if dev1_off == 8:
print("DEV1 OFF: ", dev1_off)
self.dev1_pin.value(int(not self.dev1_on_is_high))
if dev2_on == 8:
print("DEV2 ON: ", dev2_on)
if dev2_off == 8:
print("DEV2 OFF: ", dev2_off)
self.dev2_pin.value(int(not self.dev2_on_is_high))

def run_server(self):
"""runs the web server"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("", 80))
while True:
if gc.mem_free() < 102000:
conn, addr = s.accept()
print("Got a connection from %s" % str(addr))
request = conn.recv(1024)
request = str(request)
print("Content = %s" % request)


response = self._web_page_html()
conn.send("HTTP/1.1 200 OK\n")
conn.send("Content-Type: text/html\n")
conn.send("Connection: close\n\n")
# print('Connection closed')
except OSError as e:
print("Connection closed on error")

Wi-Fi This!

Our Wifi is configured and run with two lines of code in above.  This code creates a WIFI object that can connect to the specified network and set the hostname of the connection.  Some networks will pick up the hostname and let you connect by name rather than IP.

The IP address is echoed to the console and is required to find the web endpoint in certain environments.

import network
import time

class WIFI(object):
"""manages wifi connection"""

def __init__(self, ssid, passwd, hostname):
self.ssid = ssid
self.passwd = passwd
self.hostname = hostname

def do_connect(self):
"""connect to wifi"""
self.station = network.WLAN(network.STA_IF)
if not self.station.isconnected():
print("connecting to network...")
self.station.connect(self.ssid, self.passwd)
while not self.station.isconnected():
host = self.station.config("hostname")
except ValueError:
# "hostname" is available in master, but not yet in June 2022 1.19.1 release
host = self.station.config("dhcp_hostname")
print("host ", host, " network config:", self.station.ifconfig())


The web browser connects to the ESP8266 board.  The board is hardwired to the LEDs and Relays via GPIO pins. 
You can leave your development channel open on the 3.3v serial line to watch output or test with the REPL.  I use rshell to copy files to the device and access the REPL.

Revision History

Created 2022 11


Popular posts from this blog

Understanding your WSL2 RAM and swap - Changing the default 50%-25%

Installing the RNDIS driver on Windows 11 to use USB Raspberry Pi as network attached

DNS for Azure Point to Site (P2S) VPN - getting the internal IPs