ESP32 Asynchronous web server

It may often be necessary to communicate with our application running on the ESP32 microcontroller via a graphical interface. Whether changing configuration or displaying sensor values, etc. The obvious solution is to use the web browser, because we almost always have our smartphone at hand. The asynchronous web server helps with this. In this article, I would like to demonstrate the application of the ESPAsyncWebServer library on an ESP32 module with examples.

Content:

  Introduction to using the ESPAsyncWebServer library

  Sending form data using the POST method

  Website with password protected settings page

SparkFun ESP32 WROOM with ESP32-D0WDQ6 chip

SparkFun ESP32 WROOM with ESP32-D0WDQ6 chip

Xtensa dual-core 32-bit LX6 microprocessor Up to 240MHz clock frequency, Integrated 802.11 BGN WiFi, 21 GPIO 8-electrode capacitive touch support

advertising

Introduction to using the ESPAsyncWebServer library

To use the asynchronous web server, the ESPAsyncWebServer library must be installed. The AsyncTCP library must be installed as a dependency of ESPAsyncWebServer The libraries are available by clicking on the links below.

Download ESPAsyncWebServer library

Download AsyncTCP library

Unpack the downloaded files, delete the “-master” syllable appended to the end of the file, and then copy it to the “…/Arduino/libraries/” folder.

Then start/restart the Arduino IDE.

At the beginning of our code, we import the WiFi.h library, as well as the previously downloaded two libraries above.

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

After that, we declare two variables for the credentials of the WiFi network.

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

Let’s create an instance of the AsyncWebServer object, specify the port where the server will listen. Port 80 is the default HTTP port.

AsyncWebServer server(80);

We start the serial connection in the setup() function.

Serial.begin(115200);

We connect to the Wifi network and print the ESP32 IP address on the serial monitor.

WiFi.begin(ssid, password);

Serial.print("Connect to the WiFi network.");
while (WiFi.status() != WL_CONNECTED
{
  delay(500);
  Serial.print(".");
}
Serial.println();
Serial.println("Connected!");
Serial.print("Ip address: ");
Serial.println(WiFi.localIP());

An asynchronous web server can handle multiple connections at the same time. Each connected client is associated with an AsyncWebServerRequest object. ESPAsyncWebServer runs as a background process.

By calling the server.on() method, in the first parameter we set the route where the server listens for incoming HTTP requests, the second argument contains the type of HTTP requests, in our case we use the GET method, and finally in the third parameter we specify a function that will then to be executed when a request arrives on the specified route.

We can provide the HTTP response using the send method of AsyncWebServerRequest. In the following example we send a simple string, here send has three parameters. The first parameter is the 200 HTTP response code, the second parameter is the sending method, in our case “text/plain”, the third argument is the character string to be sent, “Hello AsyncWebServer”.

After that, start the server by calling the server.begin() function.

server.on("/hello_server", HTTP_GET, [](AsyncWebServerRequest *request)
{
  request->send(200, "text/plain", "Hello AsyncWebServer!");
});
 
server.begin();

Nothing is added to the loop() function now, since there is no need to call a client handler function to use AsyncWebServer.

Let’s see the full code:

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

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

AsyncWebServer server(80);

void setup()
{
  Serial.begin(115200);
 
  WiFi.begin(ssid, password);

  Serial.print("Connect to the WiFi network.");
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.println("Connected!");
  Serial.print("Ip address: ");
  Serial.println(WiFi.localIP());
 
  server.on("/hello_server", HTTP_GET, [](AsyncWebServerRequest *request)
  {
    request->send(200, "text/plain", "Hello AsyncWebServer!");
  });
 
  server.begin();
}
void loop(){}

Upload the above code to the ESP32 using ArduinoIDE. If you have this, open your favorite web browser and add the IP address copied from the ArduinoIDE serial monitor to the path specified in the server.on function, and enter it in the address bar of the browser. For example like this.

http://192.168.1.20/hello_server

The result can be seen in the browser window.

Kasa Smart Plug HS103P2, Smart Home Wi-Fi Outlet Works with Alexa, Echo, Google Home & IFTTT, No Hub Required, Remote Control

Kasa Smart Plug HS103P2

Smart Home Wi-Fi Outlet Works with Alexa, Echo, Google Home & IFTTT, No Hub Required, Remote Control

advertising

Sending form data using the POST method

In the following example, we transfer data from a text input field using the POST method, and then display the entered text.

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

AsyncWebServer server(80);

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

const char* PARAM_MESSAGE = "message";
String postMessage = "";

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML>
<html>
  <head>
    <title>ESP32 Asynchronous webserver</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script>
      function submitData() 
      {
        setTimeout(function(){
          document.location.reload(false); 
        }, 1000);   
      }
    </script>  
  </head>
  <body>
    <center>
      <h2>ESP32 Asynchronous webserver</h2>

      <div style="height:50px"></div>

      <p>HTTP POST message: %POST_MESSAGE%</p>
      
      <form action="/post" method="POST" target="self_page">
        <input type="text" name="message">
        <br><br>
        <input type="submit" value="Send" onclick="submitData()">
      </form>
    </center>
    <iframe style="display:none" name="self_page"></iframe>
  </body>
</html> )rawliteral";

void notFound(AsyncWebServerRequest *request) 
{
  request->send(404, "text/plain", "The page not found");
}

String processor(const String& var)
{
  if(var == "POST_MESSAGE")
  {
    return postMessage;
  }
  return String();
}

void setup()
{
  Serial.begin(115200);
 
  WiFi.begin(ssid, password);

  Serial.print("Connect to the WiFi network.");
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.println("Connected!");
  Serial.print("Ip address: ");
  Serial.println(WiFi.localIP());

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

  server.on("/post", HTTP_POST, [](AsyncWebServerRequest *request)
  {
    if(request->hasParam(PARAM_MESSAGE, true))
    {
      postMessage = request->getParam(PARAM_MESSAGE, true)->value();
    }
    request->send(200);
  });

  server.onNotFound(notFound);

  server.begin();
}

void loop() {}

The html code is placed in the “index_html[]” constant character array. The “PROGMEM” keyword is a variable modifier, it tells the compiler to save the contents of the variable to flash memory.

const char index_html[] PROGMEM = R"rawliteral(....)rawliteral";

“R” means treat everything between the delimiters as a raw string. So everything is as described between “rawliteral( and …. )rawliteral”. In the example, we use the “rawliteral” delimiter, but you can freely choose anything instead.

If something is typed in the address bar of the browser, the “notFound()” function handles the error. It sends the 404 HTML response code and displays the specified content, in this case it is “text/plain” text “The page not found”.

void notFound(AsyncWebServerRequest *request) 
{
  request->send(404, "text/plain", "The page not found");
}

We placed a placeholder in the html code. Placeholders are delimited by % symbols, in our example it is (%POST_MESSAGE%). When the website is loaded, the “processor()” function runs and wherever it finds the placeholder in the code, it replaces it with the appropriate String.

In this example, we modify the content of a paragraph, passing the value of the global variable “postMessage” to the %POST_MESSAGE% placeholder.

String postMessage = "";

<html>...
  <p>HTTP POST message: %POST_MESSAGE%</p>
...</html>

String processor(const String& var)
{
  if(var == "POST_MESSAGE")
  {
    return postMessage;
  }
  return String();
}

In our code, the first server.on() function serves to display the home page. The first parameter, the path, only contains the “/” character, so the ESP32 monitors requests to the IP address.

The send_P() function sends the 200 HTTP response code as a response, defines the content type, in this case “text/html”. The location of the html code to be displayed must be specified in the third parameter, in our example the string stored in the index_html array. Since we placed a placeholder in the html code, the processor function that handles it must be defined, this is the processor() function given in the fourth parameter.

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

In the example code, the second server.on() function listens to the “/post” path and accepts HTTP_POST requests. Within the function, we check whether the given parameter contains data. If yes, we extract the content of the parameter for further processing.

In this case, this “PARAM_MESSAGE” carries the content of the text input field marked with the name “message”, the value of which is transferred to the global variable “postMessage”. Finally, it sends a 200 HTTP response code.

const char* PARAM_MESSAGE = "message";
String postMessage = "";

</html>...
  <input type="text" name="message">
...</html>

server.on("/post", HTTP_POST, [](AsyncWebServerRequest *request)
{
  if(request->hasParam(PARAM_MESSAGE, true))
  {
    postMessage = request->getParam(PARAM_MESSAGE, true)->value();
  }
  request->send(200);
});

The server.onNotFound(notFound) function monitors requests that are directed to non-existent pages, for which we add the “notFound()” function as a parameter, which handles these errors.

server.onNotFound(notFound);

Finally, we start the AsyncWebServer with “server.begin();” by calling a function.

server.begin();

Let’s upload the code to the ESP32 and see the output in the web browser.

ESP32 asynchronous webserver - sending form data with POST method
ESP32 asynchronous webserver – sending form data with POST method

Website with password protected settings page

In the third example, we create a website consisting of a main page and a settings page.

At the first boot, the ESP32 tries to connect to the wifi network, but it does not have the necessary credentials to connect to the wifi. It will automatically switch to AP mode after 15 seconds of unsuccessful attempts. In this case, you need to connect to the ESP32 wifi access point.

Default identifiers required for connection:
  SSID: Esp_AP
  Password: 12345678

Important! The Password must be at least 8 characters long, otherwise it will not be taken into account by the server.

If you have successfully connected to the ESP32 access point, open the IP address ‘ 192.168.7.22 ‘ in the web browser.

On the main page, under the title line, a text field displays the state of the LED, which can be turned on or off with the push button located below it. The led is connected to the GPIO2 pin of the ESP32.

Further down we find another button, by clicking on it we can go to the settings page.

ESP32 asynchronous webserver homepage
ESP32 asynchronous webserver homepage

The settings page is password protected, the credentials required for entry are:
  default username: admin
  default password: admin .

ESP32 asynchronous webserver login
ESP32 asynchronous webserver login

There are two forms on the settings page. In the first form, you can set/modify the login IDs required for the Wi-Fi connection and the access point (AP). By clicking the save button, the data is stored in a non-volatile memory area, SPIFFS (SPI Flash File Storage).

The ESP32 will then reboot.

ESP32 asynchronous webserver network settings
ESP32 asynchronous webserver network settings

In the second form, you can change the login credentials of the settings page. These identifiers are also stored in a non-volatile memory. ESP32 will not restart after saving.

Change of ESP32 asynchronous webserver login IDs
Change of ESP32 asynchronous webserver login IDs

Below the two forms we find two more buttons, I don’t think they need an explanation.

ESP32 asynchronous webserver buttons
ESP32 asynchronous webserver buttons

This example code can be a good starting point for projects where it is necessary to change the settings.

#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
#include <Hash.h>
#include "FS.h"
#include "SPIFFS.h"
#define FORMAT_SPIFFS_IF_FAILED true
const char ledPin = 2;
String httpUsername = "admin";
String httpPassword = "admin";
String _AP_ssid = "Esp_AP";
String _AP_password = "12345678";
IPAddress local_IP(192,168,7,22);
IPAddress gateway(192,168,7,1);
IPAddress subnet(255,255,255,0);
AsyncWebServer server(80);
bool wifiIsConnected = false;
String ledValue ="false";
String ledState ="The LED is off!";
const char* param_ledvalue = "ledvalue";
const char* param_ap_ssid = "inputApSsid";
const char* param_ap_password = "inputApPassword";
const char* param_wifi_ssid = "inputWifiSsid";
const char* param_wifi_password = "inputWifiPassword";
const char* param_httpUsername = "inputHttpUsername";
const char* param_httpPassword = "inputHttpPassword";
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML>
<html>
<head>
<title>Home page</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script>
function submitData() 
{
setTimeout(function(){
document.location.reload(false); 
}, 1000);   
}
</script>
</head>
<body>
<center>
<h2>ESP32 Asynchronous web server</h2>
<div style="height:50px"></div>
<p>%LED_STATE%</p>
<form action="/ledSwitch" target="self_page">
<input type="hidden" name="ledvalue" value="%LED_VALUE%">
<input type="submit" value="LED" onclick="submitData()">
</form>
<br><br>
<button onclick="window.location.href='/settings';">Settings</button>
<iframe style="display:none" name="self_page"></iframe>
</center>
</body>
</html> )rawliteral";
const char settings_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML>
<html>
<head>
<title>Settings</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script>
function submitData() 
{
setTimeout(function(){
document.location.reload(false); 
}, 1000);   
}
function logoutButton() 
{
var xmlHttpRequest = new XMLHttpRequest();
xmlHttpRequest.open("GET", "/logout", true);
xmlHttpRequest.send();
setTimeout(function() {
window.open("/","/"); 
}, 1000);
}
</script>
</head>
<body>
<center>
<h2>Settings</h2>
<div style="height:50px"></div>
<fieldset style="width:300px">
<legend>Network settings:</legend>
<br><br>
<form action="/network" target="self_page">
AP SSID:<br>
<input type="text" value='%inputApSsid%' name="inputApSsid" size="20">
<br><br>
AP Password:<br>
<input type="text" value='%inputApPassword%' name="inputApPassword" size="20">
<br>( at least 8 characters! )
<br><br>
Wifi SSID:<br>
<input type="text" value='%inputWifiSsid%' name="inputWifiSsid" size="20">
<br><br>
Wifi Password:<br>
<input type="text" value='%inputWifiPassword%' name="inputWifiPassword" size="20">
<br><br>
<input type="submit" value="Save" onclick="submitData()">
</form>
<br><br>
</fieldset>
<div style="height:20px"></div>
<fieldset style="width:300px">
<legend>Login-Daten:</legend>
<br><br>
<form action="/http_login_set" target="self_page">
User name:<br>
<input type="text" value='%inputHttpUsername%'  name="inputHttpUsername" size="20">
<br><br>
Password:<br>
<input type="text" value='%inputHttpPassword%'  name="inputHttpPassword" size="20">
<br><br><br>
<input type="submit" value="Save" onclick="submitData()">
</form>
<br><br>
</fieldset>
<div style="height:20px"></div>
<fieldset style="width:300px">
<legend>To home page</legend>
<br><br>
<button onclick="window.location.href='/';">Home page</button>
<br><br>
</fieldset>
<div style="height:20px"></div>
<fieldset style="width:300px">
<legend>Logout</legend>
<br><br>
<button onclick="logoutButton()">Logout</button>
<br><br>
</fieldset>
<div style="height:50px;"></div>
<iframe style="display:none" name="self_page"></iframe>
</center>
</body>
</html> )rawliteral";
String inputValueAdapter(const String& inputValueSet)
{
if(inputValueSet == "LED_STATE")
{
return ledState;
}
if(inputValueSet == "LED_VALUE")
{
return ledValue;
}
if(inputValueSet == "inputApSsid")
{
return readFile(SPIFFS, "/apSsid.txt");
}
if(inputValueSet == "inputApPassword")
{
return readFile(SPIFFS, "/apPassword.txt");
}
if(inputValueSet == "inputWifiSsid")
{
return readFile(SPIFFS, "/wifiSsid.txt");
}
if(inputValueSet == "inputWifiPassword")
{
return readFile(SPIFFS, "/wifiPassword.txt");
}
if(inputValueSet == "inputHttpUsername")
{
return readFile(SPIFFS, "/httpUsername.txt");
}
if(inputValueSet == "inputHttpPassword")
{
return readFile(SPIFFS, "/httpPassword.txt");
}
return String();
}
void setupAsyncServer()
{
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
{
request->send_P(200, "text/html", index_html, inputValueAdapter);
});
server.on("/ledSwitch", HTTP_GET, [](AsyncWebServerRequest *request)
{
ledValue = request->getParam(param_ledvalue)->value();
if(ledValue == "true")
{
ledValue = "false";
digitalWrite(ledPin, LOW);
ledState =" The LED is off!";
}
else if(ledValue == "false")
{
ledValue = "true";
digitalWrite(ledPin, HIGH);
ledState ="The LED is on!";
}
request->send(200, "text/text", ledValue);
});
server.on("/settings", HTTP_GET, [](AsyncWebServerRequest *request)
{
String temp_httpUsername = readFile(SPIFFS, "/httpUsername.txt");
if(temp_httpUsername == "")
{
temp_httpUsername = httpUsername;
}
const char* http_username = temp_httpUsername.c_str();
String temp_httpPassword = readFile(SPIFFS, "/httpPassword.txt");
if(temp_httpPassword == "")
{
temp_httpPassword = httpPassword;
}
const char* http_password = temp_httpPassword.c_str();
if(!request->authenticate(http_username, http_password))
{
return request->requestAuthentication();
}
request->send_P(200, "text/html", settings_html, inputValueAdapter);
});
server.on("/network", HTTP_GET, [](AsyncWebServerRequest *request)
{
String temp_httpUsername = readFile(SPIFFS, "/httpUsername.txt");
if(temp_httpUsername == "")
{
temp_httpUsername = httpUsername;
}
const char* http_username = temp_httpUsername.c_str();
String temp_httpPassword = readFile(SPIFFS, "/httpPassword.txt");
if(temp_httpPassword == "")
{
temp_httpPassword = httpPassword;
}
const char* http_password = temp_httpPassword.c_str();
if(!request->authenticate(http_username, http_password))
{
return request->requestAuthentication();
}
String inputMessage;
if (request->hasParam(param_ap_ssid)) 
{
inputMessage = request->getParam(param_ap_ssid)->value();
writeFile(SPIFFS, "/apSsid.txt", inputMessage.c_str());
}
if (request->hasParam(param_ap_password))
{
inputMessage = request->getParam(param_ap_password)->value();
writeFile(SPIFFS, "/apPassword.txt", inputMessage.c_str());
}
if (request->hasParam(param_wifi_ssid)) 
{
inputMessage = request->getParam(param_wifi_ssid)->value();
writeFile(SPIFFS, "/wifiSsid.txt", inputMessage.c_str());
}     
if (request->hasParam(param_wifi_password)) 
{
inputMessage = request->getParam(param_wifi_password)->value();
writeFile(SPIFFS, "/wifiPassword.txt", inputMessage.c_str());
}
request->send(200, "text/text", inputMessage);
Serial.println("ESP Restart...");
ESP.restart();
});
server.on("/http_login_set", HTTP_GET, [](AsyncWebServerRequest *request)
{
String temp_httpUsername = readFile(SPIFFS, "/httpUsername.txt");
if(temp_httpUsername == "")
{
temp_httpUsername = httpUsername;
}
const char* http_username = temp_httpUsername.c_str();
String temp_httpPassword = readFile(SPIFFS, "/httpPassword.txt");
if(temp_httpPassword == "")
{
temp_httpPassword = httpPassword;
}
const char* http_password = temp_httpPassword.c_str();
if(!request->authenticate(http_username, http_password))
{
return request->requestAuthentication();
}
String inputMessage;                                
if (request->hasParam(param_httpUsername))
{
inputMessage = request->getParam(param_httpUsername)->value();
writeFile(SPIFFS, "/httpUsername.txt", inputMessage.c_str());
}
if (request->hasParam(param_httpPassword))
{
inputMessage = request->getParam(param_httpPassword)->value();
writeFile(SPIFFS, "/httpPassword.txt", inputMessage.c_str());
}
request->send(200, "text/text", inputMessage);
});
server.on("/logout", HTTP_GET, [](AsyncWebServerRequest *request)
{
request->send(401);
});
server.onNotFound(notFound);
server.begin();
}
void notFound(AsyncWebServerRequest *request) 
{
request->send(404, "text/plain", "The page not found");
}
String readFile(fs::FS &fs, const char * path)
{
String fileContent = "";
File file = fs.open(path, "r");
if(!file || file.isDirectory())
{
return fileContent;
}
while(file.available())
{
fileContent+=String((char)file.read());
}
file.close();
return fileContent;
}
void writeFile(fs::FS &fs, const char * path, const char * message)
{
File file = fs.open(path, "w");
if(!file)
{
return;
}
file.print(message);
file.close();
}
void setup_ap()
{
String temp_pass = readFile(SPIFFS, "/apPassword.txt");
if(temp_pass == "")
{
temp_pass = _AP_password;
}
String temp_ssid = readFile(SPIFFS, "/apSsid.txt");
if(temp_ssid == "")
{
temp_ssid = _AP_ssid;
}
const char* AP_ssid = temp_ssid.c_str();
const char* AP_password = temp_pass.c_str();
WiFi.softAPConfig(local_IP, gateway, subnet);
WiFi.softAP(AP_ssid, AP_password);
IPAddress Ap_Ip = WiFi.softAPIP();
Serial.print("Access Point IP address: ");
Serial.println(Ap_Ip);
Serial.println();
}
void setup_wifi() 
{
String temp_pass = readFile(SPIFFS, "/wifiPassword.txt");
char passbuff[temp_pass.length() + 1];
temp_pass.toCharArray(passbuff, temp_pass.length() + 1);
const char* password = passbuff;
String temp_ssid = readFile(SPIFFS, "/wifiSsid.txt");
char ssidbuff[temp_ssid.length() + 1];
temp_ssid.toCharArray(ssidbuff, temp_ssid.length() + 1);
const char* ssid = ssidbuff;
WiFi.begin(ssid, password);
Serial.print("Connection to WiFi network.");
int i = 0;
while (true) 
{
if(WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
i++;
if(i > 30)
{
wifiIsConnected = false;
Serial.println();
Serial.println("It cannot connect to the WiFi network.");
break;
}
}
else
{
wifiIsConnected = true;
Serial.println();
Serial.println("Connected!");
Serial.print("Ip address: ");
Serial.println(WiFi.localIP());
Serial.println();
break;
}
}
}
void setup()
{
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, HIGH);
delay(2000);
digitalWrite(ledPin, LOW);
Serial.begin(115200);
SPIFFS.begin();
WiFi.mode(WIFI_STA);
setup_wifi();
if(!wifiIsConnected)
{
WiFi.mode(WIFI_AP);
setup_ap();    
}
setupAsyncServer();
}
void loop(){}

Meross Dimmable Smart Table Lamp

WiFi Table Lamp, Support HomeKit (iOS13+), Alexa, Google Assistant and SmartThings, Tunable White and Multi-Color, Touch Control, Voice and APP Control, Schedule and Timer

advertising – amazon.com

Meross Dimmable Smart Table Lamp