Hello -
I'm working on building an ESP32 project. Still working on the project, but I know I need to build in some initial functionality. That being:
- Connect to WiFi network
- If connection to WiFi network fails initiate access point with captive portal for user to select SSID and enter password
- Save SSID and password details in ESP32 preferences (non-volatile memory) and restart ESP32.
- Back to step 1.
I've written some code to accomplish these goals, but am now stuck. When I run the program I don't see the ESP32 in my list of SSID's. When I run an example sketch to initiate an access point it works as I would expect, so I assume hardware is OK.
// Step 1 - Begin by telling the compiler which libraries will be used in this program
#include <Arduino.h> // This is the core Arduino library
#include <Wifi.h> // These are used for wifi capability and OTA updates
#include <ArduinoOTA.h>
#include <WiFiClientSecure.h>
#include <ESPAsyncWebServer.h> // These are used for the captive web portal
#include <DNSServer.h>
#include <AsyncTCP.h>
#include <Preferences.h> // This allows access to non-volatile memory to store WiFi provisioning credentials in between restarts.
#include <SPIFFS.h> // This library allows access to files outside of the main.cpp file. Specifically, the HTML page for access point creation.
// Step 2 - Create a section to declare variables that will be used in the program.
Preferences preferences; // Preferences object to save SSID and password
DNSServer dnsServer; //DNSServer object
AsyncWebServer server(80); //AsyncWebServer object for use when connectd to wifi.
String ssidList; // Create SSIDList variable
const char *SSID = "BLANK";
const char *password = "BLANK";
unsigned long previousMillis = 0; // Stores the last time the LED was updated
const long interval = 10000; // Interval at which to blink (milliseconds)
bool ledState = LOW; // Variable to store LED state
int timeout = 0;
bool connected = LOW;
// Step 3 - Define the class `CaptiveRequestHandler` above setup to avoid incomplete type error
class CaptiveRequestHandler : public AsyncWebHandler {
public:
CaptiveRequestHandler() {}
virtual ~CaptiveRequestHandler() {}
bool canHandle(AsyncWebServerRequest *request) {
return true;
}
void handleRequest(AsyncWebServerRequest *request) {
File file = SPIFFS.open("/index.html", "r");
if (!file) {
request->send(404, "text/plain", "File not found");
return;
}
// Serve the HTML file directly from SPIFFS
request->send(SPIFFS, "/index.html", "text/html");
}
};
// Step 4 - Create a section to declare functions that will be used in the program
void blinkNonBlocking(); // Better version of blink, non-blocking
void saveCredentials(const String &ssid, const String &password);
// The setup function runs once when you press reset or power the board.
void setup() {
// Initialize digital pin LED_BUILTIN as an output.
Serial.begin(115200);
pinMode(13, OUTPUT);
// Initialize SPIFFS for document linking (HTML file)
if (!SPIFFS.begin(true)) {
Serial.println("SPIFFS Mount Failed");
return;
}
WiFi.begin(SSID, password);
Serial.println("Now attempting to connect to Wifi");
while (WiFi.status() != WL_CONNECTED && timeout < 30) {
Serial.print("Still not connected to WiFi. ");
delay(500);
timeout++;
Serial.print("The timeout counter is: ");
Serial.print(timeout);
Serial.println(" of 30");
}
Serial.println("Now connected to WiFi, or timed out");
Serial.println("The WiFi.status() value is:");
Serial.println(WiFi.status());
Serial.println("The WL_CONNECTED value is:");
Serial.println(WL_CONNECTED);
if (WiFi.status() == WL_CONNECTED) {
Serial.print("If you're seeing this message, the ESP32 thinks it connected to a network. The IP address is: ");
Serial.println(WiFi.localIP());
connected = true;
ArduinoOTA.begin();
} else {
connected = false;
Serial.println("Connection unsuccessful, attempting to start access point");
// Start access point for captive portal
if (!WiFi.softAP("Klik Me No PassWOrd")) {
log_e("Soft AP creation failed.");
while (1);
}
IPAddress myIP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(myIP);
Serial.println("Server started");
}
// Start DNS server
if (dnsServer.start(53, "*", WiFi.softAPIP())) {
Serial.println("DNS server started successfully");
} else {
Serial.println("Failed to start DNS server");
return; // Exit if DNS server initialization fails
}
// Start the server
server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER);
Serial.println("The access point should have started now");
// Dynamic HTML serving handler for SSID list
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(SPIFFS, "/index.html", "text/html");
});
// Handler for saving credentials from the form
server.on("/save", HTTP_POST, [](AsyncWebServerRequest *request) {
if (request->hasParam("ssid", true) && request->hasParam("password", true)) {
String ssid = request->getParam("ssid", true)->value();
String password = request->getParam("password", true)->value();
saveCredentials(ssid, password); // Call function to save credentials
} else {
request->send(400, "text/plain", "Invalid SSID or password");
}
});
// API to get the list of available SSIDs
server.on("/ssid-list", HTTP_GET, [](AsyncWebServerRequest *request) {
// Scan for available networks
int n = WiFi.scanNetworks();
String json = "[";
for (int i = 0; i < n; ++i) {
if (i > 0) json += ",";
json += "\"" + WiFi.SSID(i) + "\"";
}
json += "]";
request->send(200, "application/json", json);
});
}
// The loop function runs over and over again forever.
void loop() {
// These are actions dependent on the wifi connectivity status.
if (connected) {
ArduinoOTA.handle(); // Handle the OTA updates
} else {
// If a wifi connection was not successful, start the DNS server for use with the access point to redirect users.
dnsServer.processNextRequest();
}
// These are actions independent of the wifi connectivity status.
blinkNonBlocking();
}
// This function causes the output on pin 13 to blink every 5 seconds without locking up the system.
void blinkNonBlocking() {
unsigned long currentMillis = millis(); // Get the current time
// Check if it's time to toggle the LED
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis; // Save the last time you blinked the LED
// Toggle the LED state
ledState = !ledState;
digitalWrite(13, ledState); // Turn the LED on/off
// Output the LED state to the serial monitor
if (ledState) {
Serial.println("Light on");
} else {
Serial.println("Light off");
}
}
}
// Function to save SSID and password
void saveCredentials(const String &ssid, const String &password) {
preferences.begin("wifiCreds", false); // Open preferences
preferences.putString("ssid", ssid); // Save SSID
preferences.putString("password", password); // Save password
preferences.end();
Serial.println("Saved SSID and password, restarting...");
ESP.restart(); // Restart ESP32 to apply the new Wi-Fi settings
}
Any thoughts? I've intentionally mis-spelled the WiFi SSID and password to force the program into launching an access point. These are the serial messages that are printed when I run the program.
Now attempting to connect to Wifi
Still not connected to WiFi. The timeout counter is: 1 of 30
Still not connected to WiFi. The timeout counter is: 2 of 30
**** Messages 3-29 skipped for clarity ****
Still not connected to WiFi. The timeout counter is: 30 of 30
Now connected to WiFi, or timed out
The WiFi.status() value is:
1
The WL_CONNECTED value is:
3
Connection unsuccessful, attempting to start access point
AP IP address: 192.168.4.1
Server started
DNS server started successfully
The access point should have started now
Light on
Light off
Light on
... etc.