Captive Portal for ESP32 Part 2

Hello everyone

the positive response and general interest to the previous blog via a Captive Portal, has led me to write another special blog on the subject and to address some of the points and wishes of you in detail. Among many other detailed questions that I will address later, the desire was to use the Captive Portal not only on an ESP8266, but also on the ESP32 run. This is not possible natively. To do this, some changes, especially of the libraries used, need to be made. Today's code is therefore ONLY running on the ESP32. In detail, in addition to the library adjustments, some optimizations have been added specifically for the ESP32.

Some readers of the blog may have found that the ESP could no longer properly reconnect with an access point after it was rebooted. I was able to re-establish this bug after a long search. This is probably in combination with certain access points, in which ESP is rejected despite correct WLAN access data when logging in to the access point svond and consequently switches from station mode to its own access point mode. In this process, the previous WLAN access data is deleted from the EEPROM (this is the intention).

The solution for this system-side bug is to try the login several times despite supposedly "wrong" access data before switching back to access point mode.

As further small improvement, the current IP address, no matter which mode the ESP32 is in, is output on the serial interface. So if you would like to output this IP address on your display, you only have to change the output at the corresponding place in the code.

In order to be compatible with many ESP boards as far as possible, I continue to dispense with the output of the statistics to external ports of the ESP.

 

Also in this part, the ESP32 builds up with our subsequent Captive Portal Code Captive Portal On. The Wi-Fi has the name "ESP_Config" and the password "12345678". With this we can connect to our mobile phone, and are then automatically directed from the mobile phone to the Captive Portal website. This essentially corresponds to the previous part in the design and functionality.

 

On this website we can now click on the system link "WiFi Settings" and now we get to a comprehensive WLAN configuration page, with which we can now select both a network with which the ESP32 should connect:

 

The wireless network selected here and the password entered are stored in the EEPROM. On the next boat, the ESP32 tries to connect to this network. If this fails, after several attempts, for example, because the network is no longer reachable or the password has been changed, the ESP switches back to access point mode and waits for a reconfiguration.

The custom code for the Captive Portal for the ESP32 is:

[

#include <Wifi.H>
#include <WiFiClient.H> 
#include <Web.H>
#include <ESPmDNS.H>
#include <DNSServer.H>
#include <Eeprom.H>

#define GPIO_OUT_W1TS_REG (DR_REG_GPIO_BASE + 0x0008)
#define GPIO_OUT_W1TC_REG (DR_REG_GPIO_BASE + 0x000c)

Static Const Byte WiFiPwdLen = 25;
Static Const Byte APSTANameLen = 20;

Struct WiFiEEPromData   {     Bool APSTA = True; Access Point or Station Mode - true AP Mode     Bool PwDReq = False; PasswordRequired     Bool CapPortal = True ; CaptivePortal on in AP Mode     Char APSTAName[APSTANameLen]; STATION /AP Point Name TO cONNECT, if defounded        Char WiFiPwd[WiFiPwdLen]; WiFiPAssword, if definded         Char ConfigValid[3]; If Config is Vaild, tag "TK" is required"   };

/* hostname for mDNS. Should work at least on windows. Try http://esp8266.local */
Const Char *ESPHostname = "ESP32";

// DNS server
const byte DNS_PORT = 53;
DNSServer dnsServer;

//Conmmon Paramenters
bool SoftAccOK  = false;

// Web server
WebServer server(80);

/* Soft AP network parameters */
IPAddress apIP(172, 20, 0, 1);
IPAddress netMsk(255, 255, 255, 0);

unsigned long currentMillis = 0;
unsigned long startMillis;

/** Current WLAN status */
short status = WL_IDLE_STATUS;

WiFiEEPromData MyWiFiConfig;
String temp ="";


void setup() 
{   REG_WRITE(GPIO_OUT_W1TS_REG, BIT(GPIO_NUM_16));     // Guru Meditation Error Remediation set   delay(1);   REG_WRITE(GPIO_OUT_W1TC_REG, BIT(GPIO_NUM_16));     // Guru Meditation Error Remediation clear   bool ConnectSuccess = false;   bool CreateSoftAPSucc  = false;   bool CInitFSSystem  = false;   bool CInitHTTPServer  = false;   byte len;    Serial.begin(9600);    while (!Serial) {     ; // wait for serial port to connect. Needed for native USB   }   Serial.println(F("Serial Interface initalized at 9600 Baud."));    WiFi.setAutoReconnect (false);   WiFi.persistent(false);   WiFi.disconnect();    WiFi.setHostname(ESPHostname); // Set the DHCP hostname assigned to ESP station.   if (loadCredentials()) // Load WLAN credentials for WiFi Settings   {       Serial.println(F("Valid Credentials found."));         if (MyWiFiConfig.APSTA == true)  // AP Mode       {          Serial.println(F("Access Point Mode selected."));          Serial.println(MyWiFiConfig.APSTA);         len = strlen(MyWiFiConfig.APSTAName);         MyWiFiConfig.APSTAName[len+1] = '\0';          len = strlen(MyWiFiConfig.WiFiPwd);         MyWiFiConfig.WiFiPwd[len+1] = '\0';           CreateSoftAPSucc = CreateWifiSoftAP();       } else       {         Serial.println(F("Station Mode selected."));                len = strlen(MyWiFiConfig.APSTAName);         MyWiFiConfig.APSTAName[len+1] = '\0';          len = strlen(MyWiFiConfig.WiFiPwd);         MyWiFiConfig.WiFiPwd[len+1] = '\0';         len = ConnectWifiAP();              if ( len == 3 ) { ConnectSuccess = true; } else { ConnectSuccess = false; }            }   } else   { //Set default Config - Create AP      Serial.println(F("NO Valid Credentials found."));       SetDefaultWiFiConfig ();      CreateSoftAPSucc = CreateWifiSoftAP();       saveCredentials();      delay(500);        }   if ((ConnectSuccess or CreateSoftAPSucc))     {                Serial.print (F("IP Address: "));       if (CreateSoftAPSucc) { Serial.println(WiFi.softAPIP());}          if (ConnectSuccess) { Serial.println(WiFi.localIP());}       InitalizeHTTPServer();          }     else     {       Serial.setDebugOutput(true); //Debug Output for WLAN on Serial Interface.       Serial.println(F("Error: Cannot connect to WLAN. Set DEFAULT Configuration."));       SetDefaultWiFiConfig();       CreateSoftAPSucc = CreateWifiSoftAP();       InitalizeHTTPServer();         SetDefaultWiFiConfig();       saveCredentials();        } 
}

void InitalizeHTTPServer() 
 {   bool initok = false;   /* Setup web pages: root, wifi config pages, SO captive portal detectors and not found. */   server.on("/", handleRoot);   server.on("/wifi", handleWifi);   if (MyWiFiConfig.CapPortal) { server.on("/generate_204", handleRoot); } //Android captive portal. Maybe not needed. Might be handled by notFound handler.   if (MyWiFiConfig.CapPortal) { server.on("/favicon.ico", handleRoot); }   //Another Android captive portal. Maybe not needed. Might be handled by notFound handler. Checked on Sony Handy   if (MyWiFiConfig.CapPortal) { server.on("/fwlink", handleRoot); }  //Microsoft captive portal. Maybe not needed. Might be handled by notFound handler.   //server.on("/generate_204", handleRoot);  //Android captive portal. Maybe not needed. Might be handled by notFound handler.   //server.on("/favicon.ico", handleRoot);    //Another Android captive portal. Maybe not needed. Might be handled by notFound handler. Checked on Sony Handy   //server.on("/fwlink", handleRoot);   //Microsoft captive portal. Maybe not needed. Might be handled by notFound handler.    server.onNotFound ( handleNotFound );   // Speicherung Header-Elemente anfordern   // server.collectHeaders(Headers, sizeof(Headers)/ sizeof(Headers[0]));   server.begin(); // Web server start
 }

boolean CreateWifiSoftAP() 
{   WiFi.disconnect();   Serial.print(F("Initalize SoftAP "));   if (MyWiFiConfig.PwDReq)      {       SoftAccOK  =  WiFi.softAP(MyWiFiConfig.APSTAName, MyWiFiConfig.WiFiPwd); // Passwortlänge mindestens 8 Zeichen !     } else     {       SoftAccOK  =  WiFi.softAP(MyWiFiConfig.APSTAName); // Access Point WITHOUT Password       // Overload Function:; WiFi.softAP(ssid, password, channel, hidden)     }   delay(2000); // Without delay I've seen the IP address blank   WiFi.softAPConfig(apIP, apIP, netMsk);   if (SoftAccOK)   {   /* Setup the DNS server redirecting all the domains to the apIP */     dnsServer.setErrorReplyCode(DNSReplyCode::NoError);   dnsServer.start(DNS_PORT, "*", apIP);   Serial.println(F("successful."));   // Serial.setDebugOutput(true); // Debug Output for WLAN on Serial Interface.   } else   {   Serial.println(F("Soft AP Error."));   Serial.println(MyWiFiConfig.APSTAName);   Serial.println(MyWiFiConfig.WiFiPwd);   }   return SoftAccOK;
}

byte ConnectWifiAP() 
{   Serial.println(F("Initalizing Wifi Client."));     byte connRes = 0;   byte i = 0;   WiFi.disconnect();   WiFi.softAPdisconnect(true); // Function will set currently configured SSID and password of the soft-AP to null values. The parameter  is optional. If set to true it will switch the soft-AP mode off.   delay(500);     WiFi.begin(MyWiFiConfig.APSTAName, MyWiFiConfig.WiFiPwd);   connRes  = WiFi.waitForConnectResult();   while (( connRes == 0 ) and (i != 10))  //if connRes == 0  "IDLE_STATUS - change Statius"     {        connRes  = WiFi.waitForConnectResult();       delay(2000);       i++;       Serial.print(F("."));       // statement(s)     }   while (( connRes == 1 ) and (i != 10))  //if connRes == 1  NO_SSID_AVAILin - SSID cannot be reached     {        connRes  = WiFi.waitForConnectResult();       delay(2000);       i++;       Serial.print(F("."));       // statement(s)     }     if (connRes == 3 ) {                          WiFi.setAutoReconnect(true); // Set whether module will attempt to reconnect to an access point in case it is disconnected.                         // Setup MDNS responder                             if (!MDNS.begin(ESPHostname)) {                                 Serial.println(F("Error: MDNS"));                                 } else { MDNS.addService("http", "tcp", 80); }                      }   while (( connRes == 4 ) and (i != 10))  //if connRes == 4  Bad Password. Sometimes happens this with corrct PWD     {        WiFi.begin(MyWiFiConfig.APSTAName, MyWiFiConfig.WiFiPwd);        connRes = WiFi.waitForConnectResult();       delay(3000);       i++;       Serial.print(F("."));                      }   if (connRes == 4 ) {                           Serial.println(F("STA Pwd Err"));                                              Serial.println(MyWiFiConfig.APSTAName);                         Serial.println(MyWiFiConfig.WiFiPwd);                          WiFi.disconnect();                       }   // if (connRes == 6 ) { Serial.println("DISCONNECTED - Not in station mode"); }   // WiFi.printDiag(Serial);
Serial.println("");
return connRes;
}

#define SD_BUFFER_PIXELS 20
/** Load WLAN credentials from EEPROM */
bool loadCredentials() 
{
 bool RetValue;
 EEPROM.begin(512);
 EEPROM.get(0, MyWiFiConfig);
 EEPROM.end();
 if (String(MyWiFiConfig.ConfigValid) = String("TK"))    {     RetValue = true;   } else   {     RetValue = false; // WLAN Settings not found.   }   return RetValue;
}

/** Store WLAN credentials to EEPROM */

bool saveCredentials() 
{
bool RetValue;
// Check logical Errors
RetValue = true;
if  (MyWiFiConfig.APSTA == true ) //AP Mode   {    if (MyWiFiConfig.PwDReq and (sizeof(String(MyWiFiConfig.WiFiPwd)) < 8))     {           RetValue = false;  // Invalid Config     }    if (sizeof(String(MyWiFiConfig.APSTAName)) < 1)     {       RetValue = false;  // Invalid Config     }   } 
if (RetValue)   {   EEPROM.begin(512);   for (int i = 0 ; i < sizeof(MyWiFiConfig) ; i++)       {       EEPROM.write(i, 0);      }   strncpy( MyWiFiConfig.ConfigValid , "TK", sizeof(MyWiFiConfig.ConfigValid) );   EEPROM.put(0, MyWiFiConfig);   EEPROM.commit();   EEPROM.end();   }   return RetValue;
}

void SetDefaultWiFiConfig()
{    byte len;    MyWiFiConfig.APSTA = true;    MyWiFiConfig.PwDReq = true;  // default PW required    MyWiFiConfig.CapPortal = true;    strncpy( MyWiFiConfig.APSTAName, "ESP_Config", sizeof(MyWiFiConfig.APSTAName) );    len = strlen(MyWiFiConfig.APSTAName);    MyWiFiConfig.APSTAName[len+1] = '\0';       strncpy( MyWiFiConfig.WiFiPwd, "12345678", sizeof(MyWiFiConfig.WiFiPwd) );    len = strlen(MyWiFiConfig.WiFiPwd);    MyWiFiConfig.WiFiPwd[len+1] = '\0';      strncpy( MyWiFiConfig.ConfigValid, "TK", sizeof(MyWiFiConfig.ConfigValid) );    len = strlen(MyWiFiConfig.ConfigValid);    MyWiFiConfig.ConfigValid[len+1] = '\0';     Serial.println(F("Reset WiFi Credentials.")); 
}

void handleRoot() {
//  Main Page:
 temp = "";
 byte PicCount = 0;
 byte ServArgs = 0;   // HTML Header   server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");   server.sendHeader("Pragma", "no-cache");   server.sendHeader("Expires", "-1");   server.setContentLength(CONTENT_LENGTH_UNKNOWN);
// HTML Content   server.send ( 200, "text/html", temp );   // Speichersparen - Schon mal dem Cleint senden   temp = "";   temp += "<! DOCTYPE HTML><html lang='de'><head><meta charset='UTF-8'><meta name= viewport content='width=device-width, initial-scale=1.0,'>";   server.sendContent(temp);   temp = "";   temp += "<style type='text/css'><!-- DIV.container { min-height: 10em; display: table-cell; vertical-align: middle }.button {height:35px; width:90px; font-size:16px}";   server.sendContent(temp);   temp = "";   temp += "body {background-color: powderblue;} </style>";   temp += "<head><title>Hauptseite</title></head>";   temp += "<h2>Hauptseite</h2>";   temp += "<body>";   server.sendContent(temp);   temp = ""; 
// Processing User Request   temp = "";    temp += "<table border=2 bgcolor = white width = 500 cellpadding =5 ><caption><p><h3>Systemlinks:</h2></p></caption>";   temp += "<tr><th><br>";   temp += "<a href='/wifi'>WIFI Einstellungen</a><br><br>";   temp += "</th></tr></table><br><br>";   temp += "<footer><p>Programmed and designed by: Tobias Kuch</p><p>Contact information: <a href='mailto:tobias.kuch@googlemail.com'>tobias.kuch@googlemail.com</a>.</p></footer>";   temp += "</body></html>";   server.sendContent(temp);   temp = "";   server.client().stop(); // Stop is needed because we sent no content length
}

void handleNotFound() {      if (captivePortal())        { // If caprive portal redirect instead of displaying the error page.         return;       }      temp = "";      // HTML Header     server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");     server.sendHeader("Pragma", "no-cache");     server.sendHeader("Expires", "-1");     server.setContentLength(CONTENT_LENGTH_UNKNOWN);     // HTML Content     temp += "<! DOCTYPE HTML><html lang='de'><head><meta charset='UTF-8'><meta name= viewport content='width=device-width, initial-scale=1.0,'>";     temp += "<style type='text/css'><!-- DIV.container { min-height: 10em; display: table-cell; vertical-align: middle }.button {height:35px; width:90px; font-size:16px}";     temp += "body {background-color: powderblue;} </style>";     temp += "<head><title>File not found</title></head>";     temp += "<h2> 404 File Not Found</h2><br>";     temp += "<h4>Debug Information:</h4><br>";     temp += "<body>";     temp += "URI: ";     temp += server.uri();     temp += "\nMethod: ";     temp+= ( server.method() == HTTP_GET ) ? "GET" : "POST";     temp += "<br>Arguments: ";     temp += server.args();     temp += "\n";       for ( uint8_t i = 0; i < server.args(); i++ ) {         temp += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n";         }     temp += "<br>Server Hostheader: "+ server.hostHeader();     for ( uint8_t i = 0; i < server.headers(); i++ ) {         temp += " " + server.headerName ( i ) + ": " + server.header ( i ) + "\n<br>";         }       temp += "</table></form><br><br><table border=2 bgcolor = white width = 500 cellpadding =5 ><caption><p><h2>You may want to browse to:</h2></p></caption>";     temp += "<tr><th>";     temp += "<a href='/'>Main Page</a><br>";     temp += "<a href='/wifi'>WIFI Settings</a><br>";     temp += "</th></tr></table><br><br>";     temp += "<footer><p>Programmed by: Tobias Kuch</p><p>Contact information: <a href='mailto:tobias.kuch@googlemail.com'>tobias.kuch@googlemail.com</a>.</p></footer>";     temp += "</body></html>";     server.send ( 404, "", temp );     server.client().stop(); // Stop is needed because we sent no content length     temp = "";      }

/** 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(ESPHostname)+".local")) {     // Serial.println("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() 
 {   //  Page: /wifi   byte i;   byte len ;   temp = "";   // Check for Site Parameters         if (server.hasArg("Reboot") )  // Reboot System         {          temp = "Rebooting System in 5 Seconds..";          server.send ( 200, "text/html", temp );          delay(5000);          server.client().stop();                WiFi.disconnect();          delay(1000);              }       if (server.hasArg("WiFiMode") and (server.arg("WiFiMode") == "1")  )  // STA Station Mode Connect to another WIFI Station          {         startMillis = millis(); // Reset Time Up Counter to avoid Idle Mode whiole operating         // Connect to existing STATION         if ( sizeof(server.arg("WiFi_Network")) > 0  )           {             Serial.println("STA Mode");             MyWiFiConfig.APSTA = false; // Access Point or Station Mode - false Station Mode             temp = "";                       for ( i = 0; i < APSTANameLen;i++) { MyWiFiConfig.APSTAName[i] =  0; }             temp = server.arg("WiFi_Network");             len =  temp.length();             for ( i = 0; i < len;i++)              {                    MyWiFiConfig.APSTAName[i] =  temp[i];                           }             temp = "";             for ( i = 0; i < WiFiPwdLen;i++)  { MyWiFiConfig.WiFiPwd[i] =  0; }                       temp = server.arg("STAWLanPW");             len =  temp.length();                                  for ( i = 0; i < len;i++)                 {                  if (temp[i] > 32) //Steuerzeichen raus                   {                    MyWiFiConfig.WiFiPwd[i] =  temp[i];                     }                 }             temp = "WiFi Connect to AP: -";              temp += MyWiFiConfig.APSTAName;             temp += "-<br>WiFi PW: -";              temp += MyWiFiConfig.WiFiPwd;             temp += "-<br>";                       temp += "Connecting to STA Mode in 2 Seconds.. <br>";             server.send ( 200, "text/html", temp );              server.sendContent(temp);             delay(2000);             server.client().stop();              server.stop();             temp = "";             WiFi.disconnect();             WiFi.softAPdisconnect(true);             delay(500);            // ConnectWifiAP            bool SaveOk = saveCredentials();                       i = ConnectWifiAP();             delay(700);             if (i != 3) // 4: WL_CONNECT_FAILED - Password is incorrect 1: WL_NO_SSID_AVAILin - Configured SSID cannot be reached               {                  Serial.print(F("Cannot Connect to specified Network. Reason: "));                  Serial.println(i);                  server.client().stop();                  delay(100);                               WiFi.setAutoReconnect (false);                  delay(100);                       WiFi.disconnect();                               delay(1000);                  SetDefaultWiFiConfig();                  CreateWifiSoftAP();                  return;                } else               {                  // Safe Config                  bool SaveOk = saveCredentials();                  InitalizeHTTPServer();                  return;               }           }        }                 if (server.hasArg("WiFiMode") and (server.arg("WiFiMode") == "2")  )  // Change AP Mode         {         startMillis = millis(); // Reset Time Up Counter to avoid Idle Mode whiole operating         // Configure Access Point         temp = server.arg("APPointName");               len =  temp.length();         temp =server.arg("APPW");         if (server.hasArg("PasswordReq"))             {                     i =  temp.length();             } else { i = 8; }                  if (  ( len > 1 ) and (server.arg("APPW") == server.arg("APPWRepeat")) and ( i > 7)          )           {             temp = "";             Serial.println(F("APMode"));             MyWiFiConfig.APSTA = true; // Access Point or Sation Mode - true AP Mode                                     if (server.hasArg("CaptivePortal"))             {               MyWiFiConfig.CapPortal = true ; //CaptivePortal on in AP Mode             } else { MyWiFiConfig.CapPortal = false ; }                          if (server.hasArg("PasswordReq"))             {               MyWiFiConfig.PwDReq = true ; //Password Required in AP Mode             } else { MyWiFiConfig.PwDReq = false ; }             for ( i = 0; i < APSTANameLen;i++) { MyWiFiConfig.APSTAName[i] =  0; }             temp = server.arg("APPointName");             len =  temp.length();             for ( i = 0; i < len;i++) { MyWiFiConfig.APSTAName[i] =  temp[i]; }             MyWiFiConfig.APSTAName[len+1] = '\0';               temp = "";             for ( i = 0; i < WiFiPwdLen;i++)  {  MyWiFiConfig.WiFiPwd[i] =  0; }                       temp = server.arg("APPW");             len =  temp.length();                                  for ( i = 0; i < len;i++)  { MyWiFiConfig.WiFiPwd[i] =  temp[i];  }             MyWiFiConfig.WiFiPwd[len+1] = '\0';               temp = "";                      if (saveCredentials()) // Save AP ConfigCongfig                 {                         temp = "Daten des AP Modes erfolgreich gespeichert. Reboot notwendig.";               } else  { temp = "Daten des AP Modes fehlerhaft.";  }           } else if (server.arg("APPW") != server.arg("APPWRepeat"))                 {                   temp = "";                   temp = "WLAN Passwort nicht gleich. Abgebrochen.";                 } else                 {                               temp = "";                   temp = "WLAN Passwort oder AP Name zu kurz. Abgebrochen.";                 }               }      // HTML Header   server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");   server.sendHeader("Pragma", "no-cache");   server.sendHeader("Expires", "-1");   server.setContentLength(CONTENT_LENGTH_UNKNOWN);
// HTML Content   temp += "<! DOCTYPE HTML><html lang='de'><head><meta charset='UTF-8'><meta name= viewport content='width=device-width, initial-scale=1.0,'>";   server.send ( 200, "text/html", temp );    temp = "";    temp += "<style type='text/css'><!-- DIV.container { min-height: 10em; display: table-cell; vertical-align: middle }.button {height:35px; width:90px; font-size:16px}";   temp += "body {background-color: powderblue;} </style><head><title>Smartes Tuerschild - WiFi Settings</title></head>";   server.sendContent(temp);   temp = "";   temp += "<h2>WiFi Einstellungen</h2><body><left>";   temp += "<table border=2 bgcolor = white width = 500 ><td><h4>Current WiFi Settings: </h4>";   if (server.client().localIP() == apIP) {      temp += "Mode : Soft Access Point (AP)<br>";      temp += "SSID : " + String (MyWiFiConfig.APSTAName) + "<br><br>";   } else {      temp += "Mode : Station (STA) <br>";      temp += "SSID  :  "+ String (MyWiFiConfig.APSTAName) + "<br>";      temp += "BSSID :  " + WiFi.BSSIDstr()+ "<br><br>";   }   temp += "</td></table><br>";   server.sendContent(temp);   temp = "";   temp += "<form action='/wifi' method='post'>";   temp += "<table border=2 bgcolor = white width = 500><tr><th><br>";   if (MyWiFiConfig.APSTA == 1)     {       temp += "<input type='radio' value='1' name='WiFiMode' > WiFi Station Mode<br>";     } else     {       temp += "<input type='radio' value='1' name='WiFiMode' checked > WiFi Station Mode<br>";     }   temp += "Available WiFi Networks:<table border=2 bgcolor = white ></tr></th><td>Number </td><td>SSID  </td><td>Encryption </td><td>WiFi Strength </td>";   server.sendContent(temp);   temp = "";   WiFi.scanDelete();   int n = WiFi.scanNetworks(false, false); //WiFi.scanNetworks(async, show_hidden)   if (n > 0) {     for (int i = 0; i < n; i++) {     temp += "</tr></th>";     String Nrb = String(i);     temp += "<td>" + Nrb + "</td>";     temp += "<td>" + WiFi.SSID(i) +"</td>";         Nrb = GetEncryptionType(WiFi.encryptionType(i));     temp += "<td>"+ Nrb + "</td>";     temp += "<td>" + String(WiFi.RSSI(i)) + "</td>";        }   } else {     temp += "</tr></th>";     temp += "<td>1 </td>";     temp += "<td>No WLAN found</td>";     temp += "<td> --- </td>";     temp += "<td> --- </td>";   }   temp += "</table><table border=2 bgcolor = white ></tr></th><td>Connect to WiFi SSID: </td><td><select name='WiFi_Network' >";
if (n > 0) {     for (int i = 0; i < n; i++) {     temp += "<option value='" + WiFi.SSID(i) +"'>" + WiFi.SSID(i) +"</option>";           }   } else {     temp += "<option value='No_WiFi_Network'>No WiFiNetwork found !/option>";   }   server.sendContent(temp);   temp = "";   temp += "</select></td></tr></th></tr></th><td>WiFi Password: </td><td>";   temp += "<input type='text' name='STAWLanPW' maxlength='40' size='40'>";    temp += "</td></tr></th><br></th></tr></table></table><table border=2 bgcolor = white width = 500 ><tr><th><br>";   server.sendContent(temp);   temp = "";   if (MyWiFiConfig.APSTA == true)     {       temp += "<input type='radio' name='WiFiMode' value='2' checked> WiFi Access Point Mode <br>";     } else     {       temp += "<input type='radio' name='WiFiMode' value='2' > WiFi Access Point Mode <br>";     }   temp += "<table border=2 bgcolor = white ></tr></th> <td>WiFi Access Point Name: </td><td>";     server.sendContent(temp);   temp = "";               if (MyWiFiConfig.APSTA == true)     {       temp += "<input type='text' name='APPointName' maxlength='"+String(APSTANameLen-1)+"' size='30' value='" + String(MyWiFiConfig.APSTAName) + "'></td>";     } else     {       temp += "<input type='text' name='APPointName' maxlength='"+String(APSTANameLen-1)+"' size='30' ></td>";     }   server.sendContent(temp);   temp = "";          if (MyWiFiConfig.APSTA == true)     {       temp += "</tr></th><td>WiFi Password: </td><td>";       temp += "<input type='password' name='APPW' maxlength='"+String(WiFiPwdLen-1)+"' size='30' value='" + String(MyWiFiConfig.WiFiPwd) + "'> </td>";       temp += "</tr></th><td>Repeat WiFi Password: </td>";       temp += "<td><input type='password' name='APPWRepeat' maxlength='"+String(WiFiPwdLen-1)+"' size='30' value='" + String(MyWiFiConfig.WiFiPwd) + "'> </td>";     } else     {       temp += "</tr></th><td>WiFi Password: </td><td>";       temp += "<input type='password' name='APPW' maxlength='"+String(WiFiPwdLen-1)+"' size='30'> </td>";       temp += "</tr></th><td>Repeat WiFi Password: </td>";       temp += "<td><input type='password' name='APPWRepeat' maxlength='"+String(WiFiPwdLen-1)+"' size='30'> </td>";     }       temp += "</table>";   server.sendContent(temp);   temp = "";         if (MyWiFiConfig.PwDReq)     {       temp += "<input type='checkbox' name='PasswordReq' checked> Password for Login required. ";      } else     {       temp += "<input type='checkbox' name='PasswordReq' > Password for Login required. ";      }   server.sendContent(Temp);   Temp = "";     If (MyWiFiConfig.CapPortal)     {       Temp += "<input type='checkbox' name='CaptivePortal' checked> Activate Captive Portal";      } else     {       Temp += "<input type='checkbox' name='CaptivePortal' > Activate Captive Portal";      }   Server.sendContent(Temp);   Temp = "";     Temp += "<br></tr></th></table><br> <button type='submit' submit name='Settings' value='1' style='height: 50px; width: 140px' autofocus>Set WiFi Settings</button>";   Temp += "<button type='submit' name='Reboot' value='1' style='height: 50px; width: 200px' >Reboot System</button>";   Server.sendContent(Temp);   Temp = "";   Temp += "<button type='reset' name='action' value='1' style='height: 50px; width: 100px' >Reset</button></form>";   Temp += "<table border=2 bgcolor = white width = 500 cellpadding =5 ><caption><p><h3>System links:</h2></p></caption<tr><th><br>";   Server.sendContent(Temp);   Temp = "";   Temp += "<a href='/'>Main Page</a><br><br></th></tr></table><br>";   Temp += "<footer><p>d;Programmed and designed by: Tobias Kuch</p><p>Contact information: <a href='mailto:tobias.kuch@googlemail.com'>tobias.kuch@googlemail.com</a>.</p></footer>";   Temp += "</body></html>";     Server.sendContent(Temp);   Server.Client().Stop(); Stop is needed because we sent no content length   Temp = "";
}


/** Is this an IP? */
Boolean Isip(String Street) {   for (Int  = 0;  < Street.length(); ++) {     Int C = Street.charAt();     If (C != '.' && (C < '0' || C > '9')) {       Return False;     }   }   Return True;
}

String GetEncryptionType(Byte thisType) {   String Output = "";    read the encryption type and print out the name:    Switch (thisType) {      Case 5:        Output = "WEP";        Return Output;        Break;      Case 2:        Output = "WPA";        Return Output;        Break;      Case 4:        Output = "WPA2";        Return Output;        Break;      Case 7:        Output = "None";        Return Output;        Break;      Case 8:        Output = "Auto";        Return Output;       Break;    }
}

/** IP to String? */
String toStringIp(Ipaddress Ip) {   String Res = "";   for (Int  = 0;  < 3; ++) {     Res += String((Ip >> (8 * )) & 0xff) + ".";   }   Res += String(((Ip >> 8 * 3)) & 0xff);   Return Res;
}

String formatBytes(Size_t Bytes) {            readable display of memory sizes    If (Bytes < 1024) {      Return String(Bytes) + " Byte";    } else If (Bytes < (1024 * 1024)) {      Return String(Bytes / 1024.0) + " KB";    } else If (Bytes < (1024 * 1024 * 1024)) {      Return String(Bytes / 1024.0 / 1024.0) + " MB";    }
 }

Void Loop() 
 {     If (SoftAccOK)   {     dnsServer.processNextRequest(); Dns   }   HTTP   Server.handleClient();     }

]

 

In the next part we want to look at the practical use of our code and build a small file server based on this code.

I wish you a lot of fun with the Captive Portal and when implementing it into my own ESP32 projects.

Esp-8266Projects for advanced

18 comments

Walter

Walter

Hi, this code is bugged, please fix it. Thanks,

Jan

Jan

Hallo Gerald, herzlichsten Dank!!! Ich hatte da schon einige Stunden herumprobiert, aber immer die falsche Stelle erwischt. Es ist exakt so wie beschrieben: durch das Auskommentieren dieser Zeile in der setup() werden die Credentials nicht überschrieben, dennoch wird ein AP angelegt mit den hinterlegten Zugangsdaten. Dadurch ist es egal, wenn das Gerät mal nicht ins WLAN kommen sollte, der AP stört ja nicht und ließe sich auch durch einen Button aktivieren. Danke nochmal!

Gerald Lechner

Gerald Lechner

Hallo Jan
Ich würde in der Setup-Routine
if ((ConnectSuccess or CreateSoftAPSucc))
{
Serial.print (F("IP Address: “));
if (CreateSoftAPSucc) { Serial.println(WiFi.softAPIP());}
if (ConnectSuccess) { Serial.println(WiFi.localIP());}
InitalizeHTTPServer();
}
else
{
Serial.setDebugOutput(true); //Debug Output for WLAN on Serial Interface.
Serial.println(F(”Error: Cannot connect to WLAN. Set DEFAULT Configuration."));
SetDefaultWiFiConfig();
CreateSoftAPSucc = CreateWifiSoftAP();
InitalizeHTTPServer();
SetDefaultWiFiConfig();
saveCredentials();
}
}
die Zeile saveCredentials(); auskommentieren. Die wird nur dann aufgerufen, wenn keine Verbindung möglich war. Wenn die Credentials hier nicht gespeichert werden, geht der ESP32 zwar in den AP-Modus, speichert diesen Zustand aber nicht ab. Damit werden beim nächsten Start wieder die abgespeicherten Credentials für den Station-Mode genutzt.

Jan

Jan

Herzlichen Dank für den tollen Sketch, läuft mit den kleinen Korrekturen soweit sehr gut. Leider werden die manuell gespeicherten WLAN-Daten gelöscht, sobald das WLAN beim Start nicht verfügbar ist. Offenbar werden die Zugangsdaten für AP und STA an der gleichen Stelle im eeprom gespeichert, sodass die vorigen Zugangsdaten unwiederbringlich gelöscht werden, sobald das Gerät in den AP-Modus wechselt. Frage: wie ließen sich die AP-Zugangdaten hart festschreiben, um zu verhindern, dass die eingetragenen WLAN-Daten im AP-Modus gelöscht werden? Im eeprom möchte ich ausschließlich die Daten für den STA-Modus speichern.

Jan

Jan

Ein sehr schöner Sketch, herzlichen Dank für die Arbeit und Veröffentlichung. Mit den Tipps aus den Kommentaren läuft das soweit sehr zuverlässig auf meinem ESP32.
Leider stört ein ‘Feature’ in meinem Projekt ganz erheblich: Findet der ESP beim Starten das vorher konfigurierte WLAN nicht, werden die Zugangsdaten komplett gelöscht. Das ist bei temporärem oder instalbilem WLAN ziemlich störend. Meine Frage in die Runde: an welcher Stelle kann ich das am besten verhindern? Die Routine für den Reset-Knopf möchte ich ja nicht komplett löschen. Ziel: WLAN-Zugangsdaten werden nur durch Reset oder Überschreiben gelöscht, nicht aber automatisch.
Danke für Tipps!!!

Achim

Achim

Hi kann mir einer schreiben wie diese ESPmDNS.h finde und einbinde .
Normal kann ich Bibilotheken einbinden aber diese finde ich nicht oder die IDE meckert das die Zip keine Bibliothek enthält

Tobias

Tobias

Sehr geehrter Herr Holler,

Ich muss leider 2 Punkte korrigieren. Zum einen ist die Beschränkung auf 20 Zeichen der SSID kein Fehler sondern eine Limitierung, die im Sinne des open Source Gedankens angepasst werden kann. Zum anderen werden Tests der Software beim Entwickeln und kursorische Abschlusstests vor Veröffentlichung durchgeführt. Ihre generalisierte Aussage, das Tests überhaupt nicht durchgeführt werden ist daher nicht korrekt.
Jedoch sind generell leider auch zeitliche Limitierungen bei der Entwicklung einzuhalten, sodass eben Bugs nicht ganz vermieden werden können. Ich freue mich jedoch, das diese sich bisher auf ein Bug bei einem komplexem Code wie diesen beschränken. Sachliche Hinweise auf diese oder für alle relevante Verbesserungswünsche, ohne Wertung, sind daher immer willkommen und werden im nächsten Release ggf. berücksichtigt.

Walter Holler

Walter Holler

Wäre es zu viel verlangt, den veröffentlichten Code vorher mal selbst auszuprobieren?
Ich habe den Code auf meinen neu gekauften ESP32 geladen und mindestens 2 Fehler gefunden:
1) Der Vergleichs-Operator für Gleichheit ist “==”. Das einfache “=” ist eine Zuweisung.
Also in loadCredentials() :
if (String(MyWiFiConfig.ConfigValid) == String(“TK”))
statt
if (String(MyWiFiConfig.ConfigValid) = String(“TK”))
2) Die maximale Länge der SSID Namen ist 32 nicht 20, sonst funktioniert es nicht bei längeren Namen wie bei mir.
static const byte APSTANameLen = 32; //was 20
Dann funktioniert auch der AP Mode, Auswahl des WLANs und Eingabe des Passwords und Wechsel in mein WLAN.
Jedoch nach Reset des ESP32 startet er wieder im AP mode..?

MCL

MCL

|ich kann kein ESP_Config finden… Nur ein ESP_C9013b….

das liegt auch an ‘alten’ credentials.
‘C9013b’ ist der zweite Teil der MAC Adresse…
Man kann die Kommandos zum Überschreiben der Credantials ‘zu Fuss’ schreiben oder so:
// if (false) // overwrite only ONCE!!
if (loadCredentials()) // Load WLAN credentials for WiFi Settings

Also einmals mit ’ if (false) ’ kompilieren und laden.
=> so werden die Credentials garantiert korrekt überschrieben
Danach wieder auskommentieren und statt dessen ’ if (loadCredentials()) ’ verwenden und laden.

Manfred

Manfred

Einfach den Code in einen vernünftigen Editor (z.B. Notpad++) kopieren und schon siehst Du, dass diese Klammer das Gegenstück von Zeile 1 ist. Fehlersuche dauerte nicht länger als die Eingabe Deiner Kommentars.

Tobias

Tobias

Hallo Matthias,
Gleiches Problem und gleiche wie bei Christoph.
Gruß

Tobias

Tobias

Hallo Christoph,
Dies liegt darin begründet, das der ESP intern die Credentials speichert, (von deinem vorherigen Projekt) . Um diese loszuwerden, führe EINMALIG in der Setup Routine folgende Sequenz aus:
SetDefaultWiFiConfig();
saveCredentials();
CreateSoftAPSucc = CreateWifiSoftAP();
Damit werden die Daten von dem Vorprojekt überschrieben. Nachdem diese überschrieben wurden, können die Zeilen wieder entfernt werden.

Wolfgang

Wolfgang

Gibt es eigentlich auch einen Teil 1?

Ulrich Klaas

Ulrich Klaas

Hallo,
im Code für den 8266 ist die Fehlerbehebung aber noch nicht drin ??
Der hat sich nicht verändert. Geschieht das noch ?

mfG
Ulli

Peter Pitzeier

Peter Pitzeier

In bool loadCredentials() muss natürlich
if (String(MyWiFiConfig.ConfigValid) == String(“TK”))
und nicht
if (String(MyWiFiConfig.ConfigValid) = String(“TK”))

Christoph O

Christoph O

Danke für das CapturePortal-Besipiel!
Ich habe bei mir das Problem, dass ich vor einiger Zeit bereits einmal einen anderen WLAN-Accesspoint geflasht habe. Die SSID habe ich damals “Heinzelmännchen” genannt.
Nun erscheint nach dem flashen dieses Beispiels wieder der Name “Heinzelmännchen” als SSID und nicht der angegebene “ESP_Config”. Ich vermute, dass “Heinzelmännchen” noch aus alten Zeiten im EEPROM steht und nicht überschrieben wurde.
Ich kann mich leider nicht mit dem Passwort “12345678” verbinden. Das alte Passwort von Heinzelmännchen kenne ich leider nicht mehr.
Kann mir irgendwer weiterhelfen?
Klar, ich könnte wieder ein eigenen WLAN-Accesspoint flashen und alles überschreiben aber hier scheint trotzdem noch ein Bug im Quelltext zu sein.

Matthias

Matthias

Hallo Zusammen,

ich kann kein ESP_Config finden… Nur ein ESP_C9013b….
Das Passwort funktioniert bei dem letzteren auch nicht.
Ich habe nichts an dem Arduino scetch verändert.

Hat sonst jemand noch dieses Problem?

Ich benutze ein Lolin D32 Pro mit einem ESP32 Wrover Chip

Danke und Gruss

Matthias

Joe

Joe

Die

]

gehört wo hin ????

Leave a comment

All comments are moderated before being published

Recommended blog posts

  1. Install ESP32 now from the board manager
  2. Lüftersteuerung Raspberry Pi
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1
  4. ESP32 - das Multitalent
  5. OTA - Over the Air - ESP programming via WLAN