ESP32 Async Web Server – Control Outputs with Arduino IDE (ESPAsyncWebServer library)

In this tutorial you’ll learn how to build an asynchronous web server with the ESP32 board to control its outputs. The board will be programmed using Arduino IDE, and we’ll use the ESPAsyncWebServer library.

ESP32 Async Web Server Control Outputs with Arduino IDE

You might also like: ESP8266 NodeMCU Async Web Server – Control Outputs with Arduino IDE (ESPAsyncWebServer library)

Asynchronous Web Server

To build the web server we’ll use the ESPAsyncWebServer library that provides an easy way to build an asynchronous web server. Building an asynchronous web server has several advantages as mentioned in the library GitHub page, such as:

  • “Handle more than one connection at the same time”;
  • “When you send the response, you are immediately ready to handle other connections while the server is taking care of sending the response in the background”;
  • “Simple template processing engine to handle templates”;
  • And much more.

Take a look at the library documentation on its GitHub page.

Parts Required

In this tutorial we’ll control three outputs. As an example, we’ll control LEDs. So, you need the following parts:

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!

Schematic

Before proceeding to the code, wire 3 LEDs to the ESP32. We’re connecting the LEDs to GPIOs 2, 4 and 33, but you can use any other GPIOs (read ESP32 GPIO Reference Guide).

ESP32 Control Three LEDs Outputs Web Server Wiring Circuit Diagram

Installing Libraries – ESP Async Web Server

To build the web server you need to install the following libraries. Click the links below to download the 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.

Project Overview

To better understand the code, let’s see how the web server works.

ESP32 Async Web Server Control Outputs with Arduino IDE mobile responsive web page
  • The web server contains one heading “ESP Web Server” and three buttons (toggle switches) to control three outputs. Each slider button has a label indicating the GPIO output pin. You can easily remove/add more outputs.
  • When the slider is red, it means the output is on (its state is HIGH). If you toggle the slider, it turns off the output (change the state to LOW).
  • When the slider is gray, it means the output is off (its state is LOW). If you toggle the slider, it turns on the output (change the state to HIGH).

How it Works?

ESP32 Async Web Server Control Outputs how it works

Let’s see what happens when you toggle the buttons. We’ll see the example for GPIO 2. It works similarly for the other buttons.

1. In the first scenario, you toggle the button to turn GPIO 2 on. When that happens, the browser makes an HTTP GET request on the /update?output=2&state=1 URL. Based on that URL, the ESP changes the state of GPIO 2 to 1 (HIGH) and turns the LED on.

2. In the second example, you toggle the button to turn GPIO 2 off. When that happens, the browser makes an HTTP GET request on the /update?output=2&state=0 URL. Based on that URL, we change the state of GPIO 2 to 0 (LOW) and turn the LED off.

Code for ESP Async Web Server

Copy the following code to your Arduino IDE.

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

// Import required libraries
#include <WiFi.h>
#include <AsyncTCP.h>
#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 = "output";
const char* PARAM_INPUT_2 = "state";

// 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">
  <link rel="icon" href="data:,">
  <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: 6px}
    .slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 3px}
    input:checked+.slider {background-color: #b30000}
    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?output="+element.id+"&state=1", true); }
  else { xhr.open("GET", "/update?output="+element.id+"&state=0", true); }
  xhr.send();
}
</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 = "";
    buttons += "<h4>Output - GPIO 2</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"2\" " + outputState(2) + "><span class=\"slider\"></span></label>";
    buttons += "<h4>Output - GPIO 4</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"4\" " + outputState(4) + "><span class=\"slider\"></span></label>";
    buttons += "<h4>Output - GPIO 33</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"33\" " + outputState(33) + "><span class=\"slider\"></span></label>";
    return buttons;
  }
  return String();
}

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

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

  pinMode(2, OUTPUT);
  digitalWrite(2, LOW);
  pinMode(4, OUTPUT);
  digitalWrite(4, LOW);
  pinMode(33, OUTPUT);
  digitalWrite(33, LOW);
  
  // 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?output=<inputMessage1>&state=<inputMessage2>
  server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
    String inputMessage1;
    String inputMessage2;
    // GET input1 value on <ESP_IP>/update?output=<inputMessage1>&state=<inputMessage2>
    if (request->hasParam(PARAM_INPUT_1) && request->hasParam(PARAM_INPUT_2)) {
      inputMessage1 = request->getParam(PARAM_INPUT_1)->value();
      inputMessage2 = request->getParam(PARAM_INPUT_2)->value();
      digitalWrite(inputMessage1.toInt(), inputMessage2.toInt());
    }
    else {
      inputMessage1 = "No message sent";
      inputMessage2 = "No message sent";
    }
    Serial.print("GPIO: ");
    Serial.print(inputMessage1);
    Serial.print(" - Set to: ");
    Serial.println(inputMessage2);
    request->send(200, "text/plain", "OK");
  });

  // Start server
  server.begin();
}

void loop() {

}

View raw code

How the Code Works

In this section we’ll explain how the code works. Keep reading if you want to learn more or jump to the Demonstration section to see the final result.

Importing libraries

First, import the required libraries. You need to include the WiFi, ESPAsyncWebserver and the AsyncTCP libraries.

#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

Setting your network credentials

Insert your network credentials in the following variables, so that the ESP32 can connect to your local network.

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

Input Parameters

To check the parameters passed on the URL (GPIO number and its state), we create two variables, one for the output and other for the state.

const char* PARAM_INPUT_1 = "output";
const char* PARAM_INPUT_2 = "state";

Remember that the ESP32 receives requests like this: /update?output=2&state=0

AsyncWebServer object

Create an AsyncWebServer object on port 80.

AsyncWebServer server(80);

Building the Web Page

All the HTML text with styles and JavaScript is stored in the index_html variable. Now we’ll go through the HTML text and see what each part does.

The title goes inside the <title> and </tile> tags. The title is exactly what it sounds like: the title of your document, which shows up in your web browser’s title bar. In this case, it is “ESP Web Server”.

<title>ESP Web Server</title>
ESP32 Async Web Server web page title HTML

The following <meta> tag makes your web page responsive in any browser (laptop, tablet or smartphone).

<meta name="viewport" content="width=device-width, initial-scale=1">

The next line prevents requests on the favicon. In this case, we don’t have a favicon. The favicon is the website icon that shows next to the title in the web browser tab. If we don’t add the following line, the ESP32 will receive a request for the favicon every time we access the web server.

<link rel="icon" href="data:,">

Between the <style></style> tags, we add some CSS to style the web page. We won’t go into detail on how this CSS styling works.

<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: 6px}
    .slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 3px}
    input:checked+.slider {background-color: #b30000}
    input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
</style>

HTML Body

Inside the <body></body> tags is where we add the web page content.

The <h2></h2> tags add a heading to the web page. In this case, the “ESP Web Server” text, but you can add any other text.

<h2>ESP Web Server</h2>

After the heading, we have the buttons. The way the buttons show up on the web page (red: if the GPIO is on; or gray: if the GPIO is off) varies depending on the current GPIO state.

When you access the web server page, you want it to show the right current GPIO states. So, instead of adding the HTML text to build the buttons, we’ll add a placeholder %BUTTONPLACEHOLDER%. This palceholder will then be replaced with the actual HTML text to build the buttons with the right states, when the web page is loaded.

%BUTTONPLACEHOLDER%

JavaScript

Then, there’s some JavaScript that is responsible to make an HTTP GET request when you toggle the buttons as we’ve explained previously.

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

Here’s the line that makes the request:

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

element.id returns the id of an HTML element. The id of each button will be the GPIO controlled as we’ll see in the next section:

  • GPIO 2 button » element.id = 2
  • GPIO 4 button » element.id = 4
  • GPIO 33 button » element.id = 33

Processor

Now, we need to create the processor() function, that replaces the placeholders in the HTML text with what we define.

When the web page is requested, check if the HTML has any placeholders. If it finds the %BUTTONPLACEHOLDER% placeholder, it returns the HTML text to create the buttons.

String processor(const String& var){
  //Serial.println(var);
  if(var == "BUTTONPLACEHOLDER"){
    String buttons = "";
    buttons += "<h4>Output - GPIO 2</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"2\" " + outputState(2) + "><span class=\"slider\"></span></label>";
    buttons += "<h4>Output - GPIO 4</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"4\" " + outputState(4) + "><span class=\"slider\"></span></label>";
    buttons += "<h4>Output - GPIO 33</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"33\" " + outputState(33) + "><span class=\"slider\"></span></label>";
    return buttons;
  }
  return String();
}

You can easily delete or add more lines to create more buttons.

Let’s take a look at how the buttons are created. We create a String variable called buttons that contains the HTML text to build the buttons. We concatenate the HTML text with the current output state so that the toggle button is either gray or red. The current output state is returned by the outputState(<GPIO>) function (it accepts as argument the GPIO number). See below:

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

The \ is used so that we can pass “” inside the String.

The outputState() function returns either “checked” if the GPIO is on or and empty field “” if the GPIO is off.

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

So, the HTML text for GPIO 2 when it is on, would be:

<h4>Output - GPIO 2</h4>
<label class="switch">
<input type="checkbox" onchange="toggleCheckbox(this)" id="2" checked><span class="slider"></span>
</label>

Let’s break this down into smaller sections to understand how it works.

In HTML, a toggle switch is an input type. The <input> tag specifies an input field where the user can enter data. The toggle switch is an input field of type checkbox. There are many other input field types.

<input type="checkbox">

The checkbox can be checked or not. When it is check, you have something as follows:

<input type="checkbox" checked>

The onchange is an event attribute that occurs when we change the value of the element (the checkbox). Whenever you check or uncheck the toggle switch, it calls the toggleCheckbox() JavaScript function for that specific element id (this).

The id specifies a unique id for that HTML element. The id allows us to manipulate the element using JavaScript or CSS.

<input type="checkbox" onchange="toggleCheckbox(this)" id="2" checked>

setup()

In the setup() initialize the Serial Monitor for debugging purposes.

Serial.begin(115200);

Set the GPIOs you want to control as outputs using the pinMode() function and set them to LOW when the ESP32 first starts. If you’ve added more GPIOs, do the same procedure.

pinMode(2, OUTPUT);
digitalWrite(2, LOW);
pinMode(4, OUTPUT);
digitalWrite(4, LOW);
pinMode(33, OUTPUT);
digitalWrite(33, LOW);

Connect to your local network and print the ESP32 IP address.

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

In the setup(), you need to handle what happens when the ESP32 receives requests. As we’ve seen previously, you receive a request of this type:

<ESP_IP>/update?output=<inputMessage1>&state=<inputMessage2>

So, we check if the request contains the PARAM_INPUT1 variable value (output) and the PARAM_INPUT2(state) and save the corresponding values on the input1Message and input2Message variables.

if (request->hasParam(PARAM_INPUT_1) && request->hasParam(PARAM_INPUT_2)) {
  inputMessage1 = request->getParam(PARAM_INPUT_1)->value();
  inputMessage2 = request->getParam(PARAM_INPUT_2)->value();

Then, we control the corresponding GPIO with the corresponding state (the inputMessage1 variable saves the GPIO number and the inputMessage2 saves the state – 0 or 1)

digitalWrite(inputMessage1.toInt(), inputMessage2.toInt());

Here’s the complete code to handle the HTTP GET /update request:

server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
  String inputMessage1;
  String inputMessage2;
  // GET input1 value on <ESP_IP>/update?output=<inputMessage1>&state=<inputMessage2>
  if (request->hasParam(PARAM_INPUT_1) && request->hasParam(PARAM_INPUT_2)) {
    inputMessage1 = request->getParam(PARAM_INPUT_1)->value();
    inputMessage2 = request->getParam(PARAM_INPUT_2)->value();
    digitalWrite(inputMessage1.toInt(), inputMessage2.toInt());
  }
  else {
    inputMessage1 = "No message sent";
    inputMessage2 = "No message sent";
  }
  Serial.print("GPIO: ");
  Serial.print(inputMessage1);
  Serial.print(" - Set to: ");
  Serial.println(inputMessage2);
  request->send(200, "text/plain", "OK");
});

Finally, start the server:

server.begin();

Demonstration

After uploading the code to your ESP32, open the Serial Monitor at a baud rate of 115200. Press the on-board RST/EN button. You should get its IP address.

Open a browser and type the ESP IP address. You’ll get access to a similar web page.

ESP32 Async Web Server Control Outputs web browser demonstration GPIOs

Press the toggle buttons to control the ESP32 GPIOs. At the same time, you should get the following messages in the Serial Monitor to help you debug your code.

ESP32 Async Web Server Control Outputs demonstration mobile

You can also access the web server from a browser in your smartphone. Whenever you open the web server, it shows the current GPIO states. Red indicates the GPIO is on, and gray that the GPIO is off.

Web Server

Wrapping Up

In this tutorial you’ve learned how to create an asynchronous web server with the ESP32 to control its outputs using toggle switches. Whenever you open the web page, it shows the updated GPIO states.

We have other web server examples using the ESPAsyncWebServer library that you may like:

We hope you found this tutorial useful. If you have any questions, post a comment below and we’ll try to get back to you.

If you like ESP32, you might consider enrolling in our course “Learn ESP32 with Arduino IDE“. You can also access our free ESP32 resources here.

Thank you for reading.



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Recommended Resources

Build a Home Automation System from Scratch » With Raspberry Pi, ESP8266, Arduino, and Node-RED.

Home Automation using ESP8266 eBook and video course » Build IoT and home automation projects.

Arduino Step-by-Step Projects » Build 25 Arduino projects with our course, even with no prior experience!

What to Read Next…


Enjoyed this project? Stay updated by subscribing our newsletter!

80 thoughts on “ESP32 Async Web Server – Control Outputs with Arduino IDE (ESPAsyncWebServer library)”

  1. Hey guys!

    Thanks for this new tutorial!

    I’ve been following your work around the ESP32 for a while now. And I continually notice your habit (or preference?) of systematically using the Javascript object XMLHttpRequest. And I must admit that I find it hard to understand this trend, when there are nowadays other, more efficient, lighter and above all faster ways to manage asynchronous interactions between a server and clients. For example, Javascript now offers the more modern Fetch and Async/Await APIs. You can take a look at one of my GitHub project which uses the Async/Await Promises for example:

    ESP32 Asynchronous Web Controlled Thermostat
    https://github.com/m1cr0lab-esp32/asynchronous-web-controlled-thermostat

    I had made this mini-project for Yves B., following a discussion on RNT Lab to help him start his own project:
    https://rntlab.com/question/java-script-code-to-refresh-home-page-only-once/

    I had also posted a reference to my project to share it with your community:
    https://rntlab.com/question/full-project-esp32-asynchronous-web-controlled-thermostat/

    But there is another technology supported by Javascript even more interesting: the WebSocket protocol. Today, I would like to shed some light on how to implement it efficiently for the ESP32. You’ll see that it has only advantages over other techniques. I have recently written a comprehensive tutorial on the subject:

    ESP32 Remote Control with WebSocket
    https://m1cr0lab-esp32.github.io/remote-control-with-websocket/

    I’m sharing it here, because I think it may also be of interest to most of your readers.
    If you take the time to read it, feel free to share your opinion with me on this thread:

    https://rntlab.com/question/full-tutorial-esp32-remote-control-with-websocket/

    My intention is not to overshadow your work, quite the contrary, but rather to add to it what I think is interesting. I hope that you’ll welcome my approach.

    Best wishes,
    Steph

    Reply
    • Hi Steph.
      Thank you so much for your contribution. This can be very useful for our readers.
      I’ll take a look at those tutorials and see what we can implement to improve our projects.
      The methods we were using for automatically updating the web page worked well, but as you stated are not the most efficient. That’s true.
      Now, we’ve been implementing server-sent events to automatically send data from the server to the client when there are new sensor readings available. See the project that we’ll release today or this project: https://randomnerdtutorials.com/esp32-esp-now-wi-fi-web-server/
      However, for automatically updating the state of the buttons and GPIOs on all clients at the same time, a two way communication like web sockets should be more suitable. I’ll take a look on how to implement that and improve our projects.
      Thanks for sharing this valuable information.
      Regards,
      Sara

      Reply
      • Thank you, Sara,

        Yes, I read your article on server-sent events. It’s good that you presented it to your readers. It complements the unidirectional approach of XMLHttpRequest with another approach… but still unidirectional 🙂

        If you read my tutorial in detail, you’ll see that WebSockets solve this problem by killing two birds with one stone. The interactions are bidirectional and the implementation remains very simple. In addition, the WebSocket protocol is much faster and lighter than HTTP.

        Reply
      • Too bad you can’t edit your own comments…

        I just wanted to add that in projects where you want to take measurements from various sensors, and as soon as they are available at ESP32, WebSockets are also the best solution. And this, whether there is a single customer or a whole colony 😉

        WebSockets is an advanced technology that allows real-time interactive bidirectional communication between the client and a server. In simple terms, WebSockets make it possible for data to be transferred from the client to the server and vice versa in real time. With WebSocket protocol, you can send messages to a server and receive event-driven responses without having to poll the server for a reply.

        Server-Sent Events (SSE) are unidirectional: only allowing client(s) to receive data from the server.

        Reply
  2. Hey,
    That’s really nice!
    Is there any tutorial where it is ecplained how to do the opposit? send http values to an existing webserver?

    Reply
  3. Dear Sara,

    thank you very much for this useful tutorial.

    In reference to the RNT Course “Learn ESP32 with Arduino IDE – 2nd Edition”, Module 4 “ESP32 Web Server”, Unit 6 “Making Your ESP32 Web Server Password Protected”, how would you implement the same password protection (with Base64 encryption) under the present context, meaning with an object ‘AsyncWebServer’ instead of an object ‘WiFiServer’ as this is the case in the course.

    Thank you and best regards,

    JM

    Reply
  4. He Sara,

    first of all, thanks for the tutorials! I’m trying to combine 2 tutorials of you, the one where you make a website with temperature and humidity via the DHT sensor, and the second is this tutorial. Sadly I didn’t succeed yet, it says: ‘outputState’ not declared in this scope . How can I fix this?

    Thanks!

    Reply
  5. Hi Sara,
    Is there any way to control the ESP32 outputs from anywhere in the world, and not just only when being connected to the same wifi network?

    Reply
  6. Hi,
    Your tutos are always great, interesting and straightforward (or almost).
    I was thinking for a way to get many variables from a webpage on a webserver.
    this tuto answers.
    i just read , thanks to you, that i can add more on the request line …. after the ?

    i read also about the websockets, which would be a “easier” solution, since i understand it 🙂 . I’m using a mesh network too. i’m not confident websockets works with it to :/

    Reply
  7. Hi,

    I’m very new into programming but this tutorials are really good so far.
    i have upload the code and it’s work fine, the only problem is to connect with the smartphone. What kind of browser is required for the connection?
    In windows i use the execute command for the ip address and it is working good.

    Kind regards,
    Martijn

    Reply
  8. Hi Sara
    Thank you for all your very interesting tutos
    In this example you use the send_P function to initialize the checkboxes/slidder by the way of a processor function.: request->send_P(200, “text/html”, index_html, processor);
    This work fine if the HTML code is imbedded in the C++ code.
    How do you process if the HTML code is in an external html file.
    Regards
    Claude

    Reply
  9. Interesting tutorial.
    I have very little knowledge of HTML, and with all the other stuff I’m trying to learn about Home Assistant, Node Red, Javascript, Python and Arduino/ESP code, I don’t want to add HTML to that list.
    Is there a resource where I could visually design my web page and have the Index_html created automatically?

    Reply
  10. Using the ‘%’ for the processor placeholders may lead into problems if the site contains values in percentage.
    For example ‘width: 100%;’
    Everything after the ‘%’ until the amount of defined chars will be interpreted as a placeholder.
    But #define TEMPLATE_PLACEHOLDER ‘$’ doesn’t work for me.
    The compiler throws a redefinition error. Doesn’t matter where i put the definement.
    If you have the same compiler issue, you can change it in “WebResponseImpl.h”.

    Have fun

    Reply
  11. Hi guys,
    I would like to understand how the CSS and HTML parts work in regards to the creation of the slide buttons.

    Thanks for your help

    Alan Masutti

    Reply
  12. Hello, is it possible to show the number of connected clients and their IP on the web page? I have tried and it has not been possible, by terminal yes.
    Regards.

    Reply
  13. Hello Sara,

    Is there any example of a Async Web server using the ESP32-S2’s? I am having problems using AsyncWebServer with ESP32-32 saola.

    Reply
  14. Hello Sara,

    is if possible to transform the code, so that instead of changing the status of the Port just a variable gets changed from one to zero?
    If it is possible, how would the code change?

    Thanks in advance

    Reply
    • I mean i know that i can just delete the digitalWrite(inputMessage1.toInt(), inputMessage2.toInt()); line but then i would have lots stuff going on in the background for nothing, wouldn’t I?

      Reply
      • Hi.
        You can delete those lines and any other lines referring to the GPIOs like the pinMode() and the pins definition at the beginning of the code.
        Regards,
        Sara

        Reply
        • Thanks for the quick answer. i got it to work 🙂 But while doing the coding a new question arose. Is it possible to change and update the status of the check boxes used according to a boolean? So that when i would change the boolean from zero to one anywhere in the code the checkbox would update and change it’s status.
          And if so how would someone pull it off.

          Sorry for taking so much of your time and thanks again for the guidance.

          Reply
  15. Hello Sara,

    first of all thanks for your great tutorials which guided me wonderful so far. Now to the reason I am writing.
    I am currently trying to combine this project with something similar to your https://randomnerdtutorials.com/esp32-web-server-sent-events-sse/ ,which i used as a base for my little project.
    But as I tried to combine the to html sections i get the error “‘outputState’ was not declared in this scope” even though i implemented it before setup().
    I located the problem and it appears that
    “” is somehow essential for the woking of the code. I noticed this because i replaced it with
    ” ” from your other project and that is when the error occurred. I tried to write the 2 lines after one another as well but that does not seem to solve the problem.
    Any ideas what the problem might be?

    Reply
    • Hi.
      Thanks for following our work.
      The error “was not declared in this scope” means that the variable was not declared before (or it might have been declared locally in another section).
      As for the other issue, there might be some syntax errors in your code.
      Regards,
      Sara

      Reply
      • Thanks for the tip, I will continue trying and if i find my mistake I’ll share it. But even if it does not work out it’s fine, as only the icons are not working.

        Reply
      • Hello Sara,

        This tutorial is very useful. I read the whole blog and This helps me a lot…. Thanks.

        But, I’m facing the same issue….

        like this.

        C++ main.cpp (project name) src
        “‘outputState’ was not declared in this scope [Ln 90, Col 31]”

        In PlatformIO Visual Studio Code.

        Please reply to me as soon as possible.

        Reply
        • Hi.
          If you’re using VS Code, you need to move the outputState function to a place before the processor functions (where it is used).
          Regards,
          Sara

          Reply
  16. hi. i am having trouble setting a static IP for this sketch. it works fine except that i have to check the IP in the monitor before i can continue. thx

    Reply
      • this is the beginning of my code
        #include <WiFi.h>
        #include <AsyncTCP.h>
        #include <ESPAsyncWebServer.h>

        const char* ssid = “xxxx”;
        const char* password = “xxxx”;

        const char* PARAM_INPUT_1 = “output”;
        const char* PARAM_INPUT_2 = “state”;

        // Static IP
        IPAddress local_IP(192, 168, 1, 184);
        IPAddress gateway(192, 168, 1, 1);
        IPAddress subnet(255, 255, 0, 0);
        IPAddress primaryDNS(xx,xx,xx,xx);
        IPAddress secondaryDNS(xx,xx,xx,xx);
        // Create AsyncWebServer object on port 80
        AsyncWebServer server(80);

        based on your ‘esp32-async-web-server-espasyncwebserver-library’

        when it runs this code:
        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());

        it returns a different IP so i have to check the IP to be able to access the site

        thx for your help

        Reply
  17. Hi Sara,

    Thanks for the greats tutorials.
    Im trying implement this project with one more led but also input 4 buttons, I already searched in this page but dont find any solution.

    Reply
    • this line manages the buttons:

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

      add further buttons and allocate GPIO (eg GPIO 21)…

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

      just keep adding lines to add more buttons

      Reply
  18. Hi guys-
    Your tutorials and guides have been very useful.
    However on this project, I get this fatal error when I compile the code.

    /AsyncTCP.h:26:10: fatal error: sdkconfig.h: No such file or directory

    Any thoughts on what is causing the error?
    Thanks and keep up the good work!

    Reply
  19. Hi, i use this code in my project, and it’s working very good, but i have a lot of buttons in my web page, so i want to place the buttons near each other (one on the left,one on the right,next line the same),but can’t find a solution for this(display:inline block doesn’t seem to work ).
    an other problem: i cannot see my buttons on a windows pc, only in android ( i only see text,no buttons in windows).

    Reply
  20. Hi, I use this code in one of my projects and it’s working very good.
    because i use a lot of buttons, i want to place them near each other (side by side), does anyone can help me with this ?
    I noticed, that the code isn’t working very well on my windows (explorer or chrome) browser,the buttons are not visible here.
    On my android phone(chrome browser),it’s working correctly..

    Reply
  21. Hi,

    I really appreciate the tutorial. I have added 4 more buttons but can you please guide me to arrange the button in horizontal order please.

    Thank you very much,
    Sandy

    Reply
  22. Hello, I really liked the tutorial a lot but I would really like to know how to do the opposite. I want to know how to control the inputs through a button inside the web server

    Reply
  23. Hey, Is there any way to use a local domain?
    For example esp32.local
    For my project it is impractical to look for the ip in the serial monitor every time.

    Reply
    • Thanks for the tutorial!
      I found it worked fine in Edge and Chrome, but to get it to work properly in Firefox I had to add autocomplete = “off” to the buttons.

      <input type=\”checkbox\” autocomplete=\”off\” onchange=...

      Without that, buttons stay checked if you reset the esp32 and hit refresh on the browser.

      Reply
      • OK, that seems to have been posted to the wrong place. Meant to put that comment at the bottom.
        What I intended to say to Mihail was:

        To access the esp32 by name instead of ip address

        #include <ESPmDNS.h>
        then after WiFi.begin() add:

        if(!MDNS.begin("esp32")){
        Serial.println("Error starting mDNS");
        return;
        }

        Then your browser will see eps32.local. Or at least it does in Windows 10.

        Reply
  24. Hello,
    I really liked the tutorial and I would try it with arduino IDE

    I use exactly this tuto without changing anything exept my credentials

    but I have a compiler error :
    D:\Documents\Arduino\libraries\ESPAsyncTCP\src\ESPAsyncTCPbuffer.cpp:27:10: fatal error: debug.h: No such file or directory
    #include <debug.h>
    ^~~~~~~~~
    compilation terminated.
    exit status 1
    Erreur de compilation pour la carte NodeMCU-32S

    I update the ESPAsyncTCP library, I checked the arduino preference,
    every seems correct and uptodate

    Do you know why there is this error

    Thank you for your answer

    Reply
    • Hi.
      I’m not sure what might be wrong.
      What’s your ESP32 boards version? Tools > Boards > Boards Manager > ESP32. Try with version 2.0.1
      Regards,
      Sara

      Reply
  25. Oi Sara,
    como faço para retornar ao formulario diretamente depois de salvar os dados no lugar de ir para “request->send(200, “text/plain”, “OK”); ” onde depois devo reingresar ao formulario?

    Reply
  26. Hello,

    I’m using a part of this tutorial.
    I have made several webpages in my arduino file.
    Everything works great.

    But now i deleted all the CSS codes and paste it in a style.css file in the folder data.
    How can i use this style sheet?

    in my head i put:

    and i put a request in my setup:
    server.on(“/style.css”, HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(SPIFFS, “/style.css”, “text/css”);
    });

    But this is not working…

    Can you help me please?

    Reply
  27. Hi,

    Thank you for your work. setInterval makes the xmlhttp request being sent over and over, which makes the memory heap on the esp32 to overflow after a long long time. The information that I got on the Esp32 is this: [ 32680][W][AsyncTCP.cpp:949] _poll(): pcb is NULL.
    I believe that it has to be with the tcp protocol keep alive feature, but I do not know how to solve it.
    I already try to abort and to use timeout on the browser side, but I believe that once the request is open on the esp32, it got stuck on the heap.

    Reply
  28. Hey! I’m doing a project with ESP-32CAM. I’m using the code “Video Streaming Web Server Sensor Readings” as a base and would like to add some buttons to control a car with gas sensors. Any idea how to do with the ESPAsyncWebServer library?

    Reply
  29. Hello Sara, I am using a DS3231 RTC for reasons that the internet drops a lot. Through the serial port I print the time every second, my question is, how can I do so that I can see it via the web? I’m using this tutorial, just add the rtc to turn on lights.

    What I print via serial is like this:

    DateTime now = rtc.now();
    Serial.print(“Hora: “);
    Serial.print(now.hour());
    Serial.print(“:”);
    Serial.print(now.minute());
    Serial.print(“:”);
    Serial.println(now.second());
    Serial.print(” Fecha: “);
    Serial.print(” Dia: “);
    Serial.print(now.dayOfWeek());
    Serial.print(“Mes:”);
    Serial.print(now.month());
    Serial.print(“Año:”);
    Serial.println(now.year());
    delay(1000);

    Reply
  30. Hola. Gracias por tanta información. Cómo podría detectar la ausencia de cliente web con las librerias ESPAsyncWebServer.h y AsyncTCP.h?.

    Necesito que el ESP siga leyendo y tomando decisiones con respecto a sus lecturas cuando el cliente se desconecte.

    Reply
  31. error: ‘mbedtls_md5_starts_ret’ was not declared in this scope; did you mean ‘mbedtls_md5_starts’?
    74 | mbedtls_md5_starts_ret(&_ctx);
    error: ‘mbedtls_md5_update_ret’ was not declared in this scope; did you mean ‘mbedtls_md5_update’?
    75 | mbedtls_md5_update_ret(&_ctx, data, len);
    error: ‘mbedtls_md5_finish_ret’ was not declared in this scope; did you mean ‘mbedtls_md5_finish’?
    76 | mbedtls_md5_finish_ret(&_ctx, _buf);

    how to solve this ?

    Reply
      • Hello Sara
        I have updated to latest ESP32 in Arduino IDE, and changed all instances to platform = [email protected] in all platfor,io.ini files i could find under Arduino folders/subfolders, but I still get compiler error:
        c:\Users\ehv\OneDrive\Dokumenter\Arduino\libraries\ESPAsyncWebServer\src\WebAuthentication.cpp: In function ‘bool getMD5(uint8_t*, uint16_t, char)’:
        c:\Users\ehv\OneDrive\Dokumenter\Arduino\libraries\ESPAsyncWebServer\src\WebAuthentication.cpp:74:3: error: ‘mbedtls_md5_starts_ret’ was not declared in this scope; did you mean ‘mbedtls_md5_starts’?
        74 | mbedtls_md5_starts_ret(&_ctx);
        | ^~~~~~~~~~~~~~~~~~~~~~
        | mbedtls_md5_starts
        c:\Users\ehv\OneDrive\Dokumenter\Arduino\libraries\ESPAsyncWebServer\src\WebAuthentication.cpp:75:3: error: ‘mbedtls_md5_update_ret’ was not declared in this scope; did you mean ‘mbedtls_md5_update’?
        75 | mbedtls_md5_update_ret(&_ctx, data, len);
        | ^~~~~~~~~~~~~~~~~~~~~~
        | mbedtls_md5_update
        c:\Users\ehv\OneDrive\Dokumenter\Arduino\libraries\ESPAsyncWebServer\src\WebAuthentication.cpp:76:3: error: ‘mbedtls_md5_finish_ret’ was not declared in this scope; did you mean ‘mbedtls_md5_finish’?
        76 | mbedtls_md5_finish_ret(&_ctx, _buf);
        | ^~~~~~~~~~~~~~~~~~~~~~
        | mbedtls_md5_finish
        c:\Users\ehv\OneDrive\Dokumenter\Arduino\libraries\ESPAsyncWebServer\src\AsyncEventSource.cpp: In member function ‘void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage
        )’:
        c:\Users\ehv\OneDrive\Dokumenter\Arduino\libraries\ESPAsyncWebServer\src\AsyncEventSource.cpp:189:7: error: ‘ets_printf’ was not declared in this scope; did you mean ‘vswprintf’?
        189 | ets_printf(“ERROR: Too many messages queued\n”);
        | ^~~~~~~~~~
        | vswprintf
        c:\Users\ehv\OneDrive\Dokumenter\Arduino\libraries\ESPAsyncWebServer\src\AsyncWebSocket.cpp: In member function ‘void AsyncWebSocketClient::_queueMessage(AsyncWebSocketMessage*)’:
        c:\Users\ehv\OneDrive\Dokumenter\Arduino\libraries\ESPAsyncWebServer\src\AsyncWebSocket.cpp:549:7: error: ‘ets_printf’ was not declared in this scope; did you mean ‘vswprintf’?
        549 | ets_printf(“ERROR: Too many messages queued\n”);
        | ^~~~~~~~~~
        | vswprintf

        exit status 1
        I am using ESPAsyncWebServer vers. 3.1.0 – My arduino IDE is vers. 2.3.2 Everything updated to latest revisions.
        what am I missing ? 🙂
        Best Regards
        Erling

        Reply
        • UPDATE – I deleted the ESPAsyncWebServer llibrary folder AND the AsyncTCP library folder, and reinstalled new updated versions from the links – That fixed the problem and the program compiles without errors. Btw I changed also all PlatformIO.ini instances with platform = [email protected] latest version.
          So the problem was, that these two library files are not updated automatically (as indirectly mentioned in the article) even they both shows up in the Library manager. So these needs to be manually updated ALWAYS.
          So Problem solved for now 🙂
          Thanks – appreciate your good support.
          Best Regards
          Erling

          Reply
  32. Hello. This approach works very well, thank you for this tutorial. However, it implements HTTP, not HTTPS! When your “web server” is shared externally, it’s becoming almost impossible now, for security reasons, not to implement HTTPS. Is this now possible? Do you have a tutorial? (The only one I’ve found isn’t about using ESPAsyncWebServer). Thanks a lot!

    Reply
    • I reply myself, the solution was to put in place a reverse proxy ! This way the client arrives in HTTPS and routed to a http web site ! This works perfectly without headhache !

      Reply
  33. I was looking into integrating wifimanager and elegantOTA together under AsyncWebServer lib but I need some help. Do you cover this subject under your editions or in your site? Thank you.

    Reply
  34. Dear Sara and Rui, as you are updating your ESP32 projects to ESP 3.x – can you please please please change this line in code: “server.on(“/update”, HTTP_GET, [] (AsyncWebServerRequest *request)” to e.g. “server.on(“/updatebutton”, HTTP_GET, [] (AsyncWebServerRequest *request)”. I added some parts of his project to a project that uses the OTA Over the Air update mechanism. After adding the above codelines to the project and uploading everything worked well, but the next update failed. The reason was easy: the OTA webserver is using the “/update” und the hood, meaning this option is not visible in the project’s sourcecode directly. Thanks for your good working tutorials Michael

    Reply
  35. Hello Sara and Rui,

    I have been running into a compiling error with the webserver library, it started when I attempted to update ElegantOTA (non pro), I have updated and reinstalled both ESPAsyncWebServer and AsyncTCP with no luck.
    Output Error message:
    ESPDashPro.cpp: In lambda function

    ESPDashPro.cpp: 56:109: error: no matching function for call to ‘AsyncWebServerRequest::beginResponse(int, const char [10], const uint8_t [188958], unsigned int)
    AsyncWebServerResponse *response = request->beginResponse(200, “text\html”, DASH_HTML, sizeof(DASH_HTML))

    ESPDashPro.h:107: In file included from
    ESPDashPro.cpp:1: from
    ESPAsyncWebServer.h:246: note candidate AsyncWebServerResponse* AsyncWebServerRequest beginResponse(int, const String&, const String&)
    AsyncWebServerResponse beginResponse(int code, const String& contentType=String(), const String& content=String())
    ^~~~~~~~~~~~~
    ESPAsyncWebServer.h:246: note candidate expects 3 arguments, 4 provided
    ESPAsyncWebServer.h:247: note candidate AsyncWebServerResponse
    AsyncWebServerRequest beginResponse(AsyncWebServerRequest FS&, const String&, const String&, bool, AwsTemplateProcessor)
    AsyncWebServerResponse beginResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr)
    ^~~~~~~~~~~~~
    ESPAsyncWebServer.h:247: note no known conversion for argument 1 from int to AsyncWebServerRequest FS& {aka fs FS&}
    ESPAsyncWebServer.h:248: note candidate AsyncWebServerResponse
    AsyncWebServerRequest beginResponse(AsyncWebServerRequest File, const String&, const String&, bool, AwsTemplateProcessor)
    AsyncWebServerResponse beginResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr)
    ^~~~~~~~~~~~~
    ESPAsyncWebServer.h:248: note no known conversion for argument 1 from int to AsyncWebServerRequest File {aka fs File}
    ESPAsyncWebServer.h:249: note candidate AsyncWebServerResponse
    AsyncWebServerRequest beginResponse(Stream&, const String&, size_t, AwsTemplateProcessor)
    AsyncWebServerResponse beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr)
    ^~~~~~~~~~~~~
    ESPAsyncWebServer.h:249: note no known conversion for argument 1 from int to Stream&
    ESPAsyncWebServer.h:250: note candidate AsyncWebServerResponse
    AsyncWebServerRequest beginResponse(const String&, size_t, AwsResponseFiller, AwsTemplateProcessor)
    AsyncWebServerResponse *beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr)
    ^~~~~~~~~~~~~
    ESPAsyncWebServer.h:250: note no known conversion for argument 3 from const uint8_t [188958] {aka const unsigned char [188958]} to AwsResponseFiller {aka std function}
    ESPDashPro.cpp: In lambda function

    ESPDashPro.cpp: 68:112: error: no matching function for call to ‘AsyncWebServerRequest::beginResponse(int, const char [10], const uint8_t [7655], unsigned int)
    AsyncWebServerResponse *response = request->beginResponse(200, DASH_LOGO_MIME, DASH_LOGO, sizeof(DASH_LOGO))

    ESPDashPro.h:107: In file included from
    ESPDashPro.cpp:1: from
    ESPAsyncWebServer.h:246: note candidate AsyncWebServerResponse* AsyncWebServerRequest beginResponse(int, const String&, const String&)
    AsyncWebServerResponse beginResponse(int code, const String& contentType=String(), const String& content=String())
    ^~~~~~~~~~~~~
    ESPAsyncWebServer.h:246: note candidate expects 3 arguments, 4 provided
    ESPAsyncWebServer.h:247: note candidate AsyncWebServerResponse
    AsyncWebServerRequest beginResponse(AsyncWebServerRequest FS&, const String&, const String&, bool, AwsTemplateProcessor)
    AsyncWebServerResponse beginResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr)
    ^~~~~~~~~~~~~
    ESPAsyncWebServer.h:247: note no known conversion for argument 1 from int to AsyncWebServerRequest FS& {aka fs FS&}
    ESPAsyncWebServer.h:248: note candidate AsyncWebServerResponse
    AsyncWebServerRequest beginResponse(AsyncWebServerRequest File, const String&, const String&, bool, AwsTemplateProcessor)
    AsyncWebServerResponse beginResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr)
    ^~~~~~~~~~~~~
    ESPAsyncWebServer.h:248: note no known conversion for argument 1 from int to AsyncWebServerRequest File {aka fs File}
    ESPAsyncWebServer.h:249: note candidate AsyncWebServerResponse
    AsyncWebServerRequest beginResponse(Stream&, const String&, size_t, AwsTemplateProcessor)
    AsyncWebServerResponse beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr)
    ^~~~~~~~~~~~~
    ESPAsyncWebServer.h:249: note no known conversion for argument 1 from int to Stream&
    ESPAsyncWebServer.h:250: note candidate AsyncWebServerResponse
    AsyncWebServerRequest beginResponse(const String&, size_t, AwsResponseFiller, AwsTemplateProcessor)
    AsyncWebServerResponse *beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr)
    ^~~~~~~~~~~~~
    ESPAsyncWebServer.h:250: note no known conversion for argument 3 from const uint8_t [7655] {aka const unsigned char [7655]} to AwsResponseFiller {aka std function}
    Error compiling libraries

    I use the VisualMicro extension for Visual Studio as my coding environment.
    and I use ESPDash in my project which also requires ESPAsyncwebserver.

    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.