Skip to content

Commit 57cd712

Browse files
authored
best way to make captive portal 2 (#49)
* add comments for more information and macro * cpp lint correction
1 parent 0d2a0f7 commit 57cd712

File tree

3 files changed

+141
-12
lines changed

3 files changed

+141
-12
lines changed

README.md

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,20 @@ This library is based on the UI from [https://github.com/ayushsharma82/ESPConnec
2020
I highly recommend looking at all OSS projects (and products) from [@ayushsharma82](https://github.com/ayushsharma82).
2121
He is making great Arduino libraries.
2222

23-
- [Features](#features)
24-
- [Usage](#usage)
25-
- [API](#api)
26-
- [Blocking mode](#blocking-mode)
27-
- [Non-blocking mode](#non-blocking-mode)
28-
- [Use an external configuration system](#use-an-external-configuration-system)
29-
- [ESP8266 Specifics](#esp8266-specifics)
30-
- [Ethernet Support](#ethernet-support)
31-
- [Logo](#logo)
32-
- [mDNS](#mdns)
33-
- [Compile Flags](#compile-flags)
23+
- [MycilaESPConnect](#mycilaespconnect)
24+
- [Features](#features)
25+
- [Usage](#usage)
26+
- [API](#api)
27+
- [Blocking mode](#blocking-mode)
28+
- [Non-blocking mode](#non-blocking-mode)
29+
- [Set static IP](#set-static-ip)
30+
- [Use an external configuration system](#use-an-external-configuration-system)
31+
- [ESP8266 Specifics](#esp8266-specifics)
32+
- [Ethernet Support](#ethernet-support)
33+
- [Logo](#logo)
34+
- [Captive Portal Detection Endpoints](#captive-portal-detection-endpoints)
35+
- [mDNS](#mdns)
36+
- [Compile Flags](#compile-flags)
3437

3538
## Features
3639

@@ -235,6 +238,25 @@ You can customize the logo by providing a web handler for `/logo`:
235238
```
236239
237240
If not provided, the logo won't appear in the Captive Portal.
241+
### Captive Portal Detection Endpoints
242+
243+
MycilaESPConnect implements multi-platform captive portal detection by providing specific endpoints that different operating systems use to test internet connectivity. When a device connects to the ESP32's WiFi network, it automatically performs these tests. If the responses don't match expectations (redirections instead of expected content), the system automatically triggers the captive portal interface.
244+
245+
| **Endpoint** | **Operating System** | **Function** | **Action** | **Purpose** |
246+
|---|---|---|---|---|
247+
| `/connecttest.txt` | Microsoft Windows | Windows connectivity test | Redirects to `http://logout.net` | Triggers Windows captive portal detection |
248+
| `/wpad.dat` | All systems | Web Proxy Auto-Discovery Protocol | Returns 404 error | Indicates no proxy configuration is provided |
249+
| `/generate_204` | Android | Android connectivity test | Redirects to WiFi Access Point IP | Triggers captive portal when Android detects no internet |
250+
| `/redirect` | Generic | Generic redirect endpoint | Redirects to captive portal interface | Provides universal redirection endpoint |
251+
| `/hotspot-detect.html` | Apple iOS/macOS | Apple hotspot detection | Redirects to WiFi Access Point IP | Triggers captive portal when Apple devices detect connectivity issues |
252+
| `/canonical.html` | Ubuntu/Linux | Ubuntu/Linux connectivity test | Redirects to captive portal configuration page | Activates captive portal on Linux systems |
253+
| `/success.txt` | Microsoft | Microsoft connectivity success page | Returns 200 OK status | Indicates successful connection for Microsoft tests |
254+
| `/ncsi.txt` | Microsoft | Network Connectivity Status Indicator | Redirects to portal for configuration | Handles Microsoft network connectivity status indicator |
255+
| `/startpage` | Generic | Generic start page | Redirects to main captive portal interface | Provides generic entry point to portal |
256+
257+
**Note**: This functionality can be disabled by setting the compile flag `-D ESPCONNECT_NO_COMPAT_CP`, which will save approximately 2KB of flash memory but may reduce captive portal detection reliability on some devices.
258+
259+
This approach ensures a smooth user experience across all devices without manual intervention, automatically guiding users to the network configuration interface when they connect to the ESP32's access point.
238260
239261
### mDNS
240262
@@ -248,3 +270,5 @@ You can disable it by setting `-D ESPCONNECT_NO_MDNS`.
248270
- `-D ESPCONNECT_NO_CAPTIVE_PORTAL`: disable Captive Portal and ESPAsyncWebServer dependency
249271
- `-D ESPCONNECT_NO_STD_STRING`: use Arduino `String` instead of `std::string`
250272
- `-D ESPCONNECT_NO_LOGGING`: disable logging
273+
- `-D ESPCONNECT_NO_COMPAT_CP`: disable better Captive Portal detection (about 2KB flash)
274+

src/MycilaESPConnect.cpp

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,9 @@ void Mycila::ESPConnect::_startAP() {
670670
WiFi.setSleep(false);
671671
WiFi.persistent(false);
672672
WiFi.setAutoReconnect(false);
673-
WiFi.softAPConfig(IPAddress(192, 168, 4, 1), IPAddress(192, 168, 4, 1), IPAddress(255, 255, 255, 0));
673+
674+
// Configure AP with specific IP range so devices recognize it as a captive portal
675+
WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255, 255, 255, 0));
674676

675677
WiFi.mode(_config.apMode ? WIFI_AP : WIFI_AP_STA);
676678

@@ -793,7 +795,61 @@ void Mycila::ESPConnect::_enableCaptivePortal() {
793795
return _state == Mycila::ESPConnect::State::PORTAL_STARTED;
794796
});
795797
}
798+
#ifndef ESPCONNECT_NO_COMPAT_CP
799+
// Microsoft Windows connectivity check - redirects to logout.net to trigger captive portal detection
800+
if (_connecttestHandler == nullptr)
801+
_connecttestHandler = &_httpd->on("/connecttest.txt", [](AsyncWebServerRequest* request) {
802+
request->redirect("http://logout.net");
803+
});
804+
805+
// Web Proxy Auto-Discovery Protocol - returns 404 as we don't provide proxy configuration
806+
if (_wpadHandler == nullptr)
807+
_wpadHandler = &_httpd->on("/wpad.dat", [](AsyncWebServerRequest* request) {
808+
request->send(404);
809+
});
810+
811+
// Android connectivity check - redirects to captive portal when no internet detected
812+
if (_generate204Handler == nullptr)
813+
_generate204Handler = &_httpd->on("/generate_204", [this](AsyncWebServerRequest* request) {
814+
request->redirect((WiFi.softAPIP().toString()).c_str());
815+
});
816+
817+
// Generic redirect endpoint - forwards to captive portal interface
818+
if (_redirectHandler == nullptr)
819+
_redirectHandler = &_httpd->on("/redirect", [this](AsyncWebServerRequest* request) {
820+
request->redirect((WiFi.softAPIP().toString()).c_str());
821+
});
822+
823+
// Apple iOS/macOS hotspot detection - redirects to captive portal when connectivity test fails
824+
if (_hotspotDetectHandler == nullptr)
825+
_hotspotDetectHandler = &_httpd->on("/hotspot-detect.html", [this](AsyncWebServerRequest* request) {
826+
request->redirect((WiFi.softAPIP().toString()).c_str());
827+
});
828+
829+
// Ubuntu/Linux connectivity check - redirects to captive portal configuration page
830+
if (_canonicalHandler == nullptr)
831+
_canonicalHandler = &_httpd->on("/canonical.html", [this](AsyncWebServerRequest* request) {
832+
request->redirect((WiFi.softAPIP().toString()).c_str());
833+
});
834+
835+
// Microsoft connectivity test success page - returns 200 OK to indicate successful connection
836+
if (_successHandler == nullptr)
837+
_successHandler = &_httpd->on("/success.txt", [](AsyncWebServerRequest* request) {
838+
request->send(200);
839+
});
796840

841+
// Microsoft Network Connectivity Status Indicator - redirects to portal for configuration
842+
if (_ncsiHandler == nullptr)
843+
_ncsiHandler = &_httpd->on("/ncsi.txt", [this](AsyncWebServerRequest* request) {
844+
request->redirect((WiFi.softAPIP().toString()).c_str());
845+
});
846+
847+
// Generic start page endpoint - redirects users to the main captive portal interface
848+
if (_startpageHandler == nullptr)
849+
_startpageHandler = &_httpd->on("/startpage", [this](AsyncWebServerRequest* request) {
850+
request->redirect((WiFi.softAPIP().toString()).c_str());
851+
});
852+
#endif
797853
_httpd->onNotFound([](AsyncWebServerRequest* request) {
798854
AsyncWebServerResponse* response = request->beginResponse(200, "text/html", ESPCONNECT_HTML, sizeof(ESPCONNECT_HTML));
799855
response->addHeader("Content-Encoding", "gzip");
@@ -838,6 +894,44 @@ void Mycila::ESPConnect::_disableCaptivePortal() {
838894
_httpd->removeHandler(_homeHandler);
839895
_homeHandler = nullptr;
840896
}
897+
#ifndef ESPCONNECT_NO_COMPAT_CP
898+
if (_connecttestHandler != nullptr) {
899+
_httpd->removeHandler(_connecttestHandler);
900+
_connecttestHandler = nullptr;
901+
}
902+
if (_wpadHandler != nullptr) {
903+
_httpd->removeHandler(_wpadHandler);
904+
_wpadHandler = nullptr;
905+
}
906+
if (_generate204Handler != nullptr) {
907+
_httpd->removeHandler(_generate204Handler);
908+
_generate204Handler = nullptr;
909+
}
910+
if (_redirectHandler != nullptr) {
911+
_httpd->removeHandler(_redirectHandler);
912+
_redirectHandler = nullptr;
913+
}
914+
if (_hotspotDetectHandler != nullptr) {
915+
_httpd->removeHandler(_hotspotDetectHandler);
916+
_hotspotDetectHandler = nullptr;
917+
}
918+
if (_canonicalHandler != nullptr) {
919+
_httpd->removeHandler(_canonicalHandler);
920+
_canonicalHandler = nullptr;
921+
}
922+
if (_successHandler != nullptr) {
923+
_httpd->removeHandler(_successHandler);
924+
_successHandler = nullptr;
925+
}
926+
if (_ncsiHandler != nullptr) {
927+
_httpd->removeHandler(_ncsiHandler);
928+
_ncsiHandler = nullptr;
929+
}
930+
if (_startpageHandler != nullptr) {
931+
_httpd->removeHandler(_startpageHandler);
932+
_startpageHandler = nullptr;
933+
}
934+
#endif
841935
#endif
842936
}
843937

src/MycilaESPConnect.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,17 @@ namespace Mycila {
241241
AsyncCallbackWebHandler* _scanHandler = nullptr;
242242
AsyncCallbackWebHandler* _connectHandler = nullptr;
243243
AsyncCallbackWebHandler* _homeHandler = nullptr;
244+
#ifndef ESPCONNECT_NO_COMPAT_CP
245+
AsyncCallbackWebHandler* _connecttestHandler = nullptr;
246+
AsyncCallbackWebHandler* _wpadHandler = nullptr;
247+
AsyncCallbackWebHandler* _generate204Handler = nullptr;
248+
AsyncCallbackWebHandler* _redirectHandler = nullptr;
249+
AsyncCallbackWebHandler* _hotspotDetectHandler = nullptr;
250+
AsyncCallbackWebHandler* _canonicalHandler = nullptr;
251+
AsyncCallbackWebHandler* _successHandler = nullptr;
252+
AsyncCallbackWebHandler* _ncsiHandler = nullptr;
253+
AsyncCallbackWebHandler* _startpageHandler = nullptr;
254+
#endif
244255
#endif
245256
State _state = State::NETWORK_DISABLED;
246257
StateCallback _callback = nullptr;

0 commit comments

Comments
 (0)