The ESP32, the newly released successor to the ESP8266, has been a rising star in IoT or WiFi-related projects. It�s a low-cost WiFi module that can be programmed to run a standalone web server with a little extra work. How cool is that?
This tutorial will walk you through the process of creating a simple ESP32 web server in the Arduino IDE.�So, let�s get started.
What exactly is a Web server and how does it work?
A web server is a place where web pages are stored, processed, and served to web clients. A web client is just a web browser that we use on our computers and phones. A web client and a web server communicate using a special protocol known as Hypertext Transfer Protocol (HTTP).
In this protocol, a client starts a conversation by sending an HTTP request for a specific web page. The server then sends back the content of that web page or an error message if it can�t find it (like the famous 404 Error).
ESP32 Operating Modes
One of the most useful features of the ESP32 is its ability to not only connect to an existing WiFi network and act as a Web Server, but also to create its own network, allowing other devices to connect directly to it and access web pages. This is possible because the ESP32 can operate in three modes: Station (STA) mode, Soft Access Point (AP) mode, and both simultaneously.
Station (STA) Mode
In Station (STA) mode, the ESP32 connects to an existing WiFi network (the one created by your wireless router).
In STA mode, the ESP32 obtains an IP address from the wireless router to which it is connected. With this IP address, it can set up a web server and serve web pages to all connected devices on the existing WiFi network.
Soft Access Point (AP) Mode
In Access Point (AP) mode, the ESP32 sets up its own WiFi network and acts as a hub, just like a WiFi router. However, unlike a WiFi router, it does not have an interface to a wired network. So, this mode of operation is called Soft Access Point (soft-AP). Also, no more than five stations can connect to it at the same time.
In AP mode, the ESP32 creates a new WiFi network and assigns it an SSID (the network�s name) and an IP address. With this IP address, it can serve web pages to all connected devices.
Wiring LEDs to an ESP32
Now that we understand the fundamentals of how a web server works and the modes in which the ESP32 can create one, it�s time to connect some LEDs to the ESP32 that we want to control via WiFi.
Begin by placing the ESP32 on your breadboard, making sure that each side of the board is on a different side of the breadboard. Next, connect two LEDs to digital GPIO 4 and 5 using a 220? current limiting resistor.
When you are finished, you should have something that looks like the image below.
The Idea Behind Using an ESP32 Web Server to Control Things
So you might be wondering, �How do I control things from a web server that only processes and serves web pages?�.
It�s extremely simple. We�re going to control things by visiting a specific URL.
When you enter a URL into a web browser, it sends an HTTP request (also known as a GET request) to a web server. It is the web server�s responsibility to handle this request.
Assume you entered a URL like http://192.168.1.1/ledon into a browser. The browser then sends an HTTP request to the ESP32. When the ESP32 receives this request, it recognizes that the user wishes to turn on the LED. As a result, it turns on the LED and sends a dynamic webpage to a browser that displays the LED�s status as �on.� Quite simple, right?
Example 1 � Configuring the ESP32 Web Server in Access Point (AP) mode
Let�s get to the interesting stuff now!
This example, as the title suggests, shows how to configure the ESP32 Web Server in Access Point (AP) mode and serve web pages to any connected client. To begin, connect your ESP32 to your computer and run the sketch. Then we will look at it in more detail.
#include <WiFi.h>
#include <WebServer.h>
/* Put your SSID & Password */
const char* ssid = "ESP32"; // Enter SSID here
const char* password = "12345678"; //Enter Password here
/* Put IP Address details */
IPAddress local_ip(192,168,1,1);
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);
WebServer server(80);
uint8_t LED1pin = 4;
bool LED1status = LOW;
uint8_t LED2pin = 5;
bool LED2status = LOW;
void setup() {
Serial.begin(115200);
pinMode(LED1pin, OUTPUT);
pinMode(LED2pin, OUTPUT);
WiFi.softAP(ssid, password);
WiFi.softAPConfig(local_ip, gateway, subnet);
delay(100);
server.on("/", handle_OnConnect);
server.on("/led1on", handle_led1on);
server.on("/led1off", handle_led1off);
server.on("/led2on", handle_led2on);
server.on("/led2off", handle_led2off);
server.onNotFound(handle_NotFound);
server.begin();
Serial.println("HTTP server started");
}
void loop() {
server.handleClient();
if(LED1status)
{digitalWrite(LED1pin, HIGH);}
else
{digitalWrite(LED1pin, LOW);}
if(LED2status)
{digitalWrite(LED2pin, HIGH);}
else
{digitalWrite(LED2pin, LOW);}
}
void handle_OnConnect() {
LED1status = LOW;
LED2status = LOW;
Serial.println("GPIO4 Status: OFF | GPIO5 Status: OFF");
server.send(200, "text/html", SendHTML(LED1status,LED2status));
}
void handle_led1on() {
LED1status = HIGH;
Serial.println("GPIO4 Status: ON");
server.send(200, "text/html", SendHTML(true,LED2status));
}
void handle_led1off() {
LED1status = LOW;
Serial.println("GPIO4 Status: OFF");
server.send(200, "text/html", SendHTML(false,LED2status));
}
void handle_led2on() {
LED2status = HIGH;
Serial.println("GPIO5 Status: ON");
server.send(200, "text/html", SendHTML(LED1status,true));
}
void handle_led2off() {
LED2status = LOW;
Serial.println("GPIO5 Status: OFF");
server.send(200, "text/html", SendHTML(LED1status,false));
}
void handle_NotFound(){
server.send(404, "text/plain", "Not found");
}
String SendHTML(uint8_t led1stat,uint8_t led2stat){
String ptr = "<!DOCTYPE html> <html>\n";
ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
ptr +="<title>LED Control</title>\n";
ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
ptr +=".button {display: block;width: 80px;background-color: #3498db;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
ptr +=".button-on {background-color: #3498db;}\n";
ptr +=".button-on:active {background-color: #2980b9;}\n";
ptr +=".button-off {background-color: #34495e;}\n";
ptr +=".button-off:active {background-color: #2c3e50;}\n";
ptr +="p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
ptr +="</style>\n";
ptr +="</head>\n";
ptr +="<body>\n";
ptr +="<h1>ESP32 Web Server</h1>\n";
ptr +="<h3>Using Access Point(AP) Mode</h3>\n";
if(led1stat)
{ptr +="<p>LED1 Status: ON</p><a class=\"button button-off\" href=\"/led1off\">OFF</a>\n";}
else
{ptr +="<p>LED1 Status: OFF</p><a class=\"button button-on\" href=\"/led1on\">ON</a>\n";}
if(led2stat)
{ptr +="<p>LED2 Status: ON</p><a class=\"button button-off\" href=\"/led2off\">OFF</a>\n";}
else
{ptr +="<p>LED2 Status: OFF</p><a class=\"button button-on\" href=\"/led2on\">ON</a>\n";}
ptr +="</body>\n";
ptr +="</html>\n";
return ptr;
}
Accessing the Web Server in AP Mode
After uploading the sketch, open the Serial Monitor at 115200 baud and press the RESET button on the ESP32. If everything is fine, it will show the �HTTP server started� �message.
Now, get a phone, laptop, or other device that can connect to a WiFi network, and look for a network called �ESP32�. Connect to the network using the password 12345678.
After connecting to your ESP32 AP network, open a browser and navigate to 192.168.1.1. The ESP32 should return a web page displaying the current status of the LEDs and buttons. At the same time, you can check the serial monitor to see the status of the ESP32�s GPIO pins.
Now, while keeping an eye on the URL, click the button to turn LED1 ON. Once you click the button, the ESP32 receives a request for the /led1on URL. It then turns on LED1 and serves a web page with the LED status updated. It also prints the GPIO pin status on the serial monitor.
You can test the LED2 button to see if it works similarly.
Let�s take a closer look at the code to see how it works so you can modify it to suit your needs.
Detailed Code Explanation
The sketch begins by including the WiFi.h
library. This library contains ESP32-specific methods that we use to connect to the network. Following that, we include the WebServer.h
library, which contains some methods that will assist us in configuring a server and handling incoming HTTP requests without having to worry about low-level implementation details.
#include <WiFi.h>
#include <WebServer.h>
Because we are configuring the ESP32 web server in Access Point (AP) mode, it will create its own WiFi network. So, we need to set the SSID, password, IP address, IP subnet mask, and IP gateway.
/* Put your SSID & Password */
const char* ssid = "ESP32"; // Enter SSID here
const char* password = "12345678"; //Enter Password here
/* Put IP Address details */
IPAddress local_ip(192,168,1,1);
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);
Following that, we create an object of the WebServer library so that we can access its functions. The constructor of this object accepts as a parameter the port to which the server will be listening. Since HTTP uses port 80 by default, we�ll use this value. This allows us to connect to the server without specifying the port in the URL.
// declare an object of WebServer library
WebServer server(80);
Next, we declare the ESP32�s GPIO pins to which LEDs are connected, as well as their initial state.
uint8_t LED1pin = 4;
bool LED1status = LOW;
uint8_t LED2pin = 5;
bool LED2status = LOW;
Inside the Setup() Function
In the setup function, we configure the ESP32 Web Server in soft Access Point (AP) mode. First, we establish a serial connection for debugging purposes and configure the GPIO pins to behave as an OUTPUT.
Serial.begin(115200);
pinMode(LED1pin, OUTPUT);
pinMode(LED2pin, OUTPUT);
Then, we configure a soft access point to create a Wi-Fi network by providing an SSID, password, IP address, IP subnet mask, and IP gateway.
WiFi.softAP(ssid, password);
WiFi.softAPConfig(local_ip, gateway, subnet);
delay(100);
To handle incoming HTTP requests, we must specify which code should be executed when a specific URL is accessed. For this, we use the .on()
method. This method accepts two parameters: a relative URL path and the name of the function to be executed when that URL is visited.
The first line of the code snippet below, for example, indicates that when a server receives an HTTP request on the root (/) path, it will call the handle_OnConnect()
function. It is important to note that the URL specified is a relative path.
Similarly, we must specify four more URLs to handle the two states of two LEDs.
server.on("/", handle_OnConnect);
server.on("/led1on", handle_led1on);
server.on("/led1off", handle_led1off);
server.on("/led2on", handle_led2on);
server.on("/led2off", handle_led2off);
We haven�t specified what the server should serve if the client requests a URL that isn�t specified with server.on()
. It should give a 404 error (Page Not Found) as a response. To accomplish this, we use the server.onNotFound()
method.
server.onNotFound(handle_NotFound);
Now, to start the server, we call the server object�s begin()
method.
server.begin();
Serial.println("HTTP server started");
Inside the Loop() Function
Actual incoming HTTP requests are handled in the loop function. For this, we use the server object�s handleClient()
method. We also change the state of LEDs based on the request.
void loop() {
server.handleClient();
if(LED1status)
{digitalWrite(LED1pin, HIGH);}
else
{digitalWrite(LED1pin, LOW);}
if(LED2status)
{digitalWrite(LED2pin, HIGH);}
else
{digitalWrite(LED2pin, LOW);}
}
Now we must write the handle_OnConnect()
function, which we previously attached to the root (/) URL with server.on
. We begin this function by setting the status of both LEDs to LOW (initial state of LEDs) and printing it on the serial monitor.
We use the send method to respond to an HTTP request. Although the method can be called with a number of different arguments, the simplest form requires the HTTP response code, the content type, and the content.
The first parameter we pass to the send method is the code 200 (one of the HTTP status codes), which corresponds to the OK response. Then we specify the content type as �text/html,� and finally we pass the SendHTML()
custom function, which generates a dynamic HTML page with the LED status.
void handle_OnConnect() {
LED1status = LOW;
LED2status = LOW;
Serial.println("GPIO4 Status: OFF | GPIO5 Status: OFF");
server.send(200, "text/html", SendHTML(LED1status,LED2status));
}
Similarly, we write five more functions to handle LED ON/OFF requests and the 404 Error page.
void handle_led1on() {
LED1status = HIGH;
Serial.println("GPIO4 Status: ON");
server.send(200, "text/html", SendHTML(true,LED2status));
}
void handle_led1off() {
LED1status = LOW;
Serial.println("GPIO4 Status: OFF");
server.send(200, "text/html", SendHTML(false,LED2status));
}
void handle_led2on() {
LED2status = HIGH;
Serial.println("GPIO5 Status: ON");
server.send(200, "text/html", SendHTML(LED1status,true));
}
void handle_led2off() {
LED2status = LOW;
Serial.println("GPIO5 Status: OFF");
server.send(200, "text/html", SendHTML(LED1status,false));
}
void handle_NotFound(){
server.send(404, "text/plain", "Not found");
}
Displaying the HTML Web Page
Whenever the ESP32 web server receives a request from a web client, the sendHTML()
function generates a web page. It simply concatenates HTML code into a long string and returns to the server.send()
function we discussed earlier. The function uses the status of the LEDs as a parameter to generate HTML content dynamically.
The first text you should always send is the <!DOCTYPE> declaration, which indicates that we�re sending HTML code.
String SendHTML(uint8_t led1stat,uint8_t led2stat){
String ptr = "<!DOCTYPE html> <html>\n";
The <meta> viewport element makes the web page responsive, ensuring that it looks good on all devices. The title tag determines the page�s title.
ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
ptr +="<title>LED Control</title>\n";
Styling the Web Page
Following that, we have some CSS to style the buttons and the overall appearance of the web page. We selected the Helvetica font and defined the content to be displayed as an inline-block, center-aligned.
ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
The code that follows then sets the color, font, and margin around the body, H1, H3, and p tags.
ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
ptr +="p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
The buttons are also styled with properties such as color, size, margin, and so on. The :active selector changes the look of buttons while they are being clicked.
ptr +=".button {display: block;width: 80px;background-color: #3498db;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
ptr +=".button-on {background-color: #3498db;}\n";
ptr +=".button-on:active {background-color: #2980b9;}\n";
ptr +=".button-off {background-color: #34495e;}\n";
ptr +=".button-off:active {background-color: #2c3e50;}\n";
Setting the Web Page Heading
Next, the heading of the web page is set. You can change this text to anything that fits your application.
ptr +="<h1>ESP32 Web Server</h1>\n";
ptr +="<h3>Using Access Point(AP) Mode</h3>\n";
Displaying the Buttons and Corresponding State
The if
statement is used to dynamically update the status of the buttons and LEDs.
if(led1stat)
{ptr +="<p>LED1 Status: ON</p><a class=\"button button-off\" href=\"/led1off\">OFF</a>\n";}
else
{ptr +="<p>LED1 Status: OFF</p><a class=\"button button-on\" href=\"/led1on\">ON</a>\n";}
if(led2stat)
{ptr +="<p>LED2 Status: ON</p><a class=\"button button-off\" href=\"/led2off\">OFF</a>\n";}
else
{ptr +="<p>LED2 Status: OFF</p><a class=\"button button-on\" href=\"/led2on\">ON</a>\n";}
Example 2 � Configuring the ESP32 Web Server in WiFi Station (STA) mode
Let�s move on to the next example, which shows how to configure the ESP32 web server in Station (STA) mode and serve web pages to any connected client on the existing network.
Before you proceed with uploading the sketch, you must make some changes to ensure that it works for you. To connect ESP32 to an existing network, you must modify the following two variables with your network credentials.
Once you�re done, go ahead and try out the sketch.
#include <WiFi.h>
#include <WebServer.h>
/*Put your SSID & Password*/
const char* ssid = " YourNetworkName"; // Enter SSID here
const char* password = " YourPassword"; //Enter Password here
WebServer server(80);
uint8_t LED1pin = 4;
bool LED1status = LOW;
uint8_t LED2pin = 5;
bool LED2status = LOW;
void setup() {
Serial.begin(115200);
delay(100);
pinMode(LED1pin, OUTPUT);
pinMode(LED2pin, OUTPUT);
Serial.println("Connecting to ");
Serial.println(ssid);
//connect to your local wi-fi network
WiFi.begin(ssid, password);
//check wi-fi is connected to wi-fi network
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected..!");
Serial.print("Got IP: "); Serial.println(WiFi.localIP());
server.on("/", handle_OnConnect);
server.on("/led1on", handle_led1on);
server.on("/led1off", handle_led1off);
server.on("/led2on", handle_led2on);
server.on("/led2off", handle_led2off);
server.onNotFound(handle_NotFound);
server.begin();
Serial.println("HTTP server started");
}
void loop() {
server.handleClient();
if(LED1status)
{digitalWrite(LED1pin, HIGH);}
else
{digitalWrite(LED1pin, LOW);}
if(LED2status)
{digitalWrite(LED2pin, HIGH);}
else
{digitalWrite(LED2pin, LOW);}
}
void handle_OnConnect() {
LED1status = LOW;
LED2status = LOW;
Serial.println("GPIO4 Status: OFF | GPIO5 Status: OFF");
server.send(200, "text/html", SendHTML(LED1status,LED2status));
}
void handle_led1on() {
LED1status = HIGH;
Serial.println("GPIO4 Status: ON");
server.send(200, "text/html", SendHTML(true,LED2status));
}
void handle_led1off() {
LED1status = LOW;
Serial.println("GPIO4 Status: OFF");
server.send(200, "text/html", SendHTML(false,LED2status));
}
void handle_led2on() {
LED2status = HIGH;
Serial.println("GPIO5 Status: ON");
server.send(200, "text/html", SendHTML(LED1status,true));
}
void handle_led2off() {
LED2status = LOW;
Serial.println("GPIO5 Status: OFF");
server.send(200, "text/html", SendHTML(LED1status,false));
}
void handle_NotFound(){
server.send(404, "text/plain", "Not found");
}
String SendHTML(uint8_t led1stat,uint8_t led2stat){
String ptr = "<!DOCTYPE html> <html>\n";
ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
ptr +="<title>LED Control</title>\n";
ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
ptr +=".button {display: block;width: 80px;background-color: #3498db;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
ptr +=".button-on {background-color: #3498db;}\n";
ptr +=".button-on:active {background-color: #2980b9;}\n";
ptr +=".button-off {background-color: #34495e;}\n";
ptr +=".button-off:active {background-color: #2c3e50;}\n";
ptr +="p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
ptr +="</style>\n";
ptr +="</head>\n";
ptr +="<body>\n";
ptr +="<h1>ESP32 Web Server</h1>\n";
ptr +="<h3>Using Station(STA) Mode</h3>\n";
if(led1stat)
{ptr +="<p>LED1 Status: ON</p><a class=\"button button-off\" href=\"/led1off\">OFF</a>\n";}
else
{ptr +="<p>LED1 Status: OFF</p><a class=\"button button-on\" href=\"/led1on\">ON</a>\n";}
if(led2stat)
{ptr +="<p>LED2 Status: ON</p><a class=\"button button-off\" href=\"/led2off\">OFF</a>\n";}
else
{ptr +="<p>LED2 Status: OFF</p><a class=\"button button-on\" href=\"/led2on\">ON</a>\n";}
ptr +="</body>\n";
ptr +="</html>\n";
return ptr;
}
Accessing the Web Server in STA mode
After uploading the sketch, open the Serial Monitor at 115200 baud and press the RESET button on the ESP32. If everything is fine, it will display the dynamic IP address obtained from your router as well as the �HTTP server started� message.
Next, launch a browser and navigate to the IP address displayed on the serial monitor. The ESP32 should display a web page with the current status of the LEDs and two buttons for controlling them. At the same time, you can check the serial monitor to see the status of the ESP32�s GPIO pins.
Now, while keeping an eye on the URL, click the button to turn LED1 ON. Once you click the button, the ESP32 receives a request for the /led1on
URL. It then turns on LED1 and serves a web page with the LED status updated. It also prints the GPIO pin status on the serial monitor.
You can test the LED2 button to see if it works similarly.
Let�s take a closer look at the code to see how it works so you can modify it to suit your needs.
Code Explanation
The only difference between this code and the previous code is that we are not creating our own WiFi network but rather joining an existing network using the WiFi.begin()
function.
//connect to your local wi-fi network
WiFi.begin(ssid, password);
While the ESP32 attempts to connect to the network, we can use the WiFi.status()
function to check the connectivity status.
//check wi-fi is connected to wi-fi network
while (WiFi.status() != WL_CONNECTED)
{
delay(1000);
Serial.print(".");
}
For your information, this function returns the following statuses:
- WL_CONNECTED: when connected to a Wi-Fi network
- WL_NO_SHIELD: when no Wi-Fi shield is present
- WL_IDLE_STATUS: a temporary status assigned when
WiFi.begin()
is called and remains active until the number of attempts expires (resulting in WL_CONNECT_FAILED) or a connection is established (resulting in WL_CONNECTED) - WL_NO_SSID_AVAIL: when no SSID are available
- WL_SCAN_COMPLETED: when the scan networks is completed
- WL_CONNECT_FAILED: when the connection fails for all the attempts
- WL_CONNECTION_LOST: when the connection is lost
- WL_DISCONNECTED: when disconnected from a network
Once connected to the network, the WiFi.localIP()
function is used to print the ESP32�s IP address.
Serial.println("");
Serial.println("WiFi connected..!");
Serial.print("Got IP: "); Serial.println(WiFi.localIP());
The only difference between AP and STA mode is that one creates its own network while the other joins an existing one. So, the rest of the code for handling HTTP requests and serving web pages in STA mode is the same as explained above for AP mode. This includes the following:
- Declaring ESP32�s GPIO pins to which LEDs are connected
- Defining multiple
server.on()
methods to handle incoming HTTP requests - Defining
server.onNotFound()
method to handle HTTP 404 error - Creating custom functions that are executed when specific URL is hit
- Creating HTML page
- Styling the web page
- Creating buttons and displaying their status