Creating a device based on a microcontroller ESP32 inevitably leads the developer to the need to connect to a home network. The problem is obvious: a hard-coded SSID and password make the device inflexible and inconvenient for the end user. The solution is to implement an Access Point mode, which allows any smartphone or laptop to connect to the controller itself for initial setup.
In this article, we'll explore how a Captive Portal works, where a device forcibly redirects browser requests to its own web page. This is an industry standard for IoT devices, eliminating the need for complex USB or Bluetooth connections to transmit network parameters. You'll learn how to create a web server, process POST requests, and store data in non-volatile memory.
To implement our plans, we will need a development environment. Arduino IDE or PlatformIOWe will use standard libraries. WiFi.h And WebServer.h, which are already built into the platform's core. This ensures high compatibility and eliminates the need to use heavy third-party frameworks for simple configuration tasks.
How Captive Portal and DNS Redirection Work
The technology consists of the microcontroller running its own DNS server. When a client device (smartphone) connects to the ESP32's WiFi network, it attempts to check for internet access by sending requests to known domains. Our DNS server intercepts these requests. any requests and returns the IP address of the controller itself.
Upon receiving the response, the user's browser automatically opens the authorization or settings page. This behavior is typical for public access points in cafes and airports. In context IoT This allows the user to simply enter the login and password for their router without knowing the IP address of the device.
⚠️ Note: Modern versions of iOS and Android operating systems are becoming increasingly strict about security. They may mark such pages as "Not Secure Connection" because the SSL certificate is usually missing or self-signed. This is normal for local settings.
The key here is to configure your DNS correctly. Simply setting up a web server may not automatically load the page. You need to use a library. DNSServer.hto intercept domain names. Without this, users would have to manually enter the address 192.168.4.1 into the browser's address bar, which reduces usability.
Technical details of DNS responses
The ESP32's DNS server is typically configured to respond with "DNS Answer" for any request. This means that a request to google.com will return the ESP32's IP address. This behavior is called DNS Hijacking in the context of Captive Portal.
Preparing the project structure and libraries
Before writing code, make sure you have the ESP32 core installed. In the Arduino IDE, this is done through the Additional Board URLs settings menu. We'll need three main libraries: WiFi.h to control the wireless module, WebServer.h to process HTTP requests and DNSServer.h to organize redirection.
It's important to declare global server objects correctly. Creating them inside the setup function can cause them to malfunction or take up unnecessary memory. It's also important to provide a mechanism for storing user-entered data. A library is ideal for this. Preferences.h (analogous to EEPROM), which allows data to be saved even after a power reset.
- 📦 WiFi.h — basic driver for working with the WiFi chip.
- 🌐 WebServer.h — a lightweight HTTP server for processing GET and POST requests.
- 🔄 DNSServer.h — implementation of a simple DNS server for redirecting requests.
- 💾 Preferences.h — work with NVS (Non-Volatile Storage) to save settings.
The sketch structure should be modular. Divide the code into functions: one for setting up the access point, one for processing the form, and one for attempting to connect to the home network. This will make debugging and reading the code easier in the future.
Implementation of web interface and HTML form
The web page the user sees should be lightweight and responsive. There's no point in using heavy CSS frameworks like Bootstrap when inline styles will do. It's best to store HTML code as a raw string literal (escaped string) or in a separate file if using the SPIFFS file system.
The form must submit data using the POST method to prevent the password from appearing in the URL or browser history. Input fields must have the correct types: type="text" for SSID and type="password" for the security key. This will ensure that password characters are hidden when entered on the smartphone screen.
const char* html_form = R"rawliteral(Setting up WiFi
)rawliteral";
Pay attention to the viewport meta tag. It's critical for the form to display correctly on mobile devices. Without it, the page may look like a scaled-down version of the desktop site, forcing the user to constantly pinch and zoom.
POST request processing algorithm and validation
When the user clicks the "Save" button, the browser sends the data to the address /saveIn this route's handler, we need to extract the field values. The WebServer library allows you to do this via the server.arg("name"), where name is the name attribute of the input field.
Before saving data, basic validation must be performed. An empty SSID or a password shorter than 8 characters (WPA2 standard) are meaningless. If the data is incorrect, the server should return an error page or redirect the user so they can correct the input.
- ✅ Check the length of the SSID string (at least 1 character).
- ✅ Password complexity check (minimum 8 characters).
- ✅ Sanitization of input data (protection against injections, although this is less critical for a local device).
After successful validation, the data is written to the NVS. The key here is to immediately reboot the device or switch the Wi-Fi mode. We can't remain in access point mode with the same settings if the goal is to connect to a different network.
⚠️ Important: When writing to NVS (Preferences), remember that the number of write cycles is limited, although large. Don't write data to memory with every request unless necessary. It's best to save only after successful form validation.
WiFi mode switching logic and data storage
After receiving the user's settings, the device should attempt to connect to the specified network. The logic is as follows: first, we disable access point (AP) mode, then initialize station (STA) mode with the new data. If the connection is successful, the device operates normally. If not, it must reactivate the access point.
To implement this logic, it's convenient to use a state machine. The device can be in the following states: CONFIG_MODE (setting mode), CONNECTING (attempt to contact), OPERATIONAL (regular operation). Switching between states occurs in the loop function or by timer.
| State | WiFi Action | WebServer Action | LED indication |
|---|---|---|---|
| CONFIG_MODE | AP (Access Point) | Active (port 80) | Slow blinking |
| CONNECTING | STA (Client) | Turned off | Fast blinking |
| OPERATIONAL | STA (Connected) | Disabled (or API only) | Constant glow |
It's important to consider a timeout. If the device fails to connect to the home network within 20-30 seconds, it should automatically revert to access point mode. Otherwise, you'll end up with a bricked device that's impossible to reconfigure without reflashing the firmware via UART.
☑️ Checking the switching logic
Debugging and Common Mistakes During Deployment
The most common problem is that the device connects to the network, but the page doesn't open. This is almost always the fault of DNS. Make sure that loop() is called dnsServer.processNextRequest()Without this call, domain redirection will not work, and the browser will display a "No Internet" error.
The second problem is the "bootloop." If the save and reset logic is written incorrectly, the ESP32 may enter an infinite reboot loop. For example, if you save a blank password and only check for its presence during boot, but not its length, the device may get stuck in a connection attempt loop.
Use Serial Monitor for debugging. Print connection statuses there: WiFi.status() will return a numeric value that can be deciphered. It's also useful to display the IP address assigned by the access point's DHCP server to understand where to contact.
void printWifiStatus() {Serial.print("SSID: ");
Serial.println(WiFi.SSID());
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
Serial.print("Signal strength (RSSI): ");
Serial.print(WiFi.RSSI());
Serial.println(" dBm");
}
Be mindful of power consumption. Hotspot mode consumes more power than sleep mode. If your device is running on battery power, keep the hotspot on for only a limited time (e.g., 3 minutes after powering on), and then turn off Wi-Fi completely if no settings have been made.
Frequently Asked Questions (FAQ)
How do I make a page open automatically on my iPhone?
iOS requires that the redirect page not contain HTTPS (since there is no certificate) and be of a certain size. Adding a meta tag that prevents caching can also sometimes help. It's impossible to completely guarantee an automatic popup on all iOS versions due to Apple's security policies, but proper DNS and HTTP 302 redirects improve the chances.
Is it possible to password protect a web page?
Yes, you can add basic HTTP authentication to WebServer. However, transmitting this password over an open channel in access point mode is unsafe. It's better to rely on the fact that connecting to the ESP32 WiFi network itself already requires a password (WPA2) if you set one in the access point code.
What should I do if I forgot the password for my ESP32 access point?
Typically, the firmware code includes a "reset button." If you hold the physical button on the board for more than 5 seconds while powering on, the device should clear the NVS (Preferences.clear()) and return to factory settings, enabling the access point with or without the default password.
How many devices can connect to ESP32 AP at the same time?
The theoretical limit of the WiFi protocol allows for connecting up to 10 clients in SoftAP mode on the ESP32. However, given the limited memory and processor time, it is recommended to limit the number of connections to 1-2 devices to ensure stable web server operation.