ESP32 – Launch Captive Portal only if WiFi connect fails

Introduction

This post is a follow-up to the ‘Create Captive Portal using ESP32‘ tutorial. We saw how a captive portal can be created using ESP32 to get data from the user as soon as he/she connects to the ESP32’s WiFi field. Now, a common use for the captive portal screen, in the context of ESP32, is to get the WiFi credentials from the user. So here’s the flow the ESP32 will generally follow:

  1. Power ON
  2. Switch to SoftAP mode
  3. Turn on Captive Portal
  4. Get WiFi credentials from the user
  5. Switch to Station mode and connect to WiFi using the available credenitals
  6. Carry on with the other operations

Now, this flow is great when the WiFi credentials are unknown. But what if the WiFi credentials are already known? You’d want to first try to connect to the WiFi using the known credentials, and only if the connection fails, prompt the user to enter the credentials again in the captive portal. Thus, the new flow will be:

  1. Power ON
  2. Check Preferences for available WiFi Credentials. If present, go to Step 3, else Step 5
  3. Switch to Station Mode and try connecting to the WiFi using the credentials
  4. If connection succeeds, go to Step 9, else Step 5
  5. Switch to SoftAP mode
  6. Turn on Captive Portal
  7. Get WiFi credentials from the user and save them to preferences
  8. Switch to Station mode and connect to WiFi using the available credenitals
  9. Carry on with the other operations

Let’s see how the code for this new flow looks like.

GitHub Code

The code can be found on GitHub here.

Brief Code Walkthrough

Much of the code is self-explanatory. You only need to look at the setup to see what’s happening.

void setup() {
  //your other setup stuff...
  Serial.begin(115200);
  Serial.println();
  preferences.begin("my-pref", false);

  is_setup_done = preferences.getBool("is_setup_done", false);
  ssid = preferences.getString("rec_ssid", "Sample_SSID");
  password = preferences.getString("rec_password", "abcdefgh");

  if (!is_setup_done)
  {
    StartCaptivePortal();
  }
  else
  {
    Serial.println("Using saved SSID and Password to attempt WiFi Connection!");
    Serial.print("Saved SSID is ");Serial.println(ssid);
    Serial.print("Saved Password is ");Serial.println(password);
    WiFiStationSetup(ssid, password);
  }

  while (!is_setup_done)
  {
    dnsServer.processNextRequest();
    delay(10);
    if (valid_ssid_received && valid_password_received)
    {
      Serial.println("Attempting WiFi Connection!");
      WiFiStationSetup(ssid, password);
    }
  }

As you can see, the is_setup_done variable controls the flow. If WiFi credentials are available, its value would be true at the time of startup. If the credentials are not available, its value would be false, and the Captive portal would be initiated.

If the WiFi connection using the available credentials fails within a given time, then its value is set to false and the captive portal is started again, to prompt the user to enter newer values for SSID and password. This is handled within the WiFiStationSetup() function.

void WiFiStationSetup(String rec_ssid, String rec_password)
{
  wifi_timeout = false;
  WiFi.mode(WIFI_STA);
  char ssid_arr[20];
  char password_arr[20];
  rec_ssid.toCharArray(ssid_arr, rec_ssid.length() + 1);
  rec_password.toCharArray(password_arr, rec_password.length() + 1);
  Serial.print("Received SSID: "); Serial.println(ssid_arr); Serial.print("And password: "); Serial.println(password_arr);
  WiFi.begin(ssid_arr, password_arr);

  uint32_t t1 = millis();
  while (WiFi.status() != WL_CONNECTED) {
    delay(2000);
    Serial.print(".");
    if (millis() - t1 > 50000) //50 seconds elapsed connecting to WiFi
    {
      Serial.println();
      Serial.println("Timeout connecting to WiFi. The SSID and Password seem incorrect.");
      valid_ssid_received = false;
      valid_password_received = false;
      is_setup_done = false;
      preferences.putBool("is_setup_done", is_setup_done);

      StartCaptivePortal();
      wifi_timeout = true;
      break;
    }
  }
  if (!wifi_timeout)
  {
    is_setup_done = true;
    Serial.println("");  Serial.print("WiFi connected to: "); Serial.println(rec_ssid);
    Serial.print("IP address: ");  Serial.println(WiFi.localIP());
    preferences.putBool("is_setup_done", is_setup_done);
    preferences.putString("rec_ssid", ssid);
    preferences.putString("rec_password", password);
  }
}

As you can see, only if the WiFi connection succeeds, the values of SSID and Password are stored to preferences, for future reference, and is_setup_done is set to true.

Serial Monitor Screenshots

At first setup, with incorrect credentials entered first, and correct credentials entered later:

First Time Setup

Next time after power reset, when it attempts to connect using the saved SSID and Password.

Second Connection Attempt

Further Improvements

The above code is provided only for reference. Here are further improvements that you can take up:

  1. Attempt to connect using known credentials in multiple attempts, instead of just one.
  2. Avoid writing to preferences if the credentials haven’t changed (to avoid wear leveling)
  3. Scan available WiFi networks, and convert the SSID field in the captive portal to a dropdown, where the top 5 networks with highest strength are displayed. Hint: Use the WiFiScan example, along with Display dynamic webpages with ESP32

Let me know in the comments if you need any help with these improvements.


For more tutorials on ESP32, checkout https://iotespresso.com/category/esp32/

Since you are here, you may find this course on Udemy, which explains how to link ESP32 with AWS IoT, to be quite interesting. Do check it out.

31 comments

  1. Hi, thanks a lot for your wonderful sharing!!! Is it possible for you to share a completed sketch that compiles all 3 further improvements you mentioned above? Thanks!

  2. Thanks for your reply, I mean these 3 improvements feature that u mentioned in your last paragraph:
    1. Attempt to connect using known credentials in multiple attempts, instead of just one.
    2. Avoid writing to preferences if the credentials haven’t changed (to avoid wear leveling)
    3. Scan available WiFi networks, and convert the SSID field in the captive portal to a dropdown, where the top 5 networks with highest strength are displayed.
    Any complete sketch that included these? Thanks in advance!

    1. Ohh those 3. Yes, I can provide a complete sketch including these. However, these were meant to be exercises for the readers. Therefore, if you have any trouble implementing any of these features do let me know. I can help you there. If you need this sketch only to compare your code, I’ll share it over email tomorrow.

      1. Thanks, you are awesome!

        Ive been doing some tests and I have a few questions:
        1. If the wifi goes away, the ESP will attempt to reconnect a few times, if it does not find the wifi then it launches the portal again. However, if I reset the ESP and put the wifi back it wont remember it.
        Is the ESP deleting the credentials if it cannot connect?
        I think it would be best to store the credentials indefinitely , so that if the wifi comes back after a few hours or days, the ESP can connect back. This needs the portal to timeout so that it can attempt to look for its wifi again.
        Something like:
        – 1 if wifi there, connect and be happy.
        -2 if wifi gone, try to find it for a few seconds (maybe 60?)
        -3 if wifi found then go to 1, else go to 4.
        -4 if no wifi found after 60 sec, create portal for another 60 seconds.
        -5 if new credentials provided then replace them and go to 1, else go to 6.
        -6 if portal timeout then close portal and go to 2.

        this way, the ESP will be jumping between portal and trying to connect, allowing to update wifi but also to reconnect after a long time offline.
        Im a bit confused with the name and order of the subroutines… any suggestions?

        2. I realized that the wifi scan is performed only once, if the wifi was not found I need to reboot the ESP to do another scan. Is it possible to add a “Re-scan” button to the portal so that we can refresh the list of available wifis? How would you do this?

        3. My sketch is using around 57% of flash only for this portal, do you know what exactly is taking so much space? Is there a way to reduce this? Is this addressed in the following tutorials?:
        ESP32 Captive Portal – Fetching HTML from SPIFFS
        ESP32 Captive Portal – Fetching HTML using LittleFS

        again, thanks a IoT (haha)

        1. Thanks for describing your queries in detail. It is always a pleasure to answer sincere queries. Here is my attempt to answer them:
          1. In the example sketch provided in this article, the credentials are not being deleted. Instead, they are being overwritten if newer credentials are available. However, portal timeout has not been added, and that’s a good suggestion. I’d definitely prefer the back and forth between Captive Portal and connection attempt, rather than just staying in the AP mode indefinitely. You can use timer interrupts to achieve this.

          2. The issue with performing Re-scan is that the ESP32 will have to switch from the AP mode to the Station mode in order to perform the Scan. That will disable the captive portal. If it needs to be implemented based on user interaction, a message should be displayed to the user that the captive portal will be temporarily shut while ESP32 scans the WiFi. An alternative is to keep the WiFi scan routine in the StartCaptivePortal() function so that you can be sure that the WiFi scan was done immediately before the start of the captive portal every time.

          3. The WiFi library is the culprit. A simple sketch with just WiFi.h inclusion would still take about 85% of the memory taken up by the captive portal sketch. There aren’t any straightforward ways of reducing the sketch size that I’m aware of. However, you can increase the flash space reserved for your sketch by adjusting the partitions.

          I hope my answers helped. Let me know if any other help is required.

  3. Thanks,
    1. I will try to figure out a way to store the credentials and do the back and forth between portal and wifi search.
    2. The Autoconnect library is a more complex solution that I have tried but It is not working as intended for me. It does however do a wifi rescan without losing wifi connection between the ESP and the phone or computer. This means there must be a way to do it.

    1. I did some reading. Seems like the ESP32 has a APSTA mode, where the AP and Station modes can coexist. See this. I’m myself yet to experiment with this, but it can be a potential solution to the problem you described: Scanning WiFi while the captive portal is active. Do give it a try yourself as well and let me know if it worked.

      1. yes, I think that is the way. I need to figure out how to insert a button to rescan wifis.
        Also, do you know how to timeout the portal after some seconds so that it goes back to try to connect to wifi?

        1. You can use the form template for the rescan button. Something like the following in the HTML (replace &lt with < below, otherwise it gets rendered as a button in the comment):
          &lt form method=”get” action=”/scan”>
          &lt button type=”submit”>Rescan wifi
          &lt /form>

          Then, within setupServer(), you can handle the /scan. The following will rescan the WiFi, and show the updated webpage to the user


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

          The assumption above is that the WiFi is in WIFI_MODE_APSTA.

  4. This is really a great thread, thank you for sharing in the first place and for keeping the discussion going. Although I don’t have any time at all at the moment I’d love to play around with this code 🙂 Where is the latest version of the definitive code? 🙂

  5. Your explanations are simply fantastic! Would it be possible to get the latest code over e-mail? Many, many thanks in advance!

  6. Hey there! Great tutorial, I have implemented some of your improvements that you mentioned, but I was hoping to see how you did it to compare. Would you be able to send it to my email as well?

  7. Hey Yash! awesome explanation. I tried the exercise, would it be possible to get the latest code over e-mail? Thank You!!!

  8. Hi, I found out that if attempt to connect with an SSID whose naming has a space, it cannot connect with it. How can I solve this problem? Thanks.

      1. Thanks for your reply, but sad to mention that this does not work for me… The received SSID print in Serial Monitor didn’t include the character after space, I think that the WiFi.begin function is trying to connect with this incomplete SSID hence causing failure to connect to it

  9. Hi there, thanks for tutorial, may I ask for full sketch too? I’d like to have a reference for point 3.

    Thanks in advance

Leave a comment

Your email address will not be published.