In this project, a standalone web server with an ESP32 will be created that controls outputs (two LEDs) using the Arduino IDE programming environment. The web server is mobile responsive and it can be accessed with any device with a browser on the local network.

Here are some outline of what the webserver we create will do,

PROJECT OVERVIEW

  • The web server we create will control the state of LEDS connected to the ESP32 GPIO 26 and GPIO27.
  • ESP32 web server can be accessed by typing the ESP32 IP address on a browser in the local network.
  • The LED can be made on or off by clicking the buttons on the web server.

Installing the ESP32 board in Arduino IDE

There’s an add-on for the Arduino IDE that allows you to program the ESP32 using the Arduino IDE and its programming language. Follow one of the following tutorials to prepare your Arduino IDE:

PARTS REQUIRED

Parts required
  1. ESP32 development board- read ESP32 Development Boards Review and comparison
  2. 2X 5mm LED
  3. 2X 330 Ohm resistor
  4. Breadboard
  5. Jumper wires

Schematic

Start by building the circuit. Connect two LEDs to the ESP32 as shown in the following schematic diagram – one LED connected to GPIO 26, and the other to GPIO 27.

LED connected to GPIO 26  and GPIO 27

ESP32 Web Server Code

The code for the ESP32 web server may be found here. Copy and paste the code below into your Arduino IDE, but don’t upload it yet. To make it work for you, you’ll need to make some adjustments.

/*********

  Rui Santos

 

*********/

// Load Wi-Fi library

#include <WiFi.h>

// Replace with your network credentials

const char* ssid = “REPLACE_WITH_YOUR_SSID”;

const char* password = “REPLACE_WITH_YOUR_PASSWORD”;

// Set web server port number to 80

WiFiServer server(80);

// Variable to store the HTTP request

String header;

// Auxiliar variables to store the current output state

String output26State = “off”;

String output27State = “off”;

// Assign output variables to GPIO pins

const int output26 = 26;

const int output27 = 27;

// Current time

unsigned long currentTime = millis();

// Previous time

unsigned long previousTime = 0;

// Define timeout time in milliseconds (example: 2000ms = 2s)

const long timeoutTime = 2000;

void setup() {

  Serial.begin(115200);

  // Initialize the output variables as outputs

  pinMode(output26, OUTPUT);

  pinMode(output27, OUTPUT);

  // Set outputs to LOW

  digitalWrite(output26, LOW);

  digitalWrite(output27, LOW);

  // Connect to Wi-Fi network with SSID and password

  Serial.print(“Connecting to “);

  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {

    delay(500);

    Serial.print(“.”);

  }

  // Print local IP address and start web server

  Serial.println(“”);

  Serial.println(“WiFi connected.”);

  Serial.println(“IP address: “);

  Serial.println(WiFi.localIP());

  server.begin();

}

void loop(){

  WiFiClient client = server.available();   // Listen for incoming clients

  if (client) {                             // If a new client connects,

    currentTime = millis();

    previousTime = currentTime;

    Serial.println(“New Client.”);          // print a message out in the serial port

    String currentLine = “”;                // make a String to hold incoming data from the client

    while (client.connected() && currentTime – previousTime <= timeoutTime) {  // loop while the client’s connected

      currentTime = millis();

      if (client.available()) {             // if there’s bytes to read from the client,

        char c = client.read();             // read a byte, then

        Serial.write(c);                    // print it out the serial monitor

        header += c;

        if (c == ‘\n’) {                    // if the byte is a newline character

          // if the current line is blank, you got two newline characters in a row.

          // that’s the end of the client HTTP request, so send a response:

          if (currentLine.length() == 0) {

            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)

            // and a content-type so the client knows what’s coming, then a blank line:

            client.println(“HTTP/1.1 200 OK”);

            client.println(“Content-type:text/html”);

            client.println(“Connection: close”);

            client.println();

            // turns the GPIOs on and off

            if (header.indexOf(“GET /26/on”) >= 0) {

              Serial.println(“GPIO 26 on”);

              output26State = “on”;

              digitalWrite(output26, HIGH);

            } else if (header.indexOf(“GET /26/off”) >= 0) {

              Serial.println(“GPIO 26 off”);

              output26State = “off”;

              digitalWrite(output26, LOW);

            } else if (header.indexOf(“GET /27/on”) >= 0) {

              Serial.println(“GPIO 27 on”);

              output27State = “on”;

              digitalWrite(output27, HIGH);

            } else if (header.indexOf(“GET /27/off”) >= 0) {

              Serial.println(“GPIO 27 off”);

              output27State = “off”;

              digitalWrite(output27, LOW);

            }

            // Display the HTML web page

            client.println(“<!DOCTYPE html><html>”);

            client.println(“<head><meta name=\”viewport\” content=\”width=device-width, initial-scale=1\”>”);

            client.println(“<link rel=\”icon\” href=\”data:,\”>”);

            // CSS to style the on/off buttons

            // Feel free to change the background-color and font-size attributes to fit your preferences

            client.println(“<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}”);

            client.println(“.button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px;”);

            client.println(“text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}”);

            client.println(“.button2 {background-color: #555555;}</style></head>”);

            // Web Page Heading

            client.println(“<body><h1>ESP32 Web Server</h1>”);

            // Display current state, and ON/OFF buttons for GPIO 26 

            client.println(“<p>GPIO 26 – State ” + output26State + “</p>”);

            // If the output26State is off, it displays the ON button      

            if (output26State==”off”) {

              client.println(“<p><a href=\”/26/on\”><button class=\”button\”>ON</button></a></p>”);

            } else {

              client.println(“<p><a href=\”/26/off\”><button class=\”button button2\”>OFF</button></a></p>”);

            }

            // Display current state, and ON/OFF buttons for GPIO 27 

            client.println(“<p>GPIO 27 – State ” + output27State + “</p>”);

            // If the output27State is off, it displays the ON button      

            if (output27State==”off”) {

              client.println(“<p><a href=\”/27/on\”><button class=\”button\”>ON</button></a></p>”);

            } else {

              client.println(“<p><a href=\”/27/off\”><button class=\”button button2\”>OFF</button></a></p>”);

            }

            client.println(“</body></html>”);

            // The HTTP response ends with another blank line

            client.println();

            // Break out of the while loop

            break;

          } else { // if you got a newline, then clear currentLine

            currentLine = “”;

          }

        } else if (c != ‘\r’) {  // if you got anything else but a carriage return character,

          currentLine += c;      // add it to the end of the currentLine

        }

      }

    }

    // Clear the header variable

    header = “”;

    // Close the connection

    client.stop();

    Serial.println(“Client disconnected.”);

    Serial.println(“”);

  }

}

Setting Your Network Credentials

You need to modify the following lines with your network credentials: SSID and password. The code is well commented on where you should make the changes.

// Replace with your network credentials
const char* ssid     = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

 

Uploading the Code

Now, you can upload the code and and the web server will work straight away. Follow the next steps to upload code to the ESP32:

1) Plug your ESP32 board in your computer;

2) In the Arduino IDE select your board in Tools > Board (in our case we’re using the ESP32 DEVKIT DOIT board);

In Arduino ID ,tools>board

3) Select the COM port in Tools > Port.

Tools>port

4) Press the Upload button in the Arduino IDE and wait a few seconds while the code compiles and uploads to your board.

5) Wait for the “Done uploading” message.

done uploading

Finding the ESP IP Address

After uploading the code, open the Serial Monitor at a baud rate of 115200.

serial monitor

Press the ESP32 EN button (reset). The ESP32 connects to Wi-Fi, and outputs the ESP IP address on the Serial Monitor. Copy that IP address, because you need it to access the ESP32 web server.

copy the IP address.

Accessing the Web Server

To access the web server, open your browser, paste the ESP32 IP address, and you’ll see the following page. In our case it is 192.168.1.135.

paste the IP address in the browser.

If you take a look at the Serial Monitor, you can see what’s happening on the background. The ESP receives an HTTP request from a new client (in this case, your browser).

HTTP request

You can also see other information about the HTTP request.

Testing the Web Server

Now you can test if your web server is working properly. Click the buttons to control the LEDs.

click buttons to control the LEDS.

You can peek at the Serial Monitor at the same time to observe what’s going on in the background. When you press the button to turn GPIO 26 ON, for example, ESP32 receives a request on the /26/on URL.

ESP32 receives a request on the /26/on URL.

When the ESP32 receives that request, it turns the LED attached to GPIO 26 ON and updates its state on the web page.

GPIO 26 ON

The button for GPIO 27 works in a similar way. Test that it is working properly.

How the Code Works

In this section will take a closer look at the code to see how it works.

The first thing you need to do is to include the WiFi library.

#include <WiFi.h>

As mentioned previously, you need to insert your ssid and password in the following lines inside the double quotes.

const char* ssid = "";
const char* password = "";

Then, you set your web server to port 80.

WiFiServer 

The following line creates a variable to store the header of the HTTP request:

String header

Next, you create auxiliar variables to store the current state of your outputs. If you want to add more outputs and save its state, you need to create more variables.

String output26State = "off";
String output27State = "off";

You also need to assign a GPIO to each of your outputs. Here we are using GPIO 26 and GPIO 27. You can use any other suitable GPIOs.

const int output26 = 26;
const int output27 = 27;

setup()

Now, let’s go into the setup(). First, we start a serial communication at a baud rate of 115200 for debugging purposes.

Serial

You also define your GPIOs as OUTPUTs and set them to LOW.

// Initialize the output variables as outputs
pinMode(output26, OUTPUT);
pinMode(output27, OUTPUT);
 
// Set outputs to LOW
digitalWrite(output26, LOW);
digitalWrite(output27, LOW);

The following lines begin the Wi-Fi connection with WiFi.begin(ssid, password), wait for a successful connection and print the ESP IP address in the Serial Monitor.

// Connect to Wi-Fi network with SSID and password
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print(".");
}
// Print local IP address and start web server
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
server

loop()

In the loop() we program what happens when a new client establishes a connection with the web server.

The ESP32 is always listening for incoming clients with the following line:

WiFiClient client = server.available(); // Listen for incoming clients

When a request is received from a client, we’ll save the incoming data. The while loop that follows will be running as long as the client stays connected. We don’t recommend changing the following part of the code unless you know exactly what you are doing.

if (client) { // If a new client connects,
  Serial.println("New Client."); // print a message out in the serial port
  String currentLine = ""; // make a String to hold incoming data from the client
  while (client.connected()) { // loop while the client's connected
    if (client.available()) { // if there's bytes to read from the client,
      char c = client.read(); // read a byte, then
      Serial.write(c); // print it out the serial monitor
      header += c;
      if (c == '\n') { // if the byte is a newline character
      // if the current line is blank, you got two newline characters in a row.
      / that's the end of the client HTTP request, so send a response:
        if (currentLine.length() == 0) {
        // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
        // and a content-type so the client knows what's coming, then a blank line:
          client.println("HTTP/1.1 200 OK");
          client.println("Content-type:text/html");
          client.println("Connection: close");
          client

The next section of if and else statements checks which button was pressed in your web page, and controls the outputs accordingly. As we’ve seen previously, we make a request on different URLs depending on the button pressed.

// turns the GPIOs on and off
if (header.indexOf("GET /26/on") >= 0) {
  Serial.println("GPIO 26 on");
  output26State = "on";
  digitalWrite(output26, HIGH);
} else if (header.indexOf("GET /26/off") >= 0) {
  Serial.println("GPIO 26 off");
  output26State = "off";
  digitalWrite(output26, LOW);
} else if (header.indexOf("GET /27/on") >= 0) {
  Serial.println("GPIO 27 on");
  output27State = "on";
  digitalWrite(output27, HIGH);
} else if (header.indexOf("GET /27/off") >= 0) {
  Serial.println("GPIO 27 off");
  output27State = "off";
  digitalWrite(output27, LOW);
}

If you click the GPIO 26 ON button, for example, the ESP32 receives a request on the /26/ON URL (we can see that that information on the HTTP header on the Serial Monitor). So we can see if the header has the GET /26/on expression. If it does, the output26state variable is set to ON, and the ESP32 turns on the LED.

This works similarly for the other buttons. So, if you want to add more outputs, you should modify this part of the code to include them.

Displaying the HTML web page

The next thing you need to do, is creating the web page. The ESP32 will be sending a response to your browser with some HTML code to build the web page.

This expression client.println is used to send the web page to the client (). As an argument, you should type what you want to communicate to the client.

The next line, which signals that we are transmitting HTML, should always be the first thing we send.

<!DOCTYPE HTML><html>

Then, the following line makes the web page responsive in any web browser.

client

And the following is used to prevent requests on the favicon. – You don’t need to worry about this line.

client

Styling the Web Page

Next, we have some CSS text to style the buttons and the web page appearance. We choose the Helvetica font, define the content to be displayed as a block and aligned at the center.

client

We style our buttons with the #4CAF50 color, without border, text in white color, and with this padding: 16px 40px. We also set the text-decoration to none, define the font size, the margin, and the cursor to a pointer.

client.println(".button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px;");
client

We also define the style for a second button, with all the properties of the button we’ve defined earlier, but with a different color. This will be the style for the off button.

client

 

Setting the Web Page First Heading

In the next line you can set the first heading of your web page. Here we have “ESP32 Web Server”, but you can change this text to whatever you like.

// Web Page Heading
client

 

Displaying the Buttons and Corresponding State

Then, you write a paragraph to display the GPIO 26 current state. As you can see we use the output26State variable, so that the state updates instantly when this variable changes.

client.println("<p>GPIO 26 - State " + output26State + "</p>");

Then, we display the on or the off button, depending on the current state of the GPIO. If the current state of the GPIO is off, we show the ON button, if not, we display the OFF button.

if (output26State=="off") {
  client.println("<p><a href=\"/26/on\"><button class=\"button\">ON</button></a></p>");
} else {
  client.println("<p><a href=\"/26/off\"><button class=\"button button2\">OFF</button></a></p>");
}

We use the same procedure for GPIO 27.

Closing the Connection

Finally, when the response ends, we clear the header variable, and stop the connection with the client with client.stop().

// Clear the header variable
header = "";
// Close the connection
client

author avatar
Amith G Nair
Experience as a product developer, innovation coach, and electronics lecturer,a seasoned professional driven by passion for designing projects.expertise extends to 3D modelling, hardware designing, and web development using HTML, WordPress, and Django.