Skip to content

Commit 0bb4fb2

Browse files
aalkuigrr
authored andcommitted
Better captive portal example.
This example serves a "hello world" on a WLAN and a SoftAP at the same time. The SoftAP allow you to configure WLAN parameters at run time. They are not setup in the sketch but saved on EEPROM. This is a captive portal because through the softAP it will redirect any http request to http://192.168.4.1/, served by the ESP8266 itself
1 parent 5a91c66 commit 0bb4fb2

File tree

5 files changed

+312
-100
lines changed

5 files changed

+312
-100
lines changed

libraries/DNSServer/examples/CaptivePortal2/CaptivePortal2.ino

Lines changed: 0 additions & 100 deletions
This file was deleted.
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#include <ESP8266WiFi.h>
2+
#include <WiFiClient.h>
3+
#include <ESP8266WebServer.h>
4+
#include <DNSServer.h>
5+
#include <ESP8266mDNS.h>
6+
#include <EEPROM.h>
7+
8+
/*
9+
* This example serves a "hello world" on a WLAN and a SoftAP at the same time.
10+
* The SoftAP allow you to configure WLAN parameters at run time. They are not setup in the sketch but saved on EEPROM.
11+
*
12+
* Connect your computer or cell phone to wifi network ESP_ap with password 12345678. A popup may appear and it allow you to go to WLAN config. If it does not then navigate to http://192.168.4.1/wifi and config it there.
13+
* Then wait for the module to connect to your wifi and take note of the WLAN IP it got. Then you can disconnect from ESP_ap and return to your regular WLAN.
14+
*
15+
* Now the ESP8266 is in your network. You can reach it through http://192.168.x.x/ (the IP you took note of) or maybe at http://esp8266.local too.
16+
*
17+
* This is a captive portal because through the softAP it will redirect any http request to http://192.168.4.1/
18+
*/
19+
20+
/* Set these to your desired softAP credentials. They are not configurable at runtime */
21+
const char *softAP_ssid = "ESP_ap";
22+
const char *softAP_password = "12345678";
23+
24+
/* hostname for mDNS. Should work at least on windows. Try http://esp8266.local */
25+
const char *myHostname = "esp8266";
26+
27+
/* Don't set this wifi credentials. They are configurated at runtime and stored on EEPROM */
28+
char ssid[32] = "";
29+
char password[32] = "";
30+
31+
// DNS server
32+
const byte DNS_PORT = 53;
33+
DNSServer dnsServer;
34+
35+
// Web server
36+
ESP8266WebServer server(80);
37+
38+
/* Soft AP network parameters */
39+
IPAddress apIP(192, 168, 4, 1);
40+
IPAddress netMsk(255, 255, 255, 0);
41+
42+
43+
/** Should I connect to WLAN asap? */
44+
boolean connect;
45+
46+
/** Last time I tried to connect to WLAN */
47+
long lastConnectTry = 0;
48+
49+
/** Current WLAN status */
50+
int status = WL_IDLE_STATUS;
51+
52+
void setup() {
53+
delay(1000);
54+
Serial.begin(9600);
55+
Serial.println();
56+
Serial.print("Configuring access point...");
57+
/* You can remove the password parameter if you want the AP to be open. */
58+
WiFi.softAPConfig(apIP, apIP, netMsk);
59+
WiFi.softAP(softAP_ssid, softAP_password);
60+
delay(500); // Without delay I've seen the IP address blank
61+
Serial.print("AP IP address: ");
62+
Serial.println(WiFi.softAPIP());
63+
64+
/* Setup the DNS server redirecting all the domains to the apIP */
65+
dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
66+
dnsServer.start(DNS_PORT, "*", apIP);
67+
68+
/* Setup web pages: root, wifi config pages, SO captive portal detectors and not found. */
69+
server.on("/", handleRoot);
70+
server.on("/wifi", handleWifi);
71+
server.on("/wifisave", handleWifiSave);
72+
server.on("/generate_204", handleRoot); //Android captive portal. Maybe not needed. Might be handled by notFound handler.
73+
server.on("/fwlink", handleRoot); //Microsoft captive portal. Maybe not needed. Might be handled by notFound handler.
74+
server.onNotFound ( handleNotFound );
75+
server.begin(); // Web server start
76+
Serial.println("HTTP server started");
77+
loadCredentials(); // Load WLAN credentials from network
78+
connect = strlen(ssid) > 0; // Request WLAN connect if there is a SSID
79+
}
80+
81+
void connectWifi() {
82+
Serial.println("Connecting as wifi client...");
83+
WiFi.disconnect();
84+
WiFi.begin ( ssid, password );
85+
int connRes = WiFi.waitForConnectResult();
86+
Serial.print ( "connRes: " );
87+
Serial.println ( connRes );
88+
}
89+
90+
void loop() {
91+
if (connect) {
92+
Serial.println ( "Connect requested" );
93+
connect = false;
94+
connectWifi();
95+
lastConnectTry = millis();
96+
}
97+
{
98+
int s = WiFi.status();
99+
if (s == 0 && millis() > (lastConnectTry + 60000) ) {
100+
/* If WLAN disconnected and idle try to connect */
101+
/* Don't set retry time too low as retry interfere the softAP operation */
102+
connect = true;
103+
}
104+
if (status != s) { // WLAN status change
105+
Serial.print ( "Status: " );
106+
Serial.println ( s );
107+
status = s;
108+
if (s == WL_CONNECTED) {
109+
/* Just connected to WLAN */
110+
Serial.println ( "" );
111+
Serial.print ( "Connected to " );
112+
Serial.println ( ssid );
113+
Serial.print ( "IP address: " );
114+
Serial.println ( WiFi.localIP() );
115+
116+
// Setup MDNS responder
117+
if (!MDNS.begin(myHostname)) {
118+
Serial.println("Error setting up MDNS responder!");
119+
} else {
120+
Serial.println("mDNS responder started");
121+
// Add service to MDNS-SD
122+
MDNS.addService("http", "tcp", 80);
123+
}
124+
} else if (s == WL_NO_SSID_AVAIL) {
125+
WiFi.disconnect();
126+
}
127+
}
128+
}
129+
// Do work:
130+
//DNS
131+
dnsServer.processNextRequest();
132+
//HTTP
133+
server.handleClient();
134+
}
135+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/** Load WLAN credentials from EEPROM */
2+
void loadCredentials() {
3+
EEPROM.begin(512);
4+
EEPROM.get(0, ssid);
5+
EEPROM.get(0+sizeof(ssid), password);
6+
char ok[2+1];
7+
EEPROM.get(0+sizeof(ssid)+sizeof(password), ok);
8+
EEPROM.end();
9+
if (String(ok) != String("OK")) {
10+
ssid[0] = 0;
11+
password[0] = 0;
12+
}
13+
Serial.println("Recovered credentials:");
14+
Serial.println(ssid);
15+
Serial.println(strlen(password)>0?"********":"<no password>");
16+
}
17+
18+
/** Store WLAN credentials to EEPROM */
19+
void saveCredentials() {
20+
EEPROM.begin(512);
21+
EEPROM.put(0, ssid);
22+
EEPROM.put(0+sizeof(ssid), password);
23+
char ok[2+1] = "OK";
24+
EEPROM.put(0+sizeof(ssid)+sizeof(password), ok);
25+
EEPROM.commit();
26+
EEPROM.end();
27+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/** Handle root or redirect to captive portal */
2+
void handleRoot() {
3+
if (captivePortal()) { // If caprive portal redirect instead of displaying the page.
4+
return;
5+
}
6+
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
7+
server.sendHeader("Pragma", "no-cache");
8+
server.sendHeader("Expires", "-1");
9+
server.send(200, "text/html", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
10+
server.sendContent(
11+
"<html><head></head><body>"
12+
"<h1>HELLO WORLD!!</h1>"
13+
);
14+
if (server.client().localIP() == apIP) {
15+
server.sendContent(String("<p>You are connected through the soft AP: ") + softAP_ssid + "</p>");
16+
} else {
17+
server.sendContent(String("<p>You are connected through the wifi network: ") + ssid + "</p>");
18+
}
19+
server.sendContent(
20+
"<p>You may want to <a href='/wifi'>config the wifi connection</a>.</p>"
21+
"</body></html>"
22+
);
23+
server.client().stop(); // Stop is needed because we sent no content length
24+
}
25+
26+
/** Redirect to captive portal if we got a request for another domain. Return true in that case so the page handler do not try to handle the request again. */
27+
boolean captivePortal() {
28+
if (!isIp(server.hostHeader()) && server.hostHeader() != (String(myHostname)+".local")) {
29+
Serial.print("Request redirected to captive portal");
30+
server.sendHeader("Location", String("http://") + toStringIp(server.client().localIP()), true);
31+
server.send ( 302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
32+
server.client().stop(); // Stop is needed because we sent no content length
33+
return true;
34+
}
35+
return false;
36+
}
37+
38+
/** Wifi config page handler */
39+
void handleWifi() {
40+
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
41+
server.sendHeader("Pragma", "no-cache");
42+
server.sendHeader("Expires", "-1");
43+
server.send(200, "text/html", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
44+
server.sendContent(
45+
"<html><head></head><body>"
46+
"<h1>Wifi config</h1>"
47+
);
48+
if (server.client().localIP() == apIP) {
49+
server.sendContent(String("<p>You are connected through the soft AP: ") + softAP_ssid + "</p>");
50+
} else {
51+
server.sendContent(String("<p>You are connected through the wifi network: ") + ssid + "</p>");
52+
}
53+
server.sendContent(
54+
"\r\n<br />"
55+
"<table><tr><th align='left'>SoftAP config</th></tr>"
56+
);
57+
server.sendContent(String() + "<tr><td>SSID " + String(softAP_ssid) + "</td></tr>");
58+
server.sendContent(String() + "<tr><td>IP " + toStringIp(WiFi.softAPIP()) + "</td></tr>");
59+
server.sendContent(
60+
"</table>"
61+
"\r\n<br />"
62+
"<table><tr><th align='left'>WLAN config</th></tr>"
63+
);
64+
server.sendContent(String() + "<tr><td>SSID " + String(ssid) + "</td></tr>");
65+
server.sendContent(String() + "<tr><td>IP " + toStringIp(WiFi.localIP()) + "</td></tr>");
66+
server.sendContent(
67+
"</table>"
68+
"\r\n<br />"
69+
"<table><tr><th align='left'>WLAN list (refresh if any missing)</th></tr>"
70+
);
71+
Serial.println("scan start");
72+
int n = WiFi.scanNetworks();
73+
Serial.println("scan done");
74+
if (n > 0) {
75+
for (int i = 0; i < n; i++) {
76+
server.sendContent(String() + "\r\n<tr><td>SSID " + String(WiFi.SSID(i)) + String((WiFi.encryptionType(i) == ENC_TYPE_NONE)?" ":" *") + " (" + WiFi.RSSI(i) + ")</td></tr>");
77+
}
78+
} else {
79+
server.sendContent(String() + "<tr><td>No WLAN found</td></tr>");
80+
}
81+
server.sendContent(
82+
"</table>"
83+
"\r\n<br /><form method='POST' action='wifisave'><h4>Connect to network:</h4>"
84+
"<input type='text' placeholder='network' name='n'/>"
85+
"<br /><input type='password' placeholder='password' name='p'/>"
86+
"<br /><input type='submit' value='Connect/Disconnect'/></form>"
87+
"<p>You may want to <a href='/'>return to the home page</a>.</p>"
88+
"</body></html>"
89+
);
90+
server.client().stop(); // Stop is needed because we sent no content length
91+
}
92+
93+
/** Handle the WLAN save form and redirect to WLAN config page again */
94+
void handleWifiSave() {
95+
Serial.println("wifi save");
96+
server.arg("n").toCharArray(ssid, sizeof(ssid) - 1);
97+
server.arg("p").toCharArray(password, sizeof(password) - 1);
98+
server.sendHeader("Location", "wifi", true);
99+
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
100+
server.sendHeader("Pragma", "no-cache");
101+
server.sendHeader("Expires", "-1");
102+
server.send ( 302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
103+
server.client().stop(); // Stop is needed because we sent no content length
104+
saveCredentials();
105+
connect = strlen(ssid) > 0; // Request WLAN connect with new credentials if there is a SSID
106+
}
107+
108+
void handleNotFound() {
109+
if (captivePortal()) { // If caprive portal redirect instead of displaying the error page.
110+
return;
111+
}
112+
String message = "File Not Found\n\n";
113+
message += "URI: ";
114+
message += server.uri();
115+
message += "\nMethod: ";
116+
message += ( server.method() == HTTP_GET ) ? "GET" : "POST";
117+
message += "\nArguments: ";
118+
message += server.args();
119+
message += "\n";
120+
121+
for ( uint8_t i = 0; i < server.args(); i++ ) {
122+
message += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n";
123+
}
124+
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
125+
server.sendHeader("Pragma", "no-cache");
126+
server.sendHeader("Expires", "-1");
127+
server.send ( 404, "text/plain", message );
128+
}
129+

0 commit comments

Comments
 (0)