mirror of
https://github.com/esp8266/Arduino.git
synced 2025-04-19 23:22:16 +03:00
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
This commit is contained in:
parent
5a91c66615
commit
0bb4fb2886
@ -1,100 +0,0 @@
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <DNSServer.h>
|
||||
|
||||
const char* ssid = "esp8266";
|
||||
boolean LEDstate[] = {LOW, false, LOW};
|
||||
|
||||
const char* html = "<html><head><title>Success</title><style>.bt{display:block;width:250px;height:100px;padding:10px;margin:10px;"
|
||||
"text-align:center;border-radius:5px;color:white;font-weight:bold;font-size:70px;text-decoration:none;} "
|
||||
"body{background:#000;} .r{background:#933;} .g{background:#363;} .y{background:#EE0;height:100px;"
|
||||
"width:100px;border-radius:50px;} .b{background:#000;height:100px;width:100px;border-radius:50px;} "
|
||||
".a{font-size:35px;} td{vertical-align:middle;}</style>"
|
||||
"</head><body><table><tr><td><div class='TGT0'></div></td><td><a class='bt g' href='/L0?v=1'>ON</a></td>"
|
||||
"<td><a class='bt r' href='/L0?v=0'>OFF</a></td></tr><tr><td><div class='TGT2'></div></td><td>"
|
||||
"<a class='bt g' href='/L2?v=1'>ON</a></td><td><a class='bt r' href='/L2?v=0'>OFF</a></td></tr>"
|
||||
"<tr><td> </td><td><a class='bt g a' href='/ALL?v=1'><br/>ALL ON</a></td><td>"
|
||||
"<a class='bt r a' href='/ALL?v=0'><br/>ALL OFF</a></td></tr></body></html>";
|
||||
|
||||
const byte DNS_PORT = 53;
|
||||
IPAddress apIP(192, 168, 1, 1);
|
||||
IPAddress netMsk(255, 255, 255, 0);
|
||||
DNSServer dnsServer;
|
||||
ESP8266WebServer server(80);
|
||||
|
||||
void setup() {
|
||||
pinMode(0, OUTPUT);
|
||||
pinMode(2, OUTPUT);
|
||||
digitalWrite(2, LEDstate[0]);
|
||||
digitalWrite(2, LEDstate[2]);
|
||||
Serial.begin(115200);
|
||||
Serial.setDebugOutput(true);
|
||||
WiFi.mode(WIFI_AP);
|
||||
WiFi.softAPConfig(apIP, apIP, netMsk);
|
||||
WiFi.softAP(ssid);
|
||||
Serial.print("SSID: ");
|
||||
Serial.println(ssid);
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.softAPIP());
|
||||
dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
|
||||
dnsServer.start(DNS_PORT, "*", apIP);
|
||||
Serial.println("USP Server started");
|
||||
server.on("/", handle_root);
|
||||
server.on("/generate_204", handle_root); //Android captive portal
|
||||
server.on("/L0", handle_L0);
|
||||
server.on("/L2", handle_L2);
|
||||
server.on("/ALL", handle_ALL);
|
||||
server.onNotFound(handleNotFound);
|
||||
server.begin();
|
||||
Serial.println("HTTP server started");
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
dnsServer.processNextRequest();
|
||||
server.handleClient();
|
||||
}
|
||||
|
||||
void handleNotFound() {
|
||||
Serial.print("\t\t\t\t URI Not Found: ");
|
||||
Serial.println(server.uri());
|
||||
server.send ( 200, "text/plain", "URI Not Found" );
|
||||
}
|
||||
|
||||
void handle_root() {
|
||||
Serial.println("Page served");
|
||||
String toSend = html;
|
||||
toSend.replace("TGT0", LEDstate[0] ? "y" : "b");
|
||||
toSend.replace("TGT2", LEDstate[2] ? "y" : "b");
|
||||
server.send(200, "text/html", toSend);
|
||||
delay(100);
|
||||
}
|
||||
|
||||
void handle_L0() {
|
||||
change_states(0);
|
||||
handle_root();
|
||||
}
|
||||
|
||||
void handle_L2() {
|
||||
change_states(2);
|
||||
handle_root();
|
||||
}
|
||||
|
||||
void handle_ALL() {
|
||||
change_states(0);
|
||||
change_states(2);
|
||||
handle_root();
|
||||
}
|
||||
|
||||
void change_states(int tgt) {
|
||||
if (server.hasArg("v")) {
|
||||
int state = server.arg("v").toInt() == 1;
|
||||
Serial.print("LED");
|
||||
Serial.print(tgt);
|
||||
Serial.print("=");
|
||||
Serial.println(state);
|
||||
LEDstate[tgt] = state ? HIGH : LOW;
|
||||
digitalWrite(tgt, LEDstate[tgt]);
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <DNSServer.h>
|
||||
#include <ESP8266mDNS.h>
|
||||
#include <EEPROM.h>
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This is a captive portal because through the softAP it will redirect any http request to http://192.168.4.1/
|
||||
*/
|
||||
|
||||
/* Set these to your desired softAP credentials. They are not configurable at runtime */
|
||||
const char *softAP_ssid = "ESP_ap";
|
||||
const char *softAP_password = "12345678";
|
||||
|
||||
/* hostname for mDNS. Should work at least on windows. Try http://esp8266.local */
|
||||
const char *myHostname = "esp8266";
|
||||
|
||||
/* Don't set this wifi credentials. They are configurated at runtime and stored on EEPROM */
|
||||
char ssid[32] = "";
|
||||
char password[32] = "";
|
||||
|
||||
// DNS server
|
||||
const byte DNS_PORT = 53;
|
||||
DNSServer dnsServer;
|
||||
|
||||
// Web server
|
||||
ESP8266WebServer server(80);
|
||||
|
||||
/* Soft AP network parameters */
|
||||
IPAddress apIP(192, 168, 4, 1);
|
||||
IPAddress netMsk(255, 255, 255, 0);
|
||||
|
||||
|
||||
/** Should I connect to WLAN asap? */
|
||||
boolean connect;
|
||||
|
||||
/** Last time I tried to connect to WLAN */
|
||||
long lastConnectTry = 0;
|
||||
|
||||
/** Current WLAN status */
|
||||
int status = WL_IDLE_STATUS;
|
||||
|
||||
void setup() {
|
||||
delay(1000);
|
||||
Serial.begin(9600);
|
||||
Serial.println();
|
||||
Serial.print("Configuring access point...");
|
||||
/* You can remove the password parameter if you want the AP to be open. */
|
||||
WiFi.softAPConfig(apIP, apIP, netMsk);
|
||||
WiFi.softAP(softAP_ssid, softAP_password);
|
||||
delay(500); // Without delay I've seen the IP address blank
|
||||
Serial.print("AP IP address: ");
|
||||
Serial.println(WiFi.softAPIP());
|
||||
|
||||
/* Setup the DNS server redirecting all the domains to the apIP */
|
||||
dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
|
||||
dnsServer.start(DNS_PORT, "*", apIP);
|
||||
|
||||
/* Setup web pages: root, wifi config pages, SO captive portal detectors and not found. */
|
||||
server.on("/", handleRoot);
|
||||
server.on("/wifi", handleWifi);
|
||||
server.on("/wifisave", handleWifiSave);
|
||||
server.on("/generate_204", handleRoot); //Android captive portal. Maybe not needed. Might be handled by notFound handler.
|
||||
server.on("/fwlink", handleRoot); //Microsoft captive portal. Maybe not needed. Might be handled by notFound handler.
|
||||
server.onNotFound ( handleNotFound );
|
||||
server.begin(); // Web server start
|
||||
Serial.println("HTTP server started");
|
||||
loadCredentials(); // Load WLAN credentials from network
|
||||
connect = strlen(ssid) > 0; // Request WLAN connect if there is a SSID
|
||||
}
|
||||
|
||||
void connectWifi() {
|
||||
Serial.println("Connecting as wifi client...");
|
||||
WiFi.disconnect();
|
||||
WiFi.begin ( ssid, password );
|
||||
int connRes = WiFi.waitForConnectResult();
|
||||
Serial.print ( "connRes: " );
|
||||
Serial.println ( connRes );
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (connect) {
|
||||
Serial.println ( "Connect requested" );
|
||||
connect = false;
|
||||
connectWifi();
|
||||
lastConnectTry = millis();
|
||||
}
|
||||
{
|
||||
int s = WiFi.status();
|
||||
if (s == 0 && millis() > (lastConnectTry + 60000) ) {
|
||||
/* If WLAN disconnected and idle try to connect */
|
||||
/* Don't set retry time too low as retry interfere the softAP operation */
|
||||
connect = true;
|
||||
}
|
||||
if (status != s) { // WLAN status change
|
||||
Serial.print ( "Status: " );
|
||||
Serial.println ( s );
|
||||
status = s;
|
||||
if (s == WL_CONNECTED) {
|
||||
/* Just connected to WLAN */
|
||||
Serial.println ( "" );
|
||||
Serial.print ( "Connected to " );
|
||||
Serial.println ( ssid );
|
||||
Serial.print ( "IP address: " );
|
||||
Serial.println ( WiFi.localIP() );
|
||||
|
||||
// Setup MDNS responder
|
||||
if (!MDNS.begin(myHostname)) {
|
||||
Serial.println("Error setting up MDNS responder!");
|
||||
} else {
|
||||
Serial.println("mDNS responder started");
|
||||
// Add service to MDNS-SD
|
||||
MDNS.addService("http", "tcp", 80);
|
||||
}
|
||||
} else if (s == WL_NO_SSID_AVAIL) {
|
||||
WiFi.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Do work:
|
||||
//DNS
|
||||
dnsServer.processNextRequest();
|
||||
//HTTP
|
||||
server.handleClient();
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
/** Load WLAN credentials from EEPROM */
|
||||
void loadCredentials() {
|
||||
EEPROM.begin(512);
|
||||
EEPROM.get(0, ssid);
|
||||
EEPROM.get(0+sizeof(ssid), password);
|
||||
char ok[2+1];
|
||||
EEPROM.get(0+sizeof(ssid)+sizeof(password), ok);
|
||||
EEPROM.end();
|
||||
if (String(ok) != String("OK")) {
|
||||
ssid[0] = 0;
|
||||
password[0] = 0;
|
||||
}
|
||||
Serial.println("Recovered credentials:");
|
||||
Serial.println(ssid);
|
||||
Serial.println(strlen(password)>0?"********":"<no password>");
|
||||
}
|
||||
|
||||
/** Store WLAN credentials to EEPROM */
|
||||
void saveCredentials() {
|
||||
EEPROM.begin(512);
|
||||
EEPROM.put(0, ssid);
|
||||
EEPROM.put(0+sizeof(ssid), password);
|
||||
char ok[2+1] = "OK";
|
||||
EEPROM.put(0+sizeof(ssid)+sizeof(password), ok);
|
||||
EEPROM.commit();
|
||||
EEPROM.end();
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
/** Handle root or redirect to captive portal */
|
||||
void handleRoot() {
|
||||
if (captivePortal()) { // If caprive portal redirect instead of displaying the page.
|
||||
return;
|
||||
}
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server.sendHeader("Pragma", "no-cache");
|
||||
server.sendHeader("Expires", "-1");
|
||||
server.send(200, "text/html", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
|
||||
server.sendContent(
|
||||
"<html><head></head><body>"
|
||||
"<h1>HELLO WORLD!!</h1>"
|
||||
);
|
||||
if (server.client().localIP() == apIP) {
|
||||
server.sendContent(String("<p>You are connected through the soft AP: ") + softAP_ssid + "</p>");
|
||||
} else {
|
||||
server.sendContent(String("<p>You are connected through the wifi network: ") + ssid + "</p>");
|
||||
}
|
||||
server.sendContent(
|
||||
"<p>You may want to <a href='/wifi'>config the wifi connection</a>.</p>"
|
||||
"</body></html>"
|
||||
);
|
||||
server.client().stop(); // Stop is needed because we sent no content length
|
||||
}
|
||||
|
||||
/** 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. */
|
||||
boolean captivePortal() {
|
||||
if (!isIp(server.hostHeader()) && server.hostHeader() != (String(myHostname)+".local")) {
|
||||
Serial.print("Request redirected to captive portal");
|
||||
server.sendHeader("Location", String("http://") + toStringIp(server.client().localIP()), true);
|
||||
server.send ( 302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
|
||||
server.client().stop(); // Stop is needed because we sent no content length
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Wifi config page handler */
|
||||
void handleWifi() {
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server.sendHeader("Pragma", "no-cache");
|
||||
server.sendHeader("Expires", "-1");
|
||||
server.send(200, "text/html", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
|
||||
server.sendContent(
|
||||
"<html><head></head><body>"
|
||||
"<h1>Wifi config</h1>"
|
||||
);
|
||||
if (server.client().localIP() == apIP) {
|
||||
server.sendContent(String("<p>You are connected through the soft AP: ") + softAP_ssid + "</p>");
|
||||
} else {
|
||||
server.sendContent(String("<p>You are connected through the wifi network: ") + ssid + "</p>");
|
||||
}
|
||||
server.sendContent(
|
||||
"\r\n<br />"
|
||||
"<table><tr><th align='left'>SoftAP config</th></tr>"
|
||||
);
|
||||
server.sendContent(String() + "<tr><td>SSID " + String(softAP_ssid) + "</td></tr>");
|
||||
server.sendContent(String() + "<tr><td>IP " + toStringIp(WiFi.softAPIP()) + "</td></tr>");
|
||||
server.sendContent(
|
||||
"</table>"
|
||||
"\r\n<br />"
|
||||
"<table><tr><th align='left'>WLAN config</th></tr>"
|
||||
);
|
||||
server.sendContent(String() + "<tr><td>SSID " + String(ssid) + "</td></tr>");
|
||||
server.sendContent(String() + "<tr><td>IP " + toStringIp(WiFi.localIP()) + "</td></tr>");
|
||||
server.sendContent(
|
||||
"</table>"
|
||||
"\r\n<br />"
|
||||
"<table><tr><th align='left'>WLAN list (refresh if any missing)</th></tr>"
|
||||
);
|
||||
Serial.println("scan start");
|
||||
int n = WiFi.scanNetworks();
|
||||
Serial.println("scan done");
|
||||
if (n > 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
server.sendContent(String() + "\r\n<tr><td>SSID " + String(WiFi.SSID(i)) + String((WiFi.encryptionType(i) == ENC_TYPE_NONE)?" ":" *") + " (" + WiFi.RSSI(i) + ")</td></tr>");
|
||||
}
|
||||
} else {
|
||||
server.sendContent(String() + "<tr><td>No WLAN found</td></tr>");
|
||||
}
|
||||
server.sendContent(
|
||||
"</table>"
|
||||
"\r\n<br /><form method='POST' action='wifisave'><h4>Connect to network:</h4>"
|
||||
"<input type='text' placeholder='network' name='n'/>"
|
||||
"<br /><input type='password' placeholder='password' name='p'/>"
|
||||
"<br /><input type='submit' value='Connect/Disconnect'/></form>"
|
||||
"<p>You may want to <a href='/'>return to the home page</a>.</p>"
|
||||
"</body></html>"
|
||||
);
|
||||
server.client().stop(); // Stop is needed because we sent no content length
|
||||
}
|
||||
|
||||
/** Handle the WLAN save form and redirect to WLAN config page again */
|
||||
void handleWifiSave() {
|
||||
Serial.println("wifi save");
|
||||
server.arg("n").toCharArray(ssid, sizeof(ssid) - 1);
|
||||
server.arg("p").toCharArray(password, sizeof(password) - 1);
|
||||
server.sendHeader("Location", "wifi", true);
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server.sendHeader("Pragma", "no-cache");
|
||||
server.sendHeader("Expires", "-1");
|
||||
server.send ( 302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
|
||||
server.client().stop(); // Stop is needed because we sent no content length
|
||||
saveCredentials();
|
||||
connect = strlen(ssid) > 0; // Request WLAN connect with new credentials if there is a SSID
|
||||
}
|
||||
|
||||
void handleNotFound() {
|
||||
if (captivePortal()) { // If caprive portal redirect instead of displaying the error page.
|
||||
return;
|
||||
}
|
||||
String message = "File Not Found\n\n";
|
||||
message += "URI: ";
|
||||
message += server.uri();
|
||||
message += "\nMethod: ";
|
||||
message += ( server.method() == HTTP_GET ) ? "GET" : "POST";
|
||||
message += "\nArguments: ";
|
||||
message += server.args();
|
||||
message += "\n";
|
||||
|
||||
for ( uint8_t i = 0; i < server.args(); i++ ) {
|
||||
message += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n";
|
||||
}
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server.sendHeader("Pragma", "no-cache");
|
||||
server.sendHeader("Expires", "-1");
|
||||
server.send ( 404, "text/plain", message );
|
||||
}
|
||||
|
21
libraries/DNSServer/examples/CaptivePortalAdvanced/tools.ino
Normal file
21
libraries/DNSServer/examples/CaptivePortalAdvanced/tools.ino
Normal file
@ -0,0 +1,21 @@
|
||||
/** Is this an IP? */
|
||||
boolean isIp(String str) {
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
int c = str.charAt(i);
|
||||
if (c != '.' && (c < '0' || c > '9')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** IP to String? */
|
||||
String toStringIp(IPAddress ip) {
|
||||
String res = "";
|
||||
for (int i = 0; i < 3; i++) {
|
||||
res += String((ip >> (8 * i)) & 0xFF) + ".";
|
||||
}
|
||||
res += String(((ip >> 8 * 3)) & 0xFF);
|
||||
return res;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user