ESP32/ESP8266: Control Outputs with Web Server and a Physical Button Simultaneously

This tutorial shows how to control the ESP32 or ESP8266 outputs using a web server and a physical button simultaneously. The output state is updated on the web page whether it is changed via physical button or web server.

ESP32 ESP8266 NodeMCU: Control Outputs with Web Server and a Physical Button Simultaneously Arduino IDE

Recommended reading: Input Data on HTML Form ESP32/ESP8266 Web Server

The ESP32/ESP8266 boards will be programmed using Arduino IDE. So make sure you have these boards installed:

Project Overview

Let’s take a quick look at how the project works.

ESP32 ESP8266 NodeMCU Web Server With Physical Button Project Overview
  • The ESP32 or ESP8266 hosts a web server that allows you to control the state of an output;
  • The current output state is displayed on the web server;
  • The ESP is also connected to a physical pushbutton that controls the same output;
  • If you change the output state using the physical puhsbutton, its current state is also updated on the web server.

In summary, this project allows you to control the same output using a web server and a push button simultaneously. Whenever the output state changes, the web server is updated.

Schematic Diagram

Before proceeding, you need to assemble a circuit with an LED and a pushbutton. We’ll connect the LED to GPIO 2 and the pushbutton to GPIO 4.

Parts Required

Here’s a list of the parts to you need to build the circuit:

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

ESP32 Schematic

ESP32 Digital Input and Digital Output Schematic Circuit LED Pushbutton

ESP8266 NodeMCU Schematic

ESP8266 NodeMCU Digital Input and Digital Output Schematic Circuit LED Pushbutton

Installing Libraries – Async Web Server

To build the web server you need to install the following libraries:

These libraries aren’t available to install through the Arduino Library Manager, so you need to copy the library files to the Arduino Installation Libraries folder. Alternatively, in your Arduino IDE, you can go to Sketch Include Library > Add .zip Library and select the libraries you’ve just downloaded.

ESP Web Server Code

Copy the following code to your Arduino IDE.

/*********
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-esp8266-web-server-physical-button/
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*********/

// Import required libraries
#ifdef ESP32
  #include <WiFi.h>
  #include <AsyncTCP.h>
#else
  #include <ESP8266WiFi.h>
  #include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h>

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

const char* PARAM_INPUT_1 = "state";

const int output = 2;
const int buttonPin = 4;

// Variables will change:
int ledState = LOW;          // the current state of the output pin
int buttonState;             // the current reading from the input pin
int lastButtonState = LOW;   // the previous reading from the input pin

// the following variables are unsigned longs because the time, measured in
// milliseconds, will quickly become a bigger number than can be stored in an int.
unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <title>ESP Web Server</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    h2 {font-size: 3.0rem;}
    p {font-size: 3.0rem;}
    body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
    .switch {position: relative; display: inline-block; width: 120px; height: 68px} 
    .switch input {display: none}
    .slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 34px}
    .slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 68px}
    input:checked+.slider {background-color: #2196F3}
    input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
  </style>
</head>
<body>
  <h2>ESP Web Server</h2>
  %BUTTONPLACEHOLDER%
<script>function toggleCheckbox(element) {
  var xhr = new XMLHttpRequest();
  if(element.checked){ xhr.open("GET", "/update?state=1", true); }
  else { xhr.open("GET", "/update?state=0", true); }
  xhr.send();
}

setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var inputChecked;
      var outputStateM;
      if( this.responseText == 1){ 
        inputChecked = true;
        outputStateM = "On";
      }
      else { 
        inputChecked = false;
        outputStateM = "Off";
      }
      document.getElementById("output").checked = inputChecked;
      document.getElementById("outputState").innerHTML = outputStateM;
    }
  };
  xhttp.open("GET", "/state", true);
  xhttp.send();
}, 1000 ) ;
</script>
</body>
</html>
)rawliteral";

// Replaces placeholder with button section in your web page
String processor(const String& var){
  //Serial.println(var);
  if(var == "BUTTONPLACEHOLDER"){
    String buttons ="";
    String outputStateValue = outputState();
    buttons+= "<h4>Output - GPIO 2 - State <span id=\"outputState\"><span></h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"output\" " + outputStateValue + "><span class=\"slider\"></span></label>";
    return buttons;
  }
  return String();
}

String outputState(){
  if(digitalRead(output)){
    return "checked";
  }
  else {
    return "";
  }
  return "";
}

void setup(){
  // Serial port for debugging purposes
  Serial.begin(115200);

  pinMode(output, OUTPUT);
  digitalWrite(output, LOW);
  pinMode(buttonPin, INPUT);
  
  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  // Print ESP Local IP Address
  Serial.println(WiFi.localIP());

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, processor);
  });

  // Send a GET request to <ESP_IP>/update?state=<inputMessage>
  server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
    String inputMessage;
    String inputParam;
    // GET input1 value on <ESP_IP>/update?state=<inputMessage>
    if (request->hasParam(PARAM_INPUT_1)) {
      inputMessage = request->getParam(PARAM_INPUT_1)->value();
      inputParam = PARAM_INPUT_1;
      digitalWrite(output, inputMessage.toInt());
      ledState = !ledState;
    }
    else {
      inputMessage = "No message sent";
      inputParam = "none";
    }
    Serial.println(inputMessage);
    request->send(200, "text/plain", "OK");
  });

  // Send a GET request to <ESP_IP>/state
  server.on("/state", HTTP_GET, [] (AsyncWebServerRequest *request) {
    request->send(200, "text/plain", String(digitalRead(output)).c_str());
  });
  // Start server
  server.begin();
}
  
void loop() {
  // read the state of the switch into a local variable:
  int reading = digitalRead(buttonPin);

  // check to see if you just pressed the button
  // (i.e. the input went from LOW to HIGH), and you've waited long enough
  // since the last press to ignore any noise:

  // If the switch changed, due to noise or pressing:
  if (reading != lastButtonState) {
    // reset the debouncing timer
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {
    // whatever the reading is at, it's been there for longer than the debounce
    // delay, so take it as the actual current state:

    // if the button state has changed:
    if (reading != buttonState) {
      buttonState = reading;

      // only toggle the LED if the new button state is HIGH
      if (buttonState == HIGH) {
        ledState = !ledState;
      }
    }
  }

  // set the LED:
  digitalWrite(output, ledState);

  // save the reading. Next time through the loop, it'll be the lastButtonState:
  lastButtonState = reading;
}

View raw code

You just need to enter your network credentials (SSID and password) and the web server will work straight away. The code is compatible with both the ESP32 and ESP8266 boards and controls GPIO 2 – you can change the code to control any other GPIO.

How the Code Works

We’ve already explained in great details how web servers like this work in previous tutorials (DHT Temperature Web Server), so we’ll just take a look at the relevant parts for this project.

Network Credentials

As said previously, you need to insert your network credentials in the following lines:

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

Button State and Output State

The ledState variable holds the LED output state. For default, when the web server starts, it is LOW.

int ledState = LOW; // the current state of the output pin

The buttonState and lastButtonState are used to detect whether the pushbutton was pressed or not.

int buttonState;            // the current reading from the input pin
int lastButtonState = LOW;  // the previous reading from the input pin

Button (web server)

We didn’t include the HTML to create the button on the the index_html variable. That’s because we want to be able to change it depending on the current LED state that can also be changed with the pushbutton.

So, we’ve create a placeholder for the button %BUTTONPLACEHOLDER% that will be replaced with HTML text to create the button later on the code (this is done in the processor() function).

<h2>ESP Web Server</h2>
%BUTTONPLACEHOLDER%

processor()

The processor() function replaces any placeholders on the HTML text with actual values. First, it checks whether the HTML texts contains any placeholders %BUTTONPLACEHOLDER%.

if(var == "BUTTONPLACEHOLDER"){

Then, call the outputState() function that returns the current output state. We save it in the outputStateValue variable.

String outputStateValue = outputState();

After that, use that value to create the HTML text to display the button with the right state:

buttons+= "<h4>Output - GPIO 2 - State <span id=\"outputState\"><span></h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"output\" " + outputStateValue + "><span class=\"slider\"></span></label>";

HTTP GET Request to Change Output State (JavaScript)

When you press the button, the toggleCheckbox() function is called. This function will make a request on different URLs to turn the LED on or off.

function toggleCheckbox(element) {
  var xhr = new XMLHttpRequest();
  if(element.checked){ xhr.open("GET", "/update?state=1", true); }
  else { xhr.open("GET", "/update?state=0", true); }
  xhr.send();
}

To turn on the LED, it makes a request on the /update?state=1 URL:

if(element.checked){ xhr.open("GET", "/update?state=1", true); }

Otherwise, it makes a request on the /update?state=0 URL.

HTTP GET Request to Update State (JavaScript)

To keep the output state updated on the web server, we call the following function that makes a new request on the /state URL every second.

setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var inputChecked;
      var outputStateM;
      if( this.responseText == 1){ 
        inputChecked = true;
        outputStateM = "On";
      }
      else { 
        inputChecked = false;
        outputStateM = "Off";
      }
      document.getElementById("output").checked = inputChecked;
      document.getElementById("outputState").innerHTML = outputStateM;
    }
  };
  xhttp.open("GET", "/state", true);
  xhttp.send();
}, 1000 ) ;

Handle Requests

Then, we need to handle what happens when the ESP32 or ESP8266 receives requests on those URLs.

When a request is received on the root / URL, we send the HTML page as well as the processor.

server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send_P(200, "text/html", index_html, processor);
});

The following lines check whether you received a request on the /update?state=1 or /update?state=0 URL and changes the ledState accordingly.

server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
  String inputMessage;
  String inputParam;
  // GET input1 value on <ESP_IP>/update?state=<inputMessage>
  if (request->hasParam(PARAM_INPUT_1)) {
    inputMessage = request->getParam(PARAM_INPUT_1)->value();
    inputParam = PARAM_INPUT_1;
    digitalWrite(output, inputMessage.toInt());
    ledState = !ledState;
  }
  else {
    inputMessage = "No message sent";
    inputParam = "none";
  }
  Serial.println(inputMessage);
  request->send(200, "text/plain", "OK");
});

When a request is received on the /state URL, we send the current output state:

server.on("/state", HTTP_GET, [] (AsyncWebServerRequest *request) {
  request->send(200, "text/plain", String(digitalRead(output)).c_str());
});

loop()

In the loop(), we debounce the pushbutton and turn the LED on or off depending on the value of the ledState variable.

digitalWrite(output, ledState);

Demonstration

Upload the code to your ESP32 or ESP8266 board.

Then, open the Serial Monitor at a baud rate of 115200. Press the on-board EN/RST button to get is IP address.

ESP32 ESP8266 NodeMCU Web Server IP Address Arduino IDE Serial Monitor

Open a browser on your local network, and type the ESP IP address. You should have access to the web server as shown below.

ESP32 ESP8266 NodeMCU Control Outputs with Web Server and a Physical Button Simultaneously Turn Off

You can toggle the button on the web server to turn the LED on.

ESP32 ESP8266 NodeMCU Control Outputs with Web Server and a Physical Button Simultaneously Turn On

You can also control the same LED with the physical pushbutton. Its state will always be updated automatically on the web server.

ESP32 Input Output Button Pressed LED On Arduino IDE

Watch the next quick video for a live demonstration:


Wrapping Up

In this tutorial you’ve learned how to control ESP32/ESP8266 outputs with a web server and a physical button at the same time. The output state is always updated whether it is changed via web server or with the physical button.

Other projects you may like:

Learn more about the ESP32 and ESP8266 with our resources:

Thanks for reading.


Learn how to program and build projects with the ESP32 and ESP8266 using MicroPython firmware DOWNLOAD »

Learn how to program and build projects with the ESP32 and ESP8266 using MicroPython firmware DOWNLOAD »


Enjoyed this project? Stay updated by subscribing our weekly newsletter!

16 thoughts on “ESP32/ESP8266: Control Outputs with Web Server and a Physical Button Simultaneously”

  1. Hi, I am using your code for LED and simple web server. I have issue that ESP is disconnected from wifi after circa 3 days. Its happening on all devices. Any idea what it can be?

    Martin

    Reply
  2. This project is fantastic, it will be highly appreciated if the hard coding is avoided for
    WI-FI Credentials by using wi-fi manager

    Reply
  3. Nice! I had just done a very similar project using the AdvancedWebServer example, and range sliders to dim leds using both switches as well as a web page. Instead of XMLHttpRequest, I used ‘fetch’. I also used POST instead of GET to send data from the browser to the ESP32. I wouldn’t have been able to tackle this project without your earlier tutorials to give me the encouragement to try it. So, thanks!

    Reply
  4. Hi, i’am fan of all your projects. They are always very clear explained. I use arduino and the esp8266/esp32 already for a long time, but I’m very bad with internet and javascript. So do you have a example like this to have multiple switches on the page and the update from the esp to the page is given in a json format. So you can update all the switches in 1 go. Thanks in advance.

    Reply
  5. Hi
    I am trying to set this up using and ESP-01. I have tried the button on GPIO2 and 3. If the buttons are connected to the board at bootup, the webserver does not appear to start. If I unplug the button, boot the system up, I can then browse to the webserver. I plug the button in and eveything then works as it should. Not sure what I might be missing.

    Reply
  6. Hi Rui,
    Awesome Project.
    Wifi Manager does not support.
    How to modify code to use without SSID PW hard coding.

    Reply
  7. Hi Rui,
    very helpful project, thank you! Is it possible to update the output state on the web server whenever the switch is pressed using the interrupt function (and not checking the led state every second)?

    Reply

Leave a Comment

Download our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.