ESP32: Create a Wi-Fi Manager (AsyncWebServer library)

In this guide, you’ll create and set up a Wi-Fi Manager with the ESPAsyncWebServer library that you can modify to use with your web server projects or with any project that needs a connection to a Wi-Fi network. The Wi-Fi Manager allows you to connect the ESP32 board to different Access Points (networks) without hard-coding network credentials (SSID and password) and upload new code to your board. Your ESP will automatically join the last saved network or set up an Access Point that you can use to configure the network credentials.

ESP32: Create a Simple Wi-Fi Manager (AsyncWebServer library)

To better understand how this project works, we recommend taking a look at the following tutorials:

We also use this Wi-Fi Manager approach on the following project:

How it Works

Take a look at the following diagram to understand how the Wi-Fi Manager we’ll create works.

ESP32 ESP8266 Wi-Fi Manager Web Server How it Works
  • When the ESP first starts, it tries to read the ssid.txt, pass.txt and ip.txt files* (1);
  • If the files are empty (2) (the first time you run the board, the files are empty), your board is set as an access point (3);
  • Using any Wi-Fi enabled device with a browser, you can connect to the newly created Access Point (default name ESP-WIFI-MANAGER);
  • After establishing a connection with the ESP-WIFI-MANAGER, you can go to the default IP address 192.168.4.1 to open a web page that allows you to configure your SSID and password (4);
  • The SSID, password, and IP address inserted in the form are saved in the corresponding files: ssid.txt, pass.txt, and ip.txt (5);
  • After that, the ESP board restarts (6);
  • This time, after restarting, the files are not empty, so the ESP will try to connect to the network in station mode using the settings you’ve inserted in the form (7);
  • If it establishes a connection, the process is completed successfully, and you can access the main web server page that can do whatever you want (control sensor readings, control outputs, display some text, etc.) (9). Otherwise, it will set the Access Point (3), and you can access the default IP address (192.168.4.1) to add another SSID/password combination.

* we also created a gateway field and a gateway.txt file to save the IP address gateway (this is not shown in the diagram).

To show you how to set the Wi-Fi Manager, we’ll set up a web server that controls one output (GPIO2—the built-in LED). You can apply the Wi-Fi Manager to any web server project built with the ESPAsyncWebServer library or to any project that requires the ESP to be connected to a wi-fi network.

Prerequisites

We’ll program the ESP32 board using Arduino IDE. So make sure you have the ESP32 board add-on installed.

If you want to program the ESP32 using VS Code + PlatformIO, follow the next tutorial:

Installing Libraries (Arduino IDE)

You need to install the following libraries in your Arduino IDE to build the web server for this project.

The ESPAsyncWebServer, AsynTCP, and ESPAsyncTCP 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.

Installing Libraries (VS Code + PlatformIO)

If you’re programming the ESP32 using PlatformIO, copy the following to the platformio.ini to include the ESPAsyncWebServer library (it will automatically include any dependencies like the AsynTCP or ESPAsyncTCP libraries), set the default filesystem to LittleFS and change the baud rate to 115200:

monitor_speed = 115200
lib_deps = ESP Async WebServer
board_build.filesystem = littlefs

Filesystem Uploader

Before proceeding, you need to have the ESP32 Uploader Plugin installed in your Arduino IDE.

If you’re using VS Code with PlatformIO, follow the next tutorials to learn how to upload files to the filesystem:

Organizing your Files

To keep the project organized and make it easier to understand, we’ll create four different files to build the web server:

  • Arduino sketch that handles the web server;
  • index.html: to define the content of the web page in station mode to control the output (or any other web page you want to build);
  • style.css: to style the web pages;
  • wifimanager.html: to define the web page’s content to display the Wi-Fi Manager when the ESP is in access point mode.
ESP32 ESP8266 Wi-Fi Manager Project Files

You should save the HTML and CSS files inside a folder called data inside the Arduino sketch folder, as shown in the previous diagram. We’ll upload these files to the ESP32 filesystem (LittleFS).

You can download all project files:

Creating the HTML Files

For this project, you need two HTML files. One to build the main page that controls the output (index.html) and another to build the Wi-Fi Manager page (wifimanager.html).

index.html

Here’s the text you should copy to your index.html file.

<!DOCTYPE html>
<html>
  <head>
    <title>ESP WEB SERVER</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" type="text/css" href="style.css">
    <link rel="icon" type="image/png" href="favicon.png">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
  </head>
  <body>
    <div class="topnav">
      <h1>ESP WEB SERVER</h1>
    </div>
    <div class="content">
      <div class="card-grid">
        <div class="card">
          <p class="card-title"><i class="fas fa-lightbulb"></i> GPIO 2</p>
          <p>
            <a href="on"><button class="button-on">ON</button></a>
            <a href="off"><button class="button-off">OFF</button></a>
          </p>
          <p class="state">State: %STATE%</p>
        </div>
      </div>
    </div>
  </body>
</html>

View raw code

We won’t explain how this HTML file works because that’s not the purpose of this tutorial. The purpose of this tutorial is to explain the parts related to the Wi-Fi Manager.

wifimanager.html

The Wi-Fi Manager web page looks like this:

ESP32 ESP8266 Wi-Fi Manager Fields

Copy the following to the wifimanager.html file. This creates a web page with a form with three input fields and a Submit button.

<!DOCTYPE html>
<html>
<head>
  <title>ESP Wi-Fi Manager</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,">
  <link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
  <div class="topnav">
    <h1>ESP Wi-Fi Manager</h1>
  </div>
  <div class="content">
    <div class="card-grid">
      <div class="card">
        <form action="/" method="POST">
          <p>
            <label for="ssid">SSID</label>
            <input type="text" id ="ssid" name="ssid"><br>
            <label for="pass">Password</label>
            <input type="text" id ="pass" name="pass"><br>
            <label for="ip">IP Address</label>
            <input type="text" id ="ip" name="ip" value="192.168.1.200"><br>
            <label for="gateway">Gateway Address</label>
            <input type="text" id ="gateway" name="gateway" value="192.168.1.1"><br>
            <input type ="submit" value ="Submit">
          </p>
        </form>
      </div>
    </div>
  </div>
</body>
</html>

View raw code

In this HTML file, we create an HTML form that will make an HTTP POST request with the data submitted to the server.

<form action="/" method="POST">

The form contains three input fields and corresponding labels: SSID, password, and IP address.

This is the input field for the SSID:

<label for="ssid">SSID</label>
<input type="text" id ="ssid" name="ssid"><br>

This is the input field for the password.

<label for="pass">Password</label>
<input type="text" id ="pass" name="pass"><br>

There is an input field for the IP address that you want to attribute to the ESP in station mode. As default, we set it to 192.168.1.200 (you can set another default IP address, or you can delete the value parameter—it won’t have a default value).

<input type="text" id ="ip" name="ip" value="192.168.1.200">

Finally, there’s an input field for the gateway address. If the default IP address is 192.168.1.200, the gateway can be 192.168.1.1 by default.

<input type="text" id ="gateway" name="gateway" value="192.168.1.1"><br>

CSS File

Copy the following styles to your style.css file. We won’t explain how these styles work. We have already explained how similar styles work in other ESP Web Server projects.

html {
  font-family: Arial, Helvetica, sans-serif; 
  display: inline-block; 
  text-align: center;
}

h1 {
  font-size: 1.8rem; 
  color: white;
}

p { 
  font-size: 1.4rem;
}

.topnav { 
  overflow: hidden; 
  background-color: #0A1128;
}

body {  
  margin: 0;
}

.content { 
  padding: 5%;
}

.card-grid { 
  max-width: 800px; 
  margin: 0 auto; 
  display: grid; 
  grid-gap: 2rem; 
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}

.card { 
  background-color: white; 
  box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
}

.card-title { 
  font-size: 1.2rem;
  font-weight: bold;
  color: #034078
}

input[type=submit] {
  border: none;
  color: #FEFCFB;
  background-color: #034078;
  padding: 15px 15px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 16px;
  width: 100px;
  margin-right: 10px;
  border-radius: 4px;
  transition-duration: 0.4s;
  }

input[type=submit]:hover {
  background-color: #1282A2;
}

input[type=text], input[type=number], select {
  width: 50%;
  padding: 12px 20px;
  margin: 18px;
  display: inline-block;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-sizing: border-box;
}

label {
  font-size: 1.2rem; 
}
.value{
  font-size: 1.2rem;
  color: #1282A2;  
}
.state {
  font-size: 1.2rem;
  color: #1282A2;
}
button {
  border: none;
  color: #FEFCFB;
  padding: 15px 32px;
  text-align: center;
  font-size: 16px;
  width: 100px;
  border-radius: 4px;
  transition-duration: 0.4s;
}
.button-on {
  background-color: #034078;
}
.button-on:hover {
  background-color: #1282A2;
}
.button-off {
  background-color: #858585;
}
.button-off:hover {
  background-color: #252524;
} 

View raw code

Setting Up the Web Server

If you’re using VS Code with the platformIO extension, you need to edit the platformio.ini file to look as shown below. If you’re using Arduino IDE, you can ignore this.

platformio.ini ESP32:

[env:esp32doit-devkit-v1]
platform = espressif32
board = esp32doit-devkit-v1
framework = arduino
monitor_speed=115200
lib_deps = ESP Async WebServer

Code

Copy the following code to your Arduino IDE or to the main.cpp file if you’re using VS Code.

/*********
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete instructions at https://RandomNerdTutorials.com/esp32-wi-fi-manager-asyncwebserver/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*********/
#include <Arduino.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
#include "LittleFS.h"

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

// Search for parameter in HTTP POST request
const char* PARAM_INPUT_1 = "ssid";
const char* PARAM_INPUT_2 = "pass";
const char* PARAM_INPUT_3 = "ip";
const char* PARAM_INPUT_4 = "gateway";

//Variables to save values from HTML form
String ssid;
String pass;
String ip;
String gateway;

// File paths to save input values permanently
const char* ssidPath = "/ssid.txt";
const char* passPath = "/pass.txt";
const char* ipPath = "/ip.txt";
const char* gatewayPath = "/gateway.txt";

IPAddress localIP;
//IPAddress localIP(192, 168, 1, 200); // hardcoded

// Set your Gateway IP address
IPAddress localGateway;
//IPAddress localGateway(192, 168, 1, 1); //hardcoded
IPAddress subnet(255, 255, 0, 0);

// Timer variables
unsigned long previousMillis = 0;
const long interval = 10000;  // interval to wait for Wi-Fi connection (milliseconds)

// Set LED GPIO
const int ledPin = 2;
// Stores LED state

String ledState;

// Initialize LittleFS
void initLittleFS() {
  if (!LittleFS.begin(true)) {
    Serial.println("An error has occurred while mounting LittleFS");
  }
  Serial.println("LittleFS mounted successfully");
}

// Read File from LittleFS
String readFile(fs::FS &fs, const char * path){
  Serial.printf("Reading file: %s\r\n", path);

  File file = fs.open(path);
  if(!file || file.isDirectory()){
    Serial.println("- failed to open file for reading");
    return String();
  }
  
  String fileContent;
  while(file.available()){
    fileContent = file.readStringUntil('\n');
    break;     
  }
  return fileContent;
}

// Write file to LittleFS
void writeFile(fs::FS &fs, const char * path, const char * message){
  Serial.printf("Writing file: %s\r\n", path);

  File file = fs.open(path, FILE_WRITE);
  if(!file){
    Serial.println("- failed to open file for writing");
    return;
  }
  if(file.print(message)){
    Serial.println("- file written");
  } else {
    Serial.println("- write failed");
  }
}

// Initialize WiFi
bool initWiFi() {
  if(ssid=="" || ip==""){
    Serial.println("Undefined SSID or IP address.");
    return false;
  }

  WiFi.mode(WIFI_STA);
  localIP.fromString(ip.c_str());
  localGateway.fromString(gateway.c_str());


  if (!WiFi.config(localIP, localGateway, subnet)){
    Serial.println("STA Failed to configure");
    return false;
  }
  WiFi.begin(ssid.c_str(), pass.c_str());
  Serial.println("Connecting to WiFi...");

  unsigned long currentMillis = millis();
  previousMillis = currentMillis;

  while(WiFi.status() != WL_CONNECTED) {
    currentMillis = millis();
    if (currentMillis - previousMillis >= interval) {
      Serial.println("Failed to connect.");
      return false;
    }
  }

  Serial.println(WiFi.localIP());
  return true;
}

// Replaces placeholder with LED state value
String processor(const String& var) {
  if(var == "STATE") {
    if(digitalRead(ledPin)) {
      ledState = "ON";
    }
    else {
      ledState = "OFF";
    }
    return ledState;
  }
  return String();
}

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

  initLittleFS();

  // Set GPIO 2 as an OUTPUT
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
  
  // Load values saved in LittleFS
  ssid = readFile(LittleFS, ssidPath);
  pass = readFile(LittleFS, passPath);
  ip = readFile(LittleFS, ipPath);
  gateway = readFile (LittleFS, gatewayPath);
  Serial.println(ssid);
  Serial.println(pass);
  Serial.println(ip);
  Serial.println(gateway);

  if(initWiFi()) {
    // Route for root / web page
    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
      request->send(LittleFS, "/index.html", "text/html", false, processor);
    });
    server.serveStatic("/", LittleFS, "/");
    
    // Route to set GPIO state to HIGH
    server.on("/on", HTTP_GET, [](AsyncWebServerRequest *request) {
      digitalWrite(ledPin, HIGH);
      request->send(LittleFS, "/index.html", "text/html", false, processor);
    });

    // Route to set GPIO state to LOW
    server.on("/off", HTTP_GET, [](AsyncWebServerRequest *request) {
      digitalWrite(ledPin, LOW);
      request->send(LittleFS, "/index.html", "text/html", false, processor);
    });
    server.begin();
  }
  else {
    // Connect to Wi-Fi network with SSID and password
    Serial.println("Setting AP (Access Point)");
    // NULL sets an open Access Point
    WiFi.softAP("ESP-WIFI-MANAGER", NULL);

    IPAddress IP = WiFi.softAPIP();
    Serial.print("AP IP address: ");
    Serial.println(IP); 

    // Web Server Root URL
    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
      request->send(LittleFS, "/wifimanager.html", "text/html");
    });
    
    server.serveStatic("/", LittleFS, "/");
    
    server.on("/", HTTP_POST, [](AsyncWebServerRequest *request) {
      int params = request->params();
      for(int i=0;i<params;i++){
        const AsyncWebParameter* p = request->getParam(i);
        if(p->isPost()){
          // HTTP POST ssid value
          if (p->name() == PARAM_INPUT_1) {
            ssid = p->value().c_str();
            Serial.print("SSID set to: ");
            Serial.println(ssid);
            // Write file to save value
            writeFile(LittleFS, ssidPath, ssid.c_str());
          }
          // HTTP POST pass value
          if (p->name() == PARAM_INPUT_2) {
            pass = p->value().c_str();
            Serial.print("Password set to: ");
            Serial.println(pass);
            // Write file to save value
            writeFile(LittleFS, passPath, pass.c_str());
          }
          // HTTP POST ip value
          if (p->name() == PARAM_INPUT_3) {
            ip = p->value().c_str();
            Serial.print("IP Address set to: ");
            Serial.println(ip);
            // Write file to save value
            writeFile(LittleFS, ipPath, ip.c_str());
          }
          // HTTP POST gateway value
          if (p->name() == PARAM_INPUT_4) {
            gateway = p->value().c_str();
            Serial.print("Gateway set to: ");
            Serial.println(gateway);
            // Write file to save value
            writeFile(LittleFS, gatewayPath, gateway.c_str());
          }
          //Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
        }
      }
      request->send(200, "text/plain", "Done. ESP will restart, connect to your router and go to IP address: " + ip);
      delay(3000);
      ESP.restart();
    });
    server.begin();
  }
}

void loop() {

}

View raw code

How The Code Works

Let’s take a look at the code and see how the Wi-Fi Manager works.

The following variables are used to search for the SSID, password, IP address, and gateway on the HTTP POST request made when the form is submitted.

// Search for parameter in HTTP POST request
const char* PARAM_INPUT_1 = "ssid";
const char* PARAM_INPUT_2 = "pass";
const char* PARAM_INPUT_3 = "ip";
const char* PARAM_INPUT_4 = "gateway";

The ssid, pass, ip, and gateway variables save the values of the SSID, password, IP address, and gateway submitted in the form.

//Variables to save values from HTML form
String ssid;
String pass;
String ip;
String gateway;

The SSID, password, IP address, and gateway when submitted are saved in files in the ESP filesystem. The following variables refer to the path of those files.

// File paths to save input values permanently
const char* ssidPath = "/ssid.txt";
const char* passPath = "/pass.txt";
const char* ipPath = "/ip.txt";
const char* gatewayPath = "/gateway.txt";

The station IP address and gateway are submitted in the Wi-Fi Manager form. The subnet is hardcoded but you can easily modify this project with another field to include the subnet, if needed.

IPAddress localIP;
//IPAddress localIP(192, 168, 1, 200); // hardcoded

// Set your Gateway IP address
IPAddress localGateway;
//IPAddress localGateway(192, 168, 1, 1); //hardcoded
IPAddress subnet(255, 255, 0, 0);

initWiFi()

The initWiFi() function returns a boolean value (either true or false) indicating if the ESP board connected successfully to a network.

bool initWiFi() {
  if(ssid=="" || ip==""){
    Serial.println("Undefined SSID or IP address.");
    return false;
  }

  WiFi.mode(WIFI_STA);
  localIP.fromString(ip.c_str());

  if (!WiFi.config(localIP, gateway, subnet)){
    Serial.println("STA Failed to configure");
    return false;
  }
  WiFi.begin(ssid.c_str(), pass.c_str());
  Serial.println("Connecting to WiFi...");

  unsigned long currentMillis = millis();
  previousMillis = currentMillis;

  while(WiFi.status() != WL_CONNECTED) {
    currentMillis = millis();
    if (currentMillis - previousMillis >= interval) {
      Serial.println("Failed to connect.");
      return false;
    }
  }

  Serial.println(WiFi.localIP());
  return true;
}

First, it checks if the ssid and ip variables are empty. If they are, it won’t be able to connect to a network, so it returns false.

if(ssid=="" || ip==""){

If that’s not the case, we’ll try to connect to the network using the SSID and password saved in the ssid and pass variables and set the IP address.

WiFi.mode(WIFI_STA);
localIP.fromString(ip.c_str());

if (!WiFi.config(localIP, gateway, subnet)){
  Serial.println("STA Failed to configure");
  return false;
}
WiFi.begin(ssid.c_str(), pass.c_str());
Serial.println("Connecting to WiFi...");

If it cannot connect to Wi-Fi after 10 seconds (interval variable), it will return false.

unsigned long currentMillis = millis();
previousMillis = currentMillis;

while(WiFi.status() != WL_CONNECTED) {
  currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    Serial.println("Failed to connect.");
    return false;
  }

If none of the previous conditions are met, it means that the ESP successfully connected to the network in station mode (returns true).

return true;

setup()

In the setup(), start reading the files to get the previously saved SSID, password, IP address, and gateway.

ssid = readFile(LittleFS, ssidPath);
pass = readFile(LittleFS, passPath);
ip = readFile(LittleFS, ipPath);
gateway = readFile (LittleFS, gatewayPath);

If the ESP connects successfully in station mode (initWiFi() function returns true), we can set the commands to handle the web server requests (or any other code that requires the ESP to be connected to the internet):

if(initWiFi()) {
  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(LittleFS, "/index.html", "text/html", false, processor);
  });
  server.serveStatic("/", LittleFS, "/");
    
  // Route to set GPIO state to HIGH
  server.on("/on", HTTP_GET, [](AsyncWebServerRequest *request) {
    digitalWrite(ledPin, HIGH);
    request->send(LittleFS, "/index.html", "text/html", false, processor);
  });

  // Route to set GPIO state to LOW
  server.on("/off", HTTP_GET, [](AsyncWebServerRequest *request) {
    digitalWrite(ledPin, LOW);
    request->send(LittleFS, "/index.html", "text/html", false, processor);
  });
  server.begin();
}

If that’s not the case, the initWiFi() function returns false. The ESP will set an access point:

else {
  // Connect to Wi-Fi network with SSID and password
  Serial.println("Setting AP (Access Point)");
  // NULL sets an open Access Point
  WiFi.softAP("ESP-WIFI-MANAGER", NULL);

  IPAddress IP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(IP); 

To set an access point, we use the softAP() method and pass as arguments the name for the access point and the password. We want the access point to be open, so we set the password to NULL. You can add a password if you wish. To learn more about setting up an Access Point, read one of the following tutorials:

When you access the Access Point, it shows the web page to enter the network credentials in the form. So, the ESP must send the wifimanager.html file when it receives a request on the root / URL.

// Web Server Root URL
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send(LittleFS, "/wifimanager.html", "text/html");
});

We must also handle what happens when the form is submitted via an HTTP POST request. The following lines save the submitted values in the ssid, pass, and ip variables and save those variables in the corresponding files.

server.on("/", HTTP_POST, [](AsyncWebServerRequest *request) {
  int params = request->params();
  for(int i=0;i<params;i++){
    AsyncWebParameter* p = request->getParam(i);
    if(p->isPost()){
      // HTTP POST ssid value
      if (p->name() == PARAM_INPUT_1) {
        ssid = p->value().c_str();
        Serial.print("SSID set to: ");
        Serial.println(ssid);
        // Write file to save value
        writeFile(LittleFS, ssidPath, ssid.c_str());
      }
      // HTTP POST pass value
      if (p->name() == PARAM_INPUT_2) {
        pass = p->value().c_str();
        Serial.print("Password set to: ");
        Serial.println(pass);
        // Write file to save value
        writeFile(LittleFS, passPath, pass.c_str());
      }
      // HTTP POST ip value
      if (p->name() == PARAM_INPUT_3) {
        ip = p->value().c_str();
        Serial.print("IP Address set to: ");
        Serial.println(ip);
        // Write file to save value
        writeFile(LittleFS, ipPath, ip.c_str());
      }
     // HTTP POST gateway value
      if (p->name() == PARAM_INPUT_4) {
        gateway = p->value().c_str();
        Serial.print("Gateway set to: ");
        Serial.println(gateway);
        // Write file to save value
        writeFile(LittleFS, gatewayPath, gateway.c_str());
      }
      //Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
    }
  }

After submitting the form, send a response with some text so that we know that the ESP received the form details:

request->send(200, "text/plain", "Done. ESP will restart, connect to your router and go to IP address: " + ip);

After three seconds, restart the ESP board with ESP.restart():

delay(3000);
ESP.restart();

That’s a quick summary of how the code works.

You can apply this idea to any of the other web server projects built with the ESPAsyncWebServer library.

Uploading Code and Files

Upload the files in the data folder to your ESP32. On the Arduino IDE, press [Ctrl] + [Shift] + [P] on Windows or [] + [Shift] + [P] on MacOS to open the command palette. Search for the Upload LittleFS to Pico/ESP8266/ESP32 command and click on it.

Upload LittleFS to Pico ESP8266 ESP32 Arduino IDE

If you don’t have this option is because you didn’t install the filesystem uploader plugin. Check this tutorial.

Important: make sure the Serial Monitor is closed before uploading to the filesystem. Otherwise, the upload will fail.

If you’re using VS Code with the PlatformIO extension, follow one of the next tutorials to learn how to upload files to your boards:

After successfully uploading the files, upload the code to your board.

Arduino IDE 2 Upload Button

Demonstration

After successfully uploading all files and sketch, you can open the Serial Monitor. If it is running the code for the first time, it will try to read the ssid.txt, pass.txt, and ip.txt files, and it won’t succeed because those files weren’t created yet. So, it will start an Access Point.

ESP-Wi-Fi Manager Sets Access Point Serial Monitor

On your computer or smartphone, go to your network settings and connect to the ESP-WIFI-MANAGER access point.

Connect to ESP32 ESP8266 Access Point Wi-Fi Manager

Then, open your browser and go to 192.168.4.1. The Wi-Fi Manager web page should open.

ESP32 ESP8266 Wi-Fi Manager Fields

Enter your network credentials: SSID and Password and an available IP address on your local network.

After that, you’ll be redirected to the following page:

ESP32 ESP8266 connected to station success Wi-Fi Manager

At the same time, the ESP should print something in the Serial Monitor indicating that the parameters you’ve inserted were successfully saved in the corresponding files.

After a few seconds, the ESP will restart. And if you’ve inserted the correct SSID and password, it will start in station mode:

ESP32 ESP8266 Connected Successfully to Wi-Fi Station

This time, open a browser on your local network and insert the ESP IP address. You should get access to the web page to control the outputs:

ESP32 ESP8266 Control Outputs Web Server

Wrapping Up

In this tutorial, you’ve learned how to set up a Wi-Fi Manager for your web server projects or for any other project that requires the ESP to be connected to the internet. With the Wi-Fi Manager, you can easily connect your ESP boards to different networks without the need to hard-code network credentials. You can apply the Wi-Fi Manager to any web server project built with the ESPAsyncWebServer library.

One of our readers created a more advanced version of this project with more fields and features, you can check his project here.

If you want to learn more about building web servers with the ESP32 and ESP8266 boards, make sure you take a look at our eBook:

We hope you’ve found this tutorial useful.

Thanks 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 »

Enjoyed this project? Stay updated by subscribing our newsletter!

163 thoughts on “ESP32: Create a Wi-Fi Manager (AsyncWebServer library)”

  1. Very nice, clean little sketch. Almost all the Anysc examples I saw have more than 1,000 lines of code. A few more lines and you could have had the OTA included. Where were you a year ago when I need this?
    Ditto with what JB said. Keep up the great tutorials!

    Bob

    Reply
  2. Hi,

    Nice Tutorial..
    But How Connect different NodeMCUs say (15 Nos) to one WiFi different network to access remotely from website.(URL)


    Karan

    Reply
  3. Very good explanation, but you assume the home network is on 192.168.1.xxx. The gateway and subnet is hardcoded. Would’nt it be better to have the possiblity to configure subnet and gateway too? i.e my home net is on 192.168.xxx.yyy where xxx is NOT ‘1’
    I think with really slight modifications in the wifimanager.html and esp-code it should be possible.

    Reply
  4. I really enjoyed the tutorial. I went through the steps and everything was fine until I loaded the 192.168.4.1 site into my browser. (I was successfully connected to the ESP-WIFI-MANAGER). The site loaded, but nothing appeared on the screen. Any ideas?

    Reply
  5. I did. I’m afraid I’m too much of a novice for that level of technical detail. I’ll just keep trying to work with it. Thanks for the input.

    Reply
    • So, there must be something wrong. Or there are steps you’re missing out.
      I don’t know what else it can be… I think I wrote all the required steps.
      If you have another ESP32, please try with another board.
      Regards,
      Sara

      Reply
      • Esp32 connect with local networks and show ip address 192.168.1.200. When I go to that ip address didn’t show any things

        Reply
        • Hi.
          That’s probably because you didn’t upload the files to the filesystem.
          Make sure you follow all the described steps.
          Regards,
          Sara

          Reply
          • I uploaded all the files according to instructions when trying to open the ip 192.168.1.200 the browser hange. Did not show any thing

          • Did you upload the index.html and style.css files?
            Did you create the data folder with those files and upload it using Tool > Sketch Data Upload?
            Are you connected to the same network of the ESP32?
            Note that with this example, you need to connect to the ESP32 access point first to insert the network credentials, and only then, you’ll connect to the 192.168.1.200 IP address via your local network.
            Regards,
            Sara

  6. Hello and Happy New Year! I enjoy and learn much from your tutorials, but I start having problems when I combine tutorial apps into a more complicated app. For example, I combined the “ESP32 with BME280 Sensor” tutorial with the “ESP32 Data Logging Temperature to MicroSD Card” and the “Guide to 1.8 TFT Display with Arduino” to create a web page to view weather conditions and also download a data log, while being able to see current conditions on a TFT if I’m physically present.

    But when I tried to add the functionality of “ESP32: Create a Wi-Fi Manager (AsyncWebServer library)” , all sorts of strange problems cropped up. SPIFFS does not mix well with SD. My attempts to convert everything to SD somehow affected the ESP32 in such a way that I have to run an “EraseFlash” sketch to get the SD to work again.

    My request is this: could you comment in the tutorials about how/where one might introduce their own modifications? Is there a tutorial about how one might organize one’s work into different linked files, sort of like libraries do? Scrolling thru a very long sketch is hard to debug.

    Reply
  7. I downloaded the files but for some reason when I try to compile I’m getting
    exit status 1
    Error compiling for board ESP32 Dev Module.
    in Arduino IDE 1.8.13;
    yet it compiles and loads fine in IDE 2
    however IDE2 does not support the tools for uploading to SPIFFS.

    Reply
  8. I’m running IDE1.8.13 under windows 10
    Connected to an ESP32 Devkit that has previously run one of your async webserver tutorials.

    Looking through the output I’m seeing this in red

    “C:\ArduinoIDEPortable\portable\packages\esp32\tools\xtensa-esp32-elf-gcc\gcc8_4_0-esp-2021r2/bin/xtensa-esp32-elf-g++” “-Wl,–Map=C:\Users\johnl\AppData\Local\Temp\arduino_build_699573/ESP32_WiFi_Manager.ino.map” “-LC:\ArduinoIDEPortable\portable\packages\esp32\hardware\esp32\2.0.1/tools/sdk/esp32/lib” “-LC:\ArduinoIDEPortable\portable\packages\esp32\hardware\esp32\2.0.1/tools/sdk/esp32/ld” -T esp32.rom.redefined.ld -T memory.ld -T sections.ld -T esp32.rom.ld -T esp32.rom.api.ld -T esp32.rom.libgcc.ld -T esp32.rom.newlib-data.ld -T esp32.rom.syscalls.ld -T esp32.peripherals.ld -mlongcalls -Wno-frame-address -Wl,–cref -Wl,–gc-sections -fno-rtti -fno-lto -u ld_include_hli_vectors_bt -u _Z5setupv -u _Z4loopv -Wl,–wrap=mbedtls_mpi_exp_mod -u esp_app_desc -u pthread_include_pthread_impl -u pthread_include_pthread_cond_impl -u pthread_include_pthread_local_storage_impl -u ld_include_highint_hdl -u start_app -u start_app_other_cores -u __ubsan_include -Wl,–wrap=longjmp -u __assert_func -u vfs_include_syscalls_impl -Wl,–undefined=uxTopUsedPriority -u app_main -u newlib_include_heap_impl -u newlib_include_syscalls_impl -u newlib_include_pthread_impl -u newlib_include_assert_impl -u __cxa_guard_dummy -DESP32 -DCORE_DEBUG_LEVEL=0 -DARDUINO_RUNNING_CORE=1 -DARDUINO_EVENT_RUNNING_CORE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -Wl,–start-group “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\sketch\ESP32_WiFi_Manager.ino.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\WiFi\WiFi.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\WiFi\WiFiAP.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\WiFi\WiFiClient.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\WiFi\WiFiGeneric.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\WiFi\WiFiMulti.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\WiFi\WiFiSTA.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\WiFi\WiFiScan.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\WiFi\WiFiServer.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\WiFi\WiFiUdp.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\ESPAsyncWebServer\AsyncEventSource.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\ESPAsyncWebServer\AsyncWebSocket.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\ESPAsyncWebServer\SPIFFSEditor.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\ESPAsyncWebServer\WebAuthentication.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\ESPAsyncWebServer\WebHandlers.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\ESPAsyncWebServer\WebRequest.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\ESPAsyncWebServer\WebResponses.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\ESPAsyncWebServer\WebServer.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\FS\FS.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\FS\vfs_api.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\AsyncTCP\AsyncTCP.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\SPIFFS\SPIFFS.cpp.o” “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\core\core.a” -lesp_ringbuf -lefuse -lesp_ipc -ldriver -lesp_pm -lmbedtls -lapp_update -lbootloader_support -lspi_flash -lnvs_flash -lpthread -lesp_gdbstub -lespcoredump -lesp_phy -lesp_system -lesp_rom -lhal -lvfs -lesp_eth -ltcpip_adapter -lesp_netif -lesp_event -lwpa_supplicant -lesp_wifi -lconsole -llwip -llog -lheap -lsoc -lesp_hw_support -lxtensa -lesp_common -lesp_timer -lfreertos -lnewlib -lcxx -lapp_trace -lasio -lbt -lcbor -lunity -lcmock -lcoap -lnghttp -lesp-tls -lesp_adc_cal -lesp_hid -ltcp_transport -lesp_http_client -lesp_http_server -lesp_https_ota -lesp_lcd -lprotobuf-c -lprotocomm -lmdns -lesp_local_ctrl -lsdmmc -lesp_serial_slave_link -lesp_websocket_client -lexpat -lwear_levelling -lfatfs -lfreemodbus -ljsmn -ljson -llibsodium -lmqtt -lopenssl -lperfmon -lspiffs -lulp -lwifi_provisioning -lbutton -ljson_parser -ljson_generator -lesp_schedule -lesp_rainmaker -lqrcode -lws2812_led -lesp-dsp -lesp32-camera -lesp_littlefs -lfb_gfx -lasio -lcbor -lcmock -lunity -lcoap -lesp_lcd -lesp_local_ctrl -lesp_websocket_client -lexpat -lfreemodbus -ljsmn -llibsodium -lperfmon -lesp_adc_cal -lesp_hid -lfatfs -lwear_levelling -lopenssl -lspiffs -lesp_rainmaker -lmqtt -lwifi_provisioning -lprotocomm -lbt -lbtdm_app -lprotobuf-c -lmdns -ljson -ljson_parser -ljson_generator -lesp_schedule -lqrcode -lcat_face_detect -lhuman_face_detect -lcolor_detect -lmfn -ldl -lesp_ringbuf -lefuse -lesp_ipc -ldriver -lesp_pm -lmbedtls -lapp_update -lbootloader_support -lspi_flash -lnvs_flash -lpthread -lesp_gdbstub -lespcoredump -lesp_phy -lesp_system -lesp_rom -lhal -lvfs -lesp_eth -ltcpip_adapter -lesp_netif -lesp_event -lwpa_supplicant -lesp_wifi -lconsole -llwip -llog -lheap -lsoc -lesp_hw_support -lxtensa -lesp_common -lesp_timer -lfreertos -lnewlib -lcxx -lapp_trace -lnghttp -lesp-tls -ltcp_transport -lesp_http_client -lesp_http_server -lesp_https_ota -lsdmmc -lesp_serial_slave_link -lulp -lmbedtls -lmbedcrypto -lmbedx509 -lcoexist -lcore -lespnow -lmesh -lnet80211 -lpp -lsmartconfig -lwapi -lesp_ringbuf -lefuse -lesp_ipc -ldriver -lesp_pm -lmbedtls -lapp_update -lbootloader_support -lspi_flash -lnvs_flash -lpthread -lesp_gdbstub -lespcoredump -lesp_phy -lesp_system -lesp_rom -lhal -lvfs -lesp_eth -ltcpip_adapter -lesp_netif -lesp_event -lwpa_supplicant -lesp_wifi -lconsole -llwip -llog -lheap -lsoc -lesp_hw_support -lxtensa -lesp_common -lesp_timer -lfreertos -lnewlib -lcxx -lapp_trace -lnghttp -lesp-tls -ltcp_transport -lesp_http_client -lesp_http_server -lesp_https_ota -lsdmmc -lesp_serial_slave_link -lulp -lmbedtls -lmbedcrypto -lmbedx509 -lcoexist -lcore -lespnow -lmesh -lnet80211 -lpp -lsmartconfig -lwapi -lesp_ringbuf -lefuse -lesp_ipc -ldriver -lesp_pm -lmbedtls -lapp_update -lbootloader_support -lspi_flash -lnvs_flash -lpthread -lesp_gdbstub -lespcoredump -lesp_phy -lesp_system -lesp_rom -lhal -lvfs -lesp_eth -ltcpip_adapter -lesp_netif -lesp_event -lwpa_supplicant -lesp_wifi -lconsole -llwip -llog -lheap -lsoc -lesp_hw_support -lxtensa -lesp_common -lesp_timer -lfreertos -lnewlib -lcxx -lapp_trace -lnghttp -lesp-tls -ltcp_transport -lesp_http_client -lesp_http_server -lesp_https_ota -lsdmmc -lesp_serial_slave_link -lulp -lmbedtls -lmbedcrypto -lmbedx509 -lcoexist -lcore -lespnow -lmesh -lnet80211 -lpp -lsmartconfig -lwapi -lesp_ringbuf -lefuse -lesp_ipc -ldriver -lesp_pm -lmbedtls -lapp_update -lbootloader_support -lspi_flash -lnvs_flash -lpthread -lesp_gdbstub -lespcoredump -lesp_phy -lesp_system -lesp_rom -lhal -lvfs -lesp_eth -ltcpip_adapter -lesp_netif -lesp_event -lwpa_supplicant -lesp_wifi -lconsole -llwip -llog -lheap -lsoc -lesp_hw_support -lxtensa -lesp_common -lesp_timer -lfreertos -lnewlib -lcxx -lapp_trace -lnghttp -lesp-tls -ltcp_transport -lesp_http_client -lesp_http_server -lesp_https_ota -lsdmmc -lesp_serial_slave_link -lulp -lmbedtls -lmbedcrypto -lmbedx509 -lcoexist -lcore -lespnow -lmesh -lnet80211 -lpp -lsmartconfig -lwapi -lesp_ringbuf -lefuse -lesp_ipc -ldriver -lesp_pm -lmbedtls -lapp_update -lbootloader_support -lspi_flash -lnvs_flash -lpthread -lesp_gdbstub -lespcoredump -lesp_phy -lesp_system -lesp_rom -lhal -lvfs -lesp_eth -ltcpip_adapter -lesp_netif -lesp_event -lwpa_supplicant -lesp_wifi -lconsole -llwip -llog -lheap -lsoc -lesp_hw_support -lxtensa -lesp_common -lesp_timer -lfreertos -lnewlib -lcxx -lapp_trace -lnghttp -lesp-tls -ltcp_transport -lesp_http_client -lesp_http_server -lesp_https_ota -lsdmmc -lesp_serial_slave_link -lulp -lmbedtls -lmbedcrypto -lmbedx509 -lcoexist -lcore -lespnow -lmesh -lnet80211 -lpp -lsmartconfig -lwapi -lphy -lrtc -lesp_phy -lphy -lrtc -lesp_phy -lphy -lrtc -lxt_hal -lm -lnewlib -lstdc++ -lpthread -lgcc -lcxx -lapp_trace -lgcov -lapp_trace -lgcov -lc -Wl,–end-group -Wl,-EL -o “C:\Users\johnl\AppData\Local\Temp\arduino_build_699573/ESP32_WiFi_Manager.ino.elf”
    c:/arduinoideportable/portable/packages/esp32/tools/xtensa-esp32-elf-gcc/gcc8_4_0-esp-2021r2/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld.exe: C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\ESPAsyncWebServer\WebAuthentication.cpp.o:(.literal._ZL6getMD5PhtPc+0x4): undefined reference to mbedtls_md5_starts'
    c:/arduinoideportable/portable/packages/esp32/tools/xtensa-esp32-elf-gcc/gcc8_4_0-esp-2021r2/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld.exe: C:\Users\johnl\AppData\Local\Temp\arduino_build_699573\libraries\ESPAsyncWebServer\WebAuthentication.cpp.o: in function
    getMD5(unsigned char*, unsigned short, char*)’:
    C:\ArduinoIDEPortable\portable\sketchbook\libraries\ESPAsyncWebServer\src/WebAuthentication.cpp:73: undefined reference to `mbedtls_md5_starts’
    collect2.exe: error: ld returned 1 exit status

    Then
    exit status 1
    Error compiling for board ESP32 Dev Module.

    Reply
    • As far as I’m aware, SPIFFS doesn’t support directory structures (‘/’ is just a character in the filename in SPIFFS), but the newer LittleFS does support them (though I’m not so sure about file permissions).

      I had intended modifying the code to use LittleFS, as Espressif say that it is preferred over SPIFFS, nowadays — but now you’ve made me wonder if that would be hackable in the way that you suggest. Maybe there’s a way to disable access to these credentials files in the webserver? Sorry, I’ve not got as far as trying any of this out yet, but I hope to do so soon.

      Reply
  9. The idea of the button would be: by pressing the button the credentials are wi-fi are erased and the user can connect to a new wifi network.

    Reply
  10. Hi Sara and Rui,

    Thank you very much for producing these nice tutorials.

    I have a little question, I got the same error as Dan reported on 28 December, actually due to a mistake … I provided the 192.168.1.xxx address whereas my home WiFI does not use that.
    But still the ESP reported WiFi.status()==WL_CONNECTED the value of both was set to 3.
    The ESP was not connected to my router….
    When I set up the proper WiFi subnet it worked immediately and I saw the ESP also as a client
    at my router

    Could you tell me what is happening here?

    Below the part of the sketch and the output from serial monitor

    while(WiFi.status() != WL_CONNECTED) {
    currentMillis = millis();
    if (currentMillis – previousMillis >= interval) {
    Serial.println(“Failed to connect.”);
    return false;
    }
    }

    Serial.println(WiFi.localIP());
    Serial.println(“Dit is een mooie bak”);
    Serial.println(WiFi.status());
    Serial.println(WL_CONNECTED);
    return true;

    serial monitor
    Connecting to WiFi…
    192.168.123.216
    Dit is een mooie bak
    3
    3

    ===== Dan:
    December 28, 2021 at 6:16 pm

    The files did upload and the ESP32 connected with this monitor readout:

    Connecting to WiFi…
    192.168.1.200

    But when I go back to my wifi network and enter 192,168.1.200 the browser just hangs up with no output.

    Thx and best regards

    paul

    Reply
    • Sorry the copy of the serial monitor was acrually a sucesful attempt

      the proper output with the error was almost the same:
      serial monitor
      Connecting to WiFi…
      192.168.1.216
      Dit is een mooie bak
      3
      3

      Reply
  11. Hi Sara,

    Thank you for your quick answer. I understand that the system will not work if the subnet is not configured properly..
    The question I have is why the system reports back WiFi.status as connected when it is not?
    In that situation the system is not reachable via WiFi so I cannot change settings anymore
    I have to use the Arduino interface to get it working again…
    My project is building a cat feeder system to be used by people that do not program arduino, That means that a WiFi configuration error should result in the system going back in station mode.

    Would you have a clue as what would be the best way to dig in the error handling here?

    Thx

    Paul

    Reply
  12. Hi Luberth,

    Thx for your suggestion, I wil look at your code (I was actually planning to do so already).

    Still it bugs me that the WiFi.status comes back with a success return value. You have any idea where I can find how the error handlig is done?

    thx
    paul

    Reply
  13. Hi there,
    After running ap mode, go to index.html and click the menu to enter wifi_manager.html and set up wifi, but an error occurs.
    Regards

    Reply
      • Hi Sara
        Thank you for your quick answer.
        I want the wifi_manager.html page to work after clicking the menu by creating a menu in the html I made in ap mode.
        However, if I press the submit button after entering wifi_manager.html, it does not work.
        This is the main html file code
        wifi_manager
        This is the main.cpp file code.
        server.on(“/move another html”, HTTP_POST, [](AsyncWebServerRequest request) {
        int params = request->params();
        for(int i=0;i<params;i++){
        AsyncWebParameter
        p = request->getParam(i);
        if(p->isPost()){
        // HTTP POST ssid value
        if (p->name() == PARAM_INPUT_1) {
        ssid = p->value().c_str();
        Serial.print(“SSID set to: “);
        Serial.println(ssid);
        // Write file to save value
        writeFile(SPIFFS, ssidPath, ssid.c_str());
        }
        // HTTP POST pass value
        if (p->name() == PARAM_INPUT_2) {
        pass = p->value().c_str();
        Serial.print(“Password set to: “);
        Serial.println(pass);
        // Write file to save value
        writeFile(SPIFFS, passPath, pass.c_str());
        }

        Reply
  14. Hi Luberth
    That’s right. I referenced your code.
    I want the wifi_manager.html page to work after clicking the menu by creating a menu in the html I made in ap mode.
    However, if I press the submit button after entering wifi_manager.html, it does not work.
    This is the first html file code
    wifi_manager
    This is the main.cpp file code.
    server.on(“/move another html”, HTTP_POST, [](AsyncWebServerRequest request) {
    int params = request->params();
    for(int i=0;i<params;i++){
    AsyncWebParameter
    p = request->getParam(i);
    if(p->isPost()){
    // HTTP POST ssid value
    if (p->name() == PARAM_INPUT_1) {
    ssid = p->value().c_str();
    Serial.print(“SSID set to: “);
    Serial.println(ssid);
    // Write file to save value
    writeFile(SPIFFS, ssidPath, ssid.c_str());
    }
    // HTTP POST pass value
    if (p->name() == PARAM_INPUT_2) {
    pass = p->value().c_str();
    Serial.print(“Password set to: “);
    Serial.println(pass);
    // Write file to save value
    writeFile(SPIFFS, passPath, pass.c_str());
    }

    Reply
  15. Hello Rui and Sara,

    I’ve been trying to add the UTF-8 charecter encoding in order to display characters in Portuguese. The problem is that when I open the .html file everything looks fine, but as I open the IP address on my browser (tried 3 different ones so far) the unicode doesn’t seem to work. I’ve added the following to my code:

    Also, I wanted to add the “show/hide” password eye icons option, and also managed to do so in the .html file. But as I open once again the IP address on my browser, it doesn’t seem to find the font awesome files that I inserted. Already tried to insert the all.min.css, insert the links pointing to the font awesome files as you did on the example, and other options around the internet, but nothing seems to work. Right now, this is displayed on my code:

    And when i check the console, it shows the message:
    GET https://use.fontawesome.com/releases/v5.13.0/css/all.css net::ERR_NAME_NOT_RESOLVED

    The whole is as the following:

    Gerenciamento de Wi-Fi

    Any suggestions on how to solve these problems? I know they are not that big of a deal, but I think it would really make my website complete.

    Thank you in advance!

    Larissa

    Reply
    • Hi.
      If the board is in access point mode, it can’t access the files from the internet.
      So, it won’t be able to access font awesome website to get the icons.
      Regards,
      Sara

      Reply
  16. Please help
    The data and sketch uploaded correctly
    The wifimanager loads and I can enter the WiFi SSID and password
    All other actions perform correctly, however when connecting to the ESP the index page does not display.
    The router displays the ESP connected.
    Everything works but nothing happens.
    Can anybody point me in the right direction

    Reply
  17. the server.on POST part can also be done like this.

    server.on("/", HTTP_POST, [](AsyncWebServerRequest *request) {
    if (request->hasParam("ssid", true) && (request->hasParam("pass", true) && request->hasParam("ip", true))){
    pass = request->getParam("pass", true)->value();
    ssid = request->getParam("ssid", true)->value();
    ip = request->getParam("ip", true)->value();

    Serial.print("SSID set to: ");
    Serial.println(ssid);
    // Write file to save value
    writeFile(SPIFFS, ssidPath, ssid.c_str());

    Serial.print("Password set to: ");
    Serial.println(pass);
    // Write file to save value
    writeFile(SPIFFS, passPath, pass.c_str());

    Serial.print("IP Address set to: ");
    Serial.println(ip);
    // Write file to save value
    writeFile(SPIFFS, ipPath, ip.c_str());

    request->send(200, "text/plain", "Done. ESP will restart, connect to your router and go to IP address: " + ip);
    delay(3000);
    ESP.restart();
    }
    });

    Reply
    • Thank you JB.
      However the problem is not with the WiFi manager. That works well.
      The new SSID and password is saved and the WIFi router connected.

      The router now sees the web server and accepts it.
      The problem appears to be in the following portion of the code which does NOT present the index page for any browser.

      if(initWiFi()) {
      // Route for root / web page
      server.on(“/”, HTTP_GET, [](AsyncWebServerRequest *request) {
      request->send(SPIFFS, “/index.html”, “text/html”, false, processor);
      });
      server.serveStatic(“/”, SPIFFS, “/”);

      Reply
      • Maybe your IPAddress gateway is 192.168.0.200
        and your IPAddress localIP is 192.168.0.200
        instead of this

        //IPAddress localIP(192, 168, 1, 200); // hardcoded
        / Set your Gateway IP address
        IPAddress gateway(192, 168, 1, 1);
        IPAddress subnet(255, 255, 0, 0);

        Reply
        • Sorry the gateway should like this.
          Maybe your IPAddress gateway is 192.168.0.1
          and your IPAddress localIP is 192.168.0.200
          instead of this

          //IPAddress localIP(192, 168, 1, 200); // hardcoded
          / Set your Gateway I

          Reply
          • By the way if you change the code in the sketch to this
            // Set your Gateway IP address
            IPAddress gateway (192, 168, 0, 1);
            IPAddress subnet (255, 255, 255, 0);
            Remember to change it in the wifimanager.html file as well.
            to avoid not seeing it on submit.

  18. Hi
    Note that if the IP gateway address in the sketch
    does not match your IP gateway address and your localIP address
    Then you will not be able to access the index page.
    Even if it looks like you are connected.

    Reply
  19. Thank you for those nice tutorials.
    A question: how to (re)connect automatically to a WiFi server when more than a simple password is required. I speak about Internet accessed after connecting to a WiFI network and then filing both an ID and a password on an Internet browser page (e.g. camping and other public accesses . . . )?

    Reply
  20. If I use https://192.168.4.1 I get a message that looks like it is from firefox saying “unable to connect”. It has a graphic of a sad sun. (note the S on https)
    If I use http://192.168.4.1 I get a text message that says “Connection timed out” that looks like it comes from the ESP. (note no S on htpp)
    I have wifi analyzer and it shows good signal strength on the ESP.
    The files were uploaded from the data directory according to the upload output.
    Can you offer trouble shooting advice please.

    Reply
    • Above was on my phone but it worked from my laptop. After some typos, it connects to the network as 192.168.0.19 as set but there are no open ports according to the port scanner and putting 192.168.0.19 in the browser as http doesn’t stick – it always goes back to https.
      Is one supposed to get the configuration page when the ESP is a station?

      Reply
  21. Solved “Connection timed out”. I posted above that my phone would not load the webpage from 192.168.4.1 even though I could connect to the Access Point. I could connect through my laptops however. The problem rests with the phone. The phone O/S will not connect to the same IP as the access point. It seems the phone demands internet access so that it can stop using mobile data. The ESP32 does not have internet access.
    All you need to do is turn off mobile data and then you can connect.

    Reply
  22. Thank you for so many great tutorials. It helped me a lot to build my own home automatisation, and many other projects.

    I have one general question: can all your web/WiFi based projectS simply be transferred to IPv6? I assume that for internet of things IPv6 would be a much better approach.

    Thanks,

    Thomas

    Reply
  23. Hi,

    Great tutorial that gives you the ability to fully customize it.
    One question:
    How can you make it such that as soon as a user joins the ESP32 Wifi network their phone forwards them to the 192.168.4.1 domain?

    I’ve noticed that using the WifiManager module you can do this. But I much prefer to use your method as it gives full control over the webpage.

    Reply
  24. Hi,
    I have the same question as Rahul Bhardwaj. Using tzapu’s wifi manager and an iPhone, after connecting to the ESP AP the iPhone automatically brings up the configuration page. Is there a way to have your WifiMagager do the same? I really like the ability to personalize the configuration page your version provides.

    Thank you for all your hard work. I have purchased several of your books and always look forward to your new projects.

    Kind Regards,
    Michael

    Reply
  25. Hi

    Is there a way to read the .txt files saved in step 1/ ??
    I saw in a previous tuto a way to read file contained in data/ folder ( test_exemple.txt) with LittleFS Uploader.

    Thanks.

    Reply
  26. thank you for those tutorials, very nerd more than me! :-p
    just a question: i’d like to create something like that BUT after point 7)
    “If it establishes a connection, the process is completed successfully, and you can access the main web server page that can do whatever you want (control sensor readings, control outputs, display some text, etc.)” i d like to continue sketch without web server: i would use the first steps to save and store SSID and PASSWORD, and after ESP32 restart use that data to connect to wifi and then run with Telegram Bot, having already SSID and PASSWORD. So i can use where i want without coding again everytime i change SSID and password.

    can i use a more simply tutorial, with ESP32 as AccessPoint to store data field in EEPROM (locations 100-200) byte-at-byte and then reboot and read from eeprom that data?

    my situation: i need to use telegram bot but i move in different AP names so i have to code again every time!
    the best should be
    -> short-cirtuit 2 pins
    -> esp32 see short circuited pins and then reboot in AP mode with a web-server with 2 fields, SSID and PASSword
    -> i can access to html page and write SSID and PASS, click a button
    -> esp32 store data in EEPROM and reboots
    -> in the meanwhile i not short-circuit those 2 pins so esp32 reboots in CLIENT mode
    -> esp32 read from eeprom and connetc to those credentials
    -> after connected, i ll use with my sketch for telegram bot to do what i have to do normally
    what shall i do?
    thank you sooo much

    Reply
  27. Hi!! I’ve done exactly the tutorial, but I have some errors at VsCode and with Arduino IDE.

    VsCode Error:
    c:/users/leosp/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld.exe: .pio\build\esp32doit-devkit-v1\lib182\libESP Async WebServer.a(WebAuthentication.cpp.o):(.literal._ZL6getMD5PhtPc+0x4): undefined reference to mbedtls_md5_starts'
    c:/users/leosp/.platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld.exe: .pio\build\esp32doit-devkit-v1\lib182\libESP Async WebServer.a(WebAuthentication.cpp.o): in function
    getMD5(unsigned char*, unsigned short, char*)’:
    C:\Users\leosp\Documents\PlatformIO\Projects\E_C_Pruebas generales/.pio/libdeps/esp32doit-devkit-v1/ESP Async WebServer/src/WebAuthentication.cpp:73: undefined reference to `mbedtls_md5_starts’
    collect2.exe: error: ld returned 1 exit status
    *** [.pio\build\esp32doit-devkit-v1\firmware.elf] Error 1

    Arduino IDE error:
    In file included from C:\Users\leosp\Downloads\ESP32_WiFi_Manager\ESP32_WiFi_Manager.ino:14:0:
    c:\Users\leosp\Documents\Arduino\libraries\ESPAsyncWebServer-master\src/ESPAsyncWebServer.h:33:22: fatal error: AsyncTCP.h: No such file or directory
    compilation terminated.

    exit status 1

    Compilation error: exit status 1

    Help please!!

    Reply
  28. hi,
    I’m using ESP32Asynwifimanager library for credentials changing working fine. I’m trying to connect the IP address 192.168.1.200 which is showing on my serial monitor. But it is not connecting to a webpage.

    Reply
  29. Hi, how do I change the Wi-Fi configuration so that I can connect to a firebase database from this?
    It is not connecting to firebase Can you help me find the fault here?

    link to the Arduino code – github.com/senath112/Weather4You-firmware/blob/main/firmware.ino

    help please…..

    Reply
    • Hi.
      I was searching for his github page and it has disappeared. It seems he has deleted his Github page. Maybe he has a new one now, but I couldn’t find it.
      Regards,
      Sara

      Reply
  30. Hi Sara
    This link at the end of your article is dead.

    “One of our readers created a more advanced version of this project with more fields and features, you can check his project here.”

    Pity. It looked promising.

    Reply
  31. hi there thanks for the great tutorials.
    i found that the wifi access point fails to come up if cant connect using the credentials are in the text files

    this is posiibly due a change in the framework but anyway
    WiFi.mode is set to WiFi.STA in the initWiFi()
    setting the WiFi.mode to WIFI_AP before starting the softAP fixes it

    Reply
  32. Greetings to all

    Sara:
    is it possible to connect Wi-Fi without ip and gateway information? just using ssid and password? I have seen the example of previous asyncwebserver connecting Wi-Fi with only ssid and password. Please suggest. Thank you

    Reply
  33. HI, big fan and customer here….

    with the absence of lidjkman’s github… I REALLY thing you guys should re-visit this tutorial, as this is code that almost EVERY microcontroller project is going to want to include…. Major gaps are:
    1. Hold down a button to erase the wifi config
    2. provide an automatic scan of available ssid’s
    3. ability to leave the direct connect ap running all the time for situations where the area does not have an ap infrastructure
    4. change code to LittleFS (easy to do on your own but still handy)
    5. OTA built-in

    These features are implemented in lots of other code, and can be “borrowed”… most of them are not async…. this will make this code the ULTIMATE and only wifimanager people would ever want for ESP32…. you could even sell it it would be so good.

    I do not have the expertise to weave all these features together…. but I know you can…

    just my opinion… probably take you a day

    Reply
  34. Hi there, many thanks for this and everything else!
    There’s a little typo in the code box an in github raw code, search for ‘frite failed’ 🙂


    Stefan

    Reply
  35. Hi.
    The program works correctly, but when I want to use it with the Time.h library, despite the correct connection to WiFi, it cannot update the time. Why?

    Reply
  36. Wow what an amazing project!!!!! Now imagine this project with the possibility that the user can also add a Telegram token and ID through a web page, it would be wonderful, it would give the user the power to change Bots whenever he wants, delete and create another BOT without worrying about changing code or even a user who has no programming knowledge. It would be too much!!!!

    Reply
  37. Reading file: /ssid.txt
    – failed to open file for reading
    Reading file: /pass.txt
    – failed to open file for reading
    Reading file: /ip.txt
    – failed to open file for reading
    Reading file: /gateway.txt
    – failed to open file for reading

    i am facing this error and the site does not open even if i enter the ip address

    Undefined SSID or IP address.
    Setting AP (Access Point)
    AP IP address: 192.168.4.1

    Reply
  38. The WifiManager library lists all available networks (SSID’s) for a convenient user experience, rather than having to know the exact spelling of the SSID and manually type it into the SSID field. How does the WifiManager do this, does anyone know? Is it in C++ (backend), or is it done in Javascript (frontend)? I wish this tutorial might contain this feature.

    Reply
    • Hi.
      It basically runs something similar to the WiFiScan example sketch that finds all networks within its range.
      You can check the example in the Arduino IDE examples menu.
      Regards,
      Sara

      Reply
  39. Hi Rui and Sara, I tried to combine these projects “Control ESP32 and ESP8266 GPIOs from Anywhere in the World “, “ESP32: Create a Wi-Fi Manager (AsyncWebServer library) ” separately they work very well but together I get an error when trying to get the json.

    Error code: -1
    {}
    JSON object = {}
    Guru Meditation Error: Core 1 panic’ed (StoreProhibited). Exception was unhandled.

    Reply
  40. Hi,

    I uploaded the files with platformio.
    I can connect to the Wfi with wifimanager, but on the index page to ontrol the led it doesn’t seem to work when i’m pressing a button.
    And maybe most important: on the bottom it says %STATE% instead of the real state op the led.
    How can this be solved please?

    Reply
  41. I followed this tutorial and want to submit user inputs via a web browser. so I improved the index.html code to submit a form and change the esp32 code. but I cannot receive any user inputs to the serial monitor from the web browser.

    Reply
  42. Dear Sara

    in case of using Preference library instead of SPIFFS
    how we need to change this part of code ?

    // Web Server Root URL
    server.on(“/”, HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(SPIFFS, “/wifimanager.html”, “text/html”);
    });

    server.serveStatic("/", SPIFFS, "/");

    server.on("/", HTTP_POST, [](AsyncWebServerRequest *request) {

    Thank you

    Reply
  43. Dear Sara,
    do you have any example using ESPAsyncWebServer with MQTT client. I just want to publish some sensor data after wifi is connected.

    Thanks

    Reply
  44. Great tutorial, I followed it with VS code and SPIFFS. However I fail to make it work, that is to show the wifimanager.html after I have connected to esp32 as AP.
    I placed a couple of lines after the initSPIFFS(); when you load values saved in SPIFFS.

    wifimanagerhtml = readFile (SPIFFS, wifimanagerPath);
    Serial.println(wifimanagerhtml);
    the result is that I receive is:

    Reading file: /wifimanager.html
    – failed to open file for reading

    I can’t figure out why. It must be something elemental. I have placed the wifimanager.html in the data directory. I have placed other files as well. For example I placed
    configFile = readFile (SPIFFS, configPath);
    Serial.println(configFile);

    This can be read as created:

    Reading file: /config.txt
    {“topic”:”home/news”,”parameter_1″:108}

    Any ideas as to where to look? Thanks a lot!!

    Reply
    • I have solved it!. When in VScode/platformio, I recreated a file in /data directory. I named it wifimanager.html and placed a very simple html code inside.I saved it and I noticed that the vscode editor recognized the file type and colored the tags and names and types differently. I went copying the wifimanager html code from the source code of firefox and rerun the whole process of building and uploading the file system. So for some reason the file wifimanager html was not created right in the first place. I don’t know why though. Maybe I copied something that was not visible(?) and pasted it inside the file making it unfit to be recognized as html file(?).

      Reply
  45. Hello,

    Great guide, I followed all and have some issue, I have copied all files and code and uploaded it to my ESP32.
    I restart the board and as expected first time, no files are available so it goes to AP mode, I can see the web page and added my info.
    All is written to the FS.
    ESP is restarting and I can see it prints the right SSID and Password, but the WiFi.begin fails.
    I debugged it a bit and if I put my SSID and Pass in the WiFi.begin all is working.
    Looks like the ssid.c_str doesn’t work or the conversion from String to char* is not done correctly.

    Any idea why this happens and how to fix?

    Thanks
    Nir

    Reply
  46. Hi,
    I am using Node32s controller

    while compiling the ESP_32_Wifi_Manager Sketch i am getting following error

    Compilation error: invalid conversion from ‘const AsyncWebParameter‘ to ‘AsyncWebParameter‘ [-fpermissive]

    Reply
    • Hi.
      There was an update in the library.
      But, we already fixed the code.
      Check the code now, it should compile just fine.
      Let me know if it is working.
      Regards,
      Sara

      Reply
  47. Hello,
    I was following the instructions on my ESP32 board (using Arduino IDE 2.3.2) , and all was working great, however after a few days I saw that Arduino wants to update it’s libraries, I selected ok , and after that my compilation fails with this error:

    Arduino/sketch_wifi/sketch_wifi.ino: In lambda function:
    Arduino/sketch_wifi/sketch_wifi.ino:345:51: error: invalid conversion from ‘const AsyncWebParameter‘ to ‘AsyncWebParameter‘ [-fpermissive]
    345 | AsyncWebParameter* p = request->getParam(i);
    | ~~~~~~~~~~~~~~~~~^~~
    | |
    | const AsyncWebParameter*

    exit status 1

    Compilation error: invalid conversion from ‘const AsyncWebParameter‘ to ‘AsyncWebParameter‘ [-fpermissive]

    Can you help ?
    Nir Gal

    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.