From 0bb4fb288603aa372034dd5df4838aa536852396 Mon Sep 17 00:00:00 2001 From: aalku Date: Sun, 16 Aug 2015 20:19:19 +0200 Subject: [PATCH] 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 --- .../CaptivePortal2/CaptivePortal2.ino | 100 ------------- .../CaptivePortalAdvanced.ino | 135 ++++++++++++++++++ .../CaptivePortalAdvanced/credentials.ino | 27 ++++ .../CaptivePortalAdvanced/handleHttp.ino | 129 +++++++++++++++++ .../examples/CaptivePortalAdvanced/tools.ino | 21 +++ 5 files changed, 312 insertions(+), 100 deletions(-) delete mode 100644 libraries/DNSServer/examples/CaptivePortal2/CaptivePortal2.ino create mode 100644 libraries/DNSServer/examples/CaptivePortalAdvanced/CaptivePortalAdvanced.ino create mode 100644 libraries/DNSServer/examples/CaptivePortalAdvanced/credentials.ino create mode 100644 libraries/DNSServer/examples/CaptivePortalAdvanced/handleHttp.ino create mode 100644 libraries/DNSServer/examples/CaptivePortalAdvanced/tools.ino diff --git a/libraries/DNSServer/examples/CaptivePortal2/CaptivePortal2.ino b/libraries/DNSServer/examples/CaptivePortal2/CaptivePortal2.ino deleted file mode 100644 index 80bf77b1b..000000000 --- a/libraries/DNSServer/examples/CaptivePortal2/CaptivePortal2.ino +++ /dev/null @@ -1,100 +0,0 @@ -#include -#include -#include -#include - -const char* ssid = "esp8266"; -boolean LEDstate[] = {LOW, false, LOW}; - -const char* html = "Success" - "" - "" - ""; - -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]); - } -} diff --git a/libraries/DNSServer/examples/CaptivePortalAdvanced/CaptivePortalAdvanced.ino b/libraries/DNSServer/examples/CaptivePortalAdvanced/CaptivePortalAdvanced.ino new file mode 100644 index 000000000..229662651 --- /dev/null +++ b/libraries/DNSServer/examples/CaptivePortalAdvanced/CaptivePortalAdvanced.ino @@ -0,0 +1,135 @@ +#include +#include +#include +#include +#include +#include + +/* + * 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(); +} + diff --git a/libraries/DNSServer/examples/CaptivePortalAdvanced/credentials.ino b/libraries/DNSServer/examples/CaptivePortalAdvanced/credentials.ino new file mode 100644 index 000000000..3f9501e79 --- /dev/null +++ b/libraries/DNSServer/examples/CaptivePortalAdvanced/credentials.ino @@ -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?"********":""); +} + +/** 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(); +} diff --git a/libraries/DNSServer/examples/CaptivePortalAdvanced/handleHttp.ino b/libraries/DNSServer/examples/CaptivePortalAdvanced/handleHttp.ino new file mode 100644 index 000000000..4164506ce --- /dev/null +++ b/libraries/DNSServer/examples/CaptivePortalAdvanced/handleHttp.ino @@ -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( + "" + "

HELLO WORLD!!

" + ); + if (server.client().localIP() == apIP) { + server.sendContent(String("

You are connected through the soft AP: ") + softAP_ssid + "

"); + } else { + server.sendContent(String("

You are connected through the wifi network: ") + ssid + "

"); + } + server.sendContent( + "

You may want to config the wifi connection.

" + "" + ); + 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( + "" + "

Wifi config

" + ); + if (server.client().localIP() == apIP) { + server.sendContent(String("

You are connected through the soft AP: ") + softAP_ssid + "

"); + } else { + server.sendContent(String("

You are connected through the wifi network: ") + ssid + "

"); + } + server.sendContent( + "\r\n
" + "
ONOFF
" - "ONOFF
 
ALL ON
" - "
ALL OFF
" + ); + server.sendContent(String() + ""); + server.sendContent(String() + ""); + server.sendContent( + "
SoftAP config
SSID " + String(softAP_ssid) + "
IP " + toStringIp(WiFi.softAPIP()) + "
" + "\r\n
" + "" + ); + server.sendContent(String() + ""); + server.sendContent(String() + ""); + server.sendContent( + "
WLAN config
SSID " + String(ssid) + "
IP " + toStringIp(WiFi.localIP()) + "
" + "\r\n
" + "" + ); + 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"); + } + } else { + server.sendContent(String() + ""); + } + server.sendContent( + "
WLAN list (refresh if any missing)
SSID " + String(WiFi.SSID(i)) + String((WiFi.encryptionType(i) == ENC_TYPE_NONE)?" ":" *") + " (" + WiFi.RSSI(i) + ")
No WLAN found
" + "\r\n

Connect to network:

" + "" + "
" + "
" + "

You may want to return to the home page.

" + "" + ); + 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 ); +} + diff --git a/libraries/DNSServer/examples/CaptivePortalAdvanced/tools.ino b/libraries/DNSServer/examples/CaptivePortalAdvanced/tools.ino new file mode 100644 index 000000000..5b6d78931 --- /dev/null +++ b/libraries/DNSServer/examples/CaptivePortalAdvanced/tools.ino @@ -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; +} +