In this user guide, we will focus on building an async web server. The web server will be created in ESP8266 NodeMCU using Arduino IDE through the ESPAsyncWebServer library. The web server will allow the user to control multiple LEDs connected with the ESP8266 NodeMCU GPIO pins simultaneously by using HTTP GET requests to transfer the data from the user to the client.
Asynchronous web servers come with significant advantages as they can handle several connections at the same time.
We have a similar guide with ESP32:
Link to Project: ESP32 Asynchronous Web Server using Arduino IDE
Asynchronous Web Server Introduction
What is Asynchronous Web Server?
An asynchronous web server is a server that can handle XMLHttpRequests requests in a scalable fashion. In the case of traditional thread based servers, there is a dedicated separate thread for each client to serve that particular client. But it may induce blocking problems when a server is waiting for the process to release the resources.
On the contrary, Asynchronous servers do not create a separate thread for each client request. It has a worker process that accepts HTTP requests from all clients and processes client requests by using event-driven efficient loops. In other words, in an asynchronous server, client HTTP requests do not block each other and get executed concurrently.
Asynchronous Web Server Library ESP8266
In this tutorial, we will use the ESPAsyncWebServer library to build an Asynchronous Web Server with ESP8266 NodeMCU and Arduino IDE. Using an Async web server has many benefits as discussed on the official GitHub page of this library:
- Using an asynchronous means server can handle more than one connection at the same time from clients
- You are called once the request is ready and parsed
- 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
- Speed is fast
- Easy to use API, HTTP Basic and Digest MD5 Authentication (default), ChunkedResponse
- Easily extendible to handle any type of content
- Supports Continue 100
- Async WebSocket plugin offering different locations without extra servers or ports
- Async EventSource (Server-Sent Events) plugin to send events to the browser
- URL Rewrite plugin for conditional and permanent URL rewrites
- ServeStatic plugin that supports cache, Last-Modified, default index, and more
- Simple template processing engine to handle templates
Installing ESPAsyncWebServer Libraries
The ESPAsyncWebServer library will help us in creating a web server with ESP8266 easily. With this library, we will set an asynchronous HTTP server. ESPAsyncTCP is another library that we will be incorporating as it a dependency for the ESPAsyncWebServer library. This library will not be used directly inside our program code and only acts as the base for the first library.
Both of these libraries are not available in the Arduino library manager so we will have to download and load them in the IDE ourselves. We will use GitHub to download the respective libraries and then place them in the library folder of our Arduino IDE.
Click ESPAsyncWebServer library and ESPAsyncTCP library to open the respective GitHub pages for the libraries.
The webpage when you open the ESPAsyncWeb Server link will look something like this.
Click the Code button and go to the Download Zip option as highlighted in the figure. Your zip file will get downloaded to your computer right away. After the download is complete, extract the .zip file to the Arduino library folder. A similar process will apply to the installation of the ESPAsyncTCP library as well. Make sure you rename the extracted files as ESPAsyncWebServer and ESPAsyncTCP accordingly.
You can also go to Sketch > Include Library > Add .zip Library inside the IDE to add the libraries as well. Through this procedure now we will be able to use the functionalities of the libraries inside our Arduino IDE.
ESP8266 NodeMCU Asynchronous Web Server Project Overview
We will control 4 LED outputs through the asynchronous web server. Our aim is to control outputs so we will use LEDs for simplicity and ease of use. In this project , you will require the following components:
Required Components
- ESP8266 NodeMCU development board
- Four 5mm LEDs
- Four 220ohm resistors
- Breadboard
- Connecting Wires
Schematic Diagram
Follow the schematic diagram below to assemble your circuit.
We have used four different coloured LEDs and connected their anode pin with four different GPIO pins. Later on, in the program code, we will configure these GPIO pins as output pins. The cathode pins are grounded through the 220ohm resistors.
The table below shows the GPIO PINs connected with each LED.
LED | GPIO PIN |
---|---|
Blue | GPIO5 |
Yellow | GPIO4 |
Red | GPIO0 |
Green | GPIO2 |
You can choose any suitable output GPIO pins as you prefer.
How Asynchronous Web Server Works?
We will now demonstrate the working of the webserver.
The webpage will consist of the title ‘ESP8266 NodeMCU WEB SERVER’ and four toggle buttons one after another. These buttons will control the GPIO output pins connected with the four LEDs. The buttons will be able to slide back and forth hence giving rise to the toggle effect. When the slider button will be red then the LED will turn ON (output is on) and when the slider button will be grey then the LED will be OFF (output is off). Sliding the button will toggle the output. Each button will be associated with a different GPIO pin which will be specified on top of the button.
To make it a bit easy to understand we will demonstrate how the green LED will turn ON/OFF through the user input. Notice that the LED is connected with GPIO4. The figure below shows the process of toggling the sliding button.
Initially, the GPIO4 will be OFF as indicated by the grey button. When we will toggle it then the web server will make an HTTP GET request on the URL. The HTTP request will be of the format: /update?output=32&state=1. This indicates that we want to turn the output which is connected to pin 32 to a state of 1 i.e., HIGH. Thus, the LED connected with that respective GPIO turns ON.
similarly, in the second scenario, initially, the GPIO4 is ON as indicated by the red button. When we will toggle it then the web server will make an HTTP GET request on the URL. The HTTP request will be of the format: /update?output=32&state=0. This indicates that we want to turn the output which is connected to pin 32 to a state of 0 i.e., LOW.
ESP8266 ESPAsyncWebServer Arduino Sketch
Open your Arduino IDE and create a new file. Copy the code given below in that file.
// Importing necessary libraries
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
// Setting network credentials
const char* ssid = "Enter_Your_WiFi_Name";
const char* password = "Enter_Your_WiFi_Password";
const char* input_parameter1 = "output";
const char* input_parameter2 = "state";
// Creating a AsyncWebServer object
AsyncWebServer server(80);
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<title>ESP8266 NodeMCU 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;}
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>ESP8266 NodeMCU 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 - GPIO5</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"5\" " + outputState(5) + "><span class=\"slider\"></span></label>";
buttons += "<h4>Output - GPIO4</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"4\" " + outputState(4) + "><span class=\"slider\"></span></label>";
buttons += "<h4>Output - GPIO0</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"0\" " + outputState(0) + "><span class=\"slider\"></span></label>";
buttons += "<h4>Output - GPIO2</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"2\" " + outputState(2) + "><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(5,OUTPUT);
digitalWrite(5, LOW);
pinMode(4, OUTPUT);
digitalWrite(4, LOW);
pinMode(0, OUTPUT);
digitalWrite(0, LOW);
pinMode(2, OUTPUT);
digitalWrite(2, 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(input_parameter1) && request->hasParam(input_parameter2)) {
inputMessage1 = request->getParam(input_parameter1)->value();
inputMessage2 = request->getParam(input_parameter2)->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() {
}
How the Code Works?
Importing Libraries
Firstly, we will import the necessary libraries. For this project, we are using three of them. ESP8266WiFi.h, ESPAsyncWebServer.h and ESPAsyncTCP.h. As we have to connect our ESP8266 NodeMCU to a wireless network hence we need ESP8266WiFi.h library for that purpose. The other two libraries are the ones that we recently downloaded. We will use them to build our asynchronous HTTP web server.
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
Setting Network Credentials:
Next, we will create two global variables, one for the SSID and the other for the password. These will hold our network credentials which will be used to connect to our wireless network. Replace both of them with your credentials to ensure a successful connection.
// Setting network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
Setting Input Parameters
We will pass two global variables of type char. These will be the input parameters which we will pass on the URL. One parameter is the GPIO number which we will call ‘output’ and the other is its state (1 or 0). Notice that both of these inputs will be in the form of a numerical number.
const char* input_paramter1 = "output";
const char* input_parameter2 = "state";
Creating the AsyncWebServer Object
The AsyncWebServer object will be used to set up the ESP8266 NodeMCU web server. We will pass the default HTTP port which is 80, as the input to the constructor. This will be the port where the server will listen to the requests.
AsyncWebServer server(80);
Creating the Web Page
We will create the index_html variable to store the HTML text. We will start with the title of the web page. The tag will indicate the beginning of the title and </tile> tag will indicate the ending. In between these tags, we will specify “ESP8266 NodeMCU WEB SERVER” which will be displayed in the browser’s title bar.
<title>ESP8266 NodeMCU WEB SERVER</title>
Next, we will create a meta tag to make sure our web server is available for all browsers e.g., smartphones, laptops, computers etc.
<meta name="viewport" content="width=device-width, initial-scale=1">
CSS Styling Web Page
CSS is used to give styles to a web page. To add CSS files in head tags, we will use tags to mark the beginning and the end. We will set the display text to font type Arial and align it in the centre of the webpage. The font size of the title and the first paragraph as indicated by h2 and p will also be set. Next, as we are using sliding switches in our project of two different colours, we will incorporate their font size, colour and positioning as well.
<style>
html {font-family: Arial; display: inline-block; text-align: center;}
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 Web Page Body
The next step will be to define the HTML web page body. This will go inside the tags which mark the beginning and the ending of the script. This part will include the heading of the web page and the buttons. We will include the heading of our webpage inside the tags and it will be the same as that of the web browser title i.e., ESP8266 NodeMCU WEB SERVER.
<h2>ESP8266 NodeMCU WEB SERVER</h2>
Creating Sliding Buttons
Then, we will define the buttons on our web page. We have buttons of two colours, red when the GPIO state is 1 and grey when the GPIO state is 0. So we will use a placeholder to monitor the correct GPIO states. %BUTTONPLACEHOLDER% will be used as the placeholder which will help us in creating the buttons.
We will use JavaScript to create a function which checks for the correct toggle feature of our sliding buttons through an if else statement. It will create the HTTP GET request whenever a button will be slid over inside the if statement. The element.id will correspond to the GPIO pin number associated with the button.
<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>
Processor () function
Inside the processor() function, we will replace the placeholder (%BUTTONPLACEHOLDER%) and build the buttons. This will occur whenever the web page will be accessed and if the placeholder is found inside the HTML script. As you can see, we will build four buttons but you can easily increase/decrease the number of buttons by adding/deleting the buttons section. The buttons variable will be of the type string and we will pass an empty string at first. Then, we will concatenate the HTML text for the buttons according to the current output state (1 or 0) of the button and build it accordingly (red or grey).
/ 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 - GPIO5</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"5\" " + outputState(5) + "><span class=\"slider\"></span></label>";
buttons += "<h4>Output - GPIO4</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"4\" " + outputState(4) + "><span class=\"slider\"></span></label>";
buttons += "<h4>Output - GPIO0</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"0\" " + outputState(0) + "><span class=\"slider\"></span></label>";
buttons += "<h4>Output - GPIO2</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"2\" " + outputState(2) + "><span class=\"slider\"></span></label>";
return buttons;
}
return String();
}
outputState() function
We will also define another function of type string named ‘outputState.’ This will take in the output of the buttons as a parameter and through an if-else statement will check if the GPIO is ON or not. This will be accomplished by the digitalRead() function. If the GPIO is indeed ON then it will return the string ‘checked’ otherwise an empty string “ ” will be returned. The toggle switch will act as an input type which we will specify as ‘checkbox.’ Whenever the value of the checkbox will be changed, an ‘onchange’ will occur. This in result will call a toggleCheckbox() function which will access the unique id associated with each GPIO pin.
String outputState(int output){
if(digitalRead(output)){
return "checked";
}
else {
return "";
}
}
Setup() function
Inside the setup() function, we will open a serial connection at a baud rate of 115200. By using the pinMode() function, the GPIO PINs will be passed as a parameter inside the function which will be configured as output pins. We will initialize all the pins to a LOW state at the time of boot that means all the LEDs will be OFF.
Serial.begin(115200);
pinMode(5,OUTPUT);
digitalWrite(5, LOW);
pinMode(4, OUTPUT);
digitalWrite(4, LOW);
pinMode(0, OUTPUT);
digitalWrite(0, LOW);
pinMode(2, OUTPUT);
digitalWrite(2, LOW);
Configuring WIFI
The following section of code will connect our ESP8266 NodeMCU board with the local network whose network credentials we already specified above. After the connection will be established, the IP address of the ESP8266 NodeMCU board will get printed on the serial monitor. This will help us to make a request to the server.
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi");
}
Serial.println(WiFi.localIP());
Handling Requests (ESP8266 NodeMCU side)
The next step will be to handle the requests received by the ESP8266 NodeMCU module. We will check for two parameters in the request which we already specified in the code: the output and the state. These are saved in the variables: ‘input_parameter1’ and ‘input_parameter2.’ These values will get saved in new variables named inputMessage1 which will contain the GPIO pin and inputMessage2 which will contain the state of the GPIO pin.
if (request->hasParam(input_parameter1) && request->hasParam(input_parameter2)) {
inputMessage1 = request->getParam(input_parameter1)->value();
inputMessage2 = request->getParam(input_parameter2)->value();
digitalWrite(inputMessage1.toInt(), inputMessage2.toInt());
These will then get printed on the serial monitor. Also, the send() method will be used to return the HTTP response. It takes in three parameters. The first parameter is the response code which we will specify as 200. It is the HTTP response code for ok. The second parameter is the content type of the response which we will specify as “text/plain” and the third parameter is the actual message which we will be sent as the HTTP response. This is set as “OK”. The arrow operator will be used to call the send method on the AsyncWebServerRequest object.
Serial.print("GPIO: ");
Serial.print(inputMessage1);
Serial.print(" - Set to: ");
Serial.println(inputMessage2);
request->send(200, "text/plain", "OK");
Initiating Connection
To start the server, we will call the begin() on our server object.
server.begin();
loop() function
Our loop function is empty as we are building an asynchronous server and it uses an event-driven approach. Therefore, we do not need to call any handling function inside it.
void loop() {
}
Demonstration
After you have uploaded your code to the ESP8266 NodeMCU development board press its reset button.
In your Arduino IDE, open up the serial monitor and you will be able to see the IP address of your ESP8266 NodeMCU module.
Copy that address into a web browser and press enter. The web server will look something like this:
Now you can slide the buttons and toggle the outputs connected to the various LEDs. You will be able to control all the four LEDs through these buttons. Likewise, the serial monitor will also print the current states of the GPIO pins which you would have changed. The web page will contain all the updated GPIO states according to the colour of the buttons shown.
If you want to make this web server password protected, you can read this post on HTTP authentication:
Conclusion
In this guide, we learned how to create an asynchronous web server through which we could control multiple outputs from the ESP8266 NodeMCU development board.
You may also like to read these interesting ESP8266 NodeMCU projects:
- Create Simple ESP8266 NodeMCU Web server in Arduino IDE
- ESP8266 NodeMCU Web Server using LittleFS (Flash File System)
- LittleFS Introduction & Install ESP8266 NodeMCU Filesystem Uploader in Arduino IDE
- Accessing ESP32 web server from anywhere in the world ( ESP8266 compatible)
- Getting Epoch/Unix time with ESP8266 NodeMCU through NTP server using Arduino IDE
- MPU-6050 with ESP8266 NodeMCU
- Control NodeMCU with HTTP shortcuts
got an error:
esptool.FatalError: Invalid head of packet (0x46)
Can you help me?
Archiving built core (caching) in: C:\Users\MICHAL~1\AppData\Local\Temp\arduino_cache_15483\core\core_esp8266_esp8266_d1_mini_CpuFrequency_80,UploadSpeed_921600,FlashSize_4M3M_f8f1277f95077b355ca86927bc3bd451.a
libraries\ESPAsyncTCP-master\ESPAsyncTCP.cpp.o: In function `__gnu_cxx::new_allocator<std::_Sp_counted_deleter<ACErrorTracker*, std::__shared_ptr::_Deleter<std::allocator >, std::allocator, (__gnu_cxx::_Lock_policy)0> >::allocate(unsigned int, void const*)’:
C:\Moje data\Přenosné arduino záloha 1.2.2021\Přenosné Arduino\portable\sketchbook\libraries\ESPAsyncTCP-master\src/ESPAsyncTCP.cpp:1194: undefined reference to `std::nothrow’
libraries\ESPAsyncTCP-master\ESPAsyncTCP.cpp.o: In function `_Sp_counted_base’:
C:\Moje data\Přenosné arduino záloha 1.2.2021\Přenosné Arduino\portable\sketchbook\libraries\ESPAsyncTCP-master\src/ESPAsyncTCP.cpp:1194: undefined reference to `operator new(unsigned int, std::nothrow_t const&)’
libraries\ESPAsyncTCP-master\ESPAsyncTCP.cpp.o: In function `AsyncServer::_accept(tcp_pcb*, signed char)’:
C:\Moje data\Přenosné arduino záloha 1.2.2021\Přenosné Arduino\portable\sketchbook\libraries\ESPAsyncTCP-master\src/ESPAsyncTCP.cpp:1194: undefined reference to `operator new(unsigned int, std::nothrow_t const&)’
collect2.exe: error: ld returned 1 exit status