This tutorial is a step-by-step guide that shows how to build a standalone ESP8266 Web Server that controls two outputs (two LEDs). This ESP8266 NodeMCU Web Server is mobile responsive and it can be accessed with any device with a browser in your local network.

Parts required

  • ESP8266 12-E – ESP-12E is a miniature Wi-Fi module present in the market and is used for establishing a wireless network connection for microcontroller or processor. The core of ESP-12E is ESP8266EX, which is a high integration wireless SoC (System on Chip).
  • 2x LEDs
  • 2x Resistors (220 or 330 ohms should work just fine)
  • Breadboard
  • Jumper wires

Schematic diagram

Connect two LEDs to your ESP8266 as shown in the following schematic diagram – with one LED connected to GPIO 4 (D2), and another to GPIO 5 (D1).

Connect two LEDs to ESP8266
Connect two LEDs to ESP8266

CODE

#include <ESP8266WiFi.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 output5State = "off";
String output4State = "off";

// Assign output variables to GPIO pins
const int output5 = 5;
const int output4 = 4;

// 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(output5, OUTPUT);
  pinMode(output4, OUTPUT);
  // Set outputs to LOW
  digitalWrite(output5, LOW);
  digitalWrite(output4, 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,
    Serial.println("New Client.");          // print a message out in the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    currentTime = millis();
    previousTime = currentTime;
    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 /5/on") >= 0) {
              Serial.println("GPIO 5 on");
              output5State = "on";
              digitalWrite(output5, HIGH);
            } else if (header.indexOf("GET /5/off") >= 0) {
              Serial.println("GPIO 5 off");
              output5State = "off";
              digitalWrite(output5, LOW);
            } else if (header.indexOf("GET /4/on") >= 0) {
              Serial.println("GPIO 4 on");
              output4State = "on";
              digitalWrite(output4, HIGH);
            } else if (header.indexOf("GET /4/off") >= 0) {
              Serial.println("GPIO 4 off");
              output4State = "off";
              digitalWrite(output4, 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: #195B6A; border: none; color: white; padding: 16px 40px;");
            client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");
            client.println(".button2 {background-color: #77878A;}</style></head>");
            
            // Web Page Heading
            client.println("<body><h1>ESP8266 Web Server</h1>");
            
            // Display current state, and ON/OFF buttons for GPIO 5  
            client.println("<p>GPIO 5 - State " + output5State + "</p>");
            // If the output5State is off, it displays the ON button       
            if (output5State=="off") {
              client.println("<p><a href=\"/5/on\"><button class=\"button\">ON</button></a></p>");
            } else {
              client.println("<p><a href=\"/5/off\"><button class=\"button button2\">OFF</button></a></p>");
            } 
               
            // Display current state, and ON/OFF buttons for GPIO 4  
            client.println("<p>GPIO 4 - State " + output4State + "</p>");
            // If the output4State is off, it displays the ON button       
            if (output4State=="off") {
              client.println("<p><a href=\"/4/on\"><button class=\"button\">ON</button></a></p>");
            } else {
              client.println("<p><a href=\"/4/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("");
  }
}

How the Code Works

Now, let’s take a closer look at the code to see how it works so that you are able to modify it to fulfil your needs.

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

// Load Wi-Fi library

#include <ESP8266WiFi.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 webserver to port 80.

// Set web server port number to 80

WiFiServer server(80);

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.

// Auxiliar variables to store the current output state

String output5State = "off";

String output4State = "off";

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

// Assign output variables to GPIO pins

const int output5 = 5;

const int output4 = 4;

setup()

Now, let’s go into the setup(). The setup() function only runs once when your ESP first boots. First, we start a serial communication at a baud rate of 115200 for debugging purposes.

Serial.begin(115200);

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

// Initialize the output variables as outputs

pinMode(output5, OUTPUT);

pinMode(output4, OUTPUT);

// Set outputs to LOW

digitalWrite(output5, LOW);

digitalWrite(output4, LOW);

The following lines begin the Wi-Fi connection with WiFi.begin(ssid, password), wait for a successful connection and prints 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.begin();

loop()

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

The ESP is always listening for incoming clients with this 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.println();

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 we press.

// turns the GPIOs on and off

if (header.indexOf("GET /5/on") >= 0) {

  Serial.println("GPIO 5 on");

  output5State = "on";

  digitalWrite(output5, HIGH);

} else if (header.indexOf("GET /5/off") >= 0) {

  Serial.println("GPIO 5 off");

  output5State = "off";

  digitalWrite(output5, LOW);

} else if (header.indexOf("GET /4/on") >= 0) {

  Serial.println("GPIO 4 on");

  output4State = "on";

  digitalWrite(output4, HIGH);

} else if (header.indexOf("GET /4/off") >= 0) {

  Serial.println("GPIO 4 off");

  output4State = "off";

  digitalWrite(output4, LOW);

}

For example, if you’ve pressed the GPIO 5 ON button, the URL changes to the ESP IP address followed by /5/ON, and we receive that information on the HTTP header. So, we can check if the header contains the expression GET /5/on.

If it contains, the code prints a message on the serial monitor, changes the output5State variable to on, and turns the LED on.

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 step is to create the web page. The ESP8266 will send some HTML text to your browser in order to show the web page.

The client.println() function is used to provide 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’re transmitting HTML, should always be the first text you send.

<!DOCTYPE html><html>

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

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

The next one is used to prevent requests related to the favicon – You don’t need to worry about this line.

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

Styling the Web Page

Next, we have some CSS 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 centre.

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

We style our buttons with the some properties to define color, size, border, etc…

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

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

Then, we 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.println(".button2 {background-color: #77878A;}</style></head>");

Setting the Web Page First Heading

In the next line, you set the first heading of your web page, you can change this text to whatever you like.

// Web Page Title

client.println("<h1>ESP8266 Web Server</h1>");

Displaying the Buttons and Corresponding State

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

client.println("<p>GPIO 5 - State " + output5State + "</p>");

Then, we display the on or the off button, depending on the current state of the GPIO.

if (output5State=="off") {

 client.println("<p><a href=\"/5/on\"><button class=\"button\">ON</button></a></p>");

} else {

 client.println("<p><a href=\"/5/off\"><button class=\"button button2\">OFF</button></a></p>");

}

We use the same procedure for GPIO 4.

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.stop();

Taking it Further

Now that you know how the code works, you can modify the code to add more outputs or modify your web page. To modify your web page you may need to know some HTML and CSS.

Instead of controlling two LEDs, you can control a relay to control practically any electronics appliance.

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.