Skip to content

Captive portal example #97

Open

Description

@flatsiedatsie

Is your feature request related to a problem? Please describe.
Thank you for the many wonderul examples, they have helped a lot. But one thing I have not been able to get working is turning the EP32 into a hotspot that also redirects users to the website that it's hosting. I would like users to be able to connect to the ESP's wifi hotspot, and that the browser would then automatically show the website that it's hosting. No login is required.

I tried to get this to work by combining examples I found with this code. However, when I enables a local DNS host, it always resulted in 'guru meditation' error. This must be my own user error. But after trying to get it to work for a few hours, and feeling that I may not be the first to try and use an ESP32 this way, I thought I'd ask for any tips, suggestions, or perhaps even an example.

Describe the solution you'd like
Advice would be welcome. An example would be amazing. I would be happy to help develop an example and then share it as part of the exampes of this addon.

Describe alternatives you've considered
I spent a lot of time trying to make it work myself, but my skill level is probably not high enough.

Additional context
It's for an art project. The idea is that people who visit an exhibit can control a water fountain themselves by connecting to the device's hotspot with their mobile phone, and then automatically being guided to the website on it. Then they can control the fountain themselves.

Activity

  1. added a commit that references this issue on Sep 4, 2020
    b404b35
  2. fhessel commented on Sep 5, 2020

    @fhessel
    Owner

    Could you try #99 and check if that works for you?

    I had the same issue with a lot of guru meditation, but that seems to be caused by the way the DHCP server is implemented in the AP mode for Arduino. It seems to store configuration in the non-volatile flash partition, which then causes the reboot shortly after a client connects. So in case you face that issue, maybe just try erasing the whole flash and then uploading a clean sketch.

    Limitations of the example are described in the PR, but the basic functionality works. You might need to turn off mobile data when you're connected to the AP, I'm not sure if that can be circumvented.

  3. self-assigned this
    on Sep 5, 2020
  4. flatsiedatsie commented on Sep 7, 2020

    @flatsiedatsie
    Author

    Wow, thank you!

    I've actually also managed to get it to work at the same time. The trick that worked for me was:

    DNS as per the examples -> create a http server too, which captures root -> index.html on non-https server forwards to https server using a meta refresh element.

    I'll look into your solution (it's probably less of a round-about way) and implement that instead.

  5. flatsiedatsie commented on Sep 7, 2020

    @flatsiedatsie
    Author

    Looking at your code it seems you did the same thing that I stubled upon: adding an insecure http server step solves the problem.

    I tried running your code, but it didn't work.

    • upload code
    • connect to open hotspot captiveESP
    • open random website in browser (https://www.youtube.com/) in Chromium on macos
    • ERR_CONNECTION_REFUSED

    This is in the serial output:

    Setting up WiFi... OK
    Starting DNS... OK
    Starting HTTP server... OK
    [HTTPS:I] New connection. Socket FID=56
    [HTTPS:I] Request: GET /bag (FID=56)
    [HTTPS:W] Could not find a matching resource
    [HTTPS:I] Connection closed. Socket FID=56
    [HTTPS:I] New connection. Socket FID=57
    [HTTPS:I] Request: GET /bag (FID=57)
    [HTTPS:W] Could not find a matching resource
    [HTTPS:I] Connection closed. Socket FID=57
    [HTTPS:I] New connection. Socket FID=58
    [HTTPS:I] Request: GET /bag (FID=58)
    [HTTPS:W] Could not find a matching resource
    [HTTPS:I] Connection closed. Socket FID=58
    [HTTPS:I] New connection. Socket FID=59
    [HTTPS:I] Request: GET /bag (FID=59)
    

    I disabled the https-everywhere plugin, but that didn't change things.

    The server does seem to turn http into https, but it seems to do this before it moves things to the internal IP address?

    A small typo in the example code:
    Do not uses connection config persistence

    Here is my working messy code:

    
    // For captive portal
    #include <DNSServer.h>
    const byte DNS_PORT = 53;
    DNSServer dnsServer;
    
    #define USE_SD // true               // This switches between using an SD card of SPIFFS as the filesystem
    
    #define LED 2
    
    unsigned long last_unlock_time = 0;
    
    #include "FS.h"
    
    #ifdef USE_SD
    #include "SD.h"
    #include "SPI.h"
    #define PIN_NUM_MISO 16 
    #define PIN_NUM_MOSI 13 
    #define PIN_NUM_SCK  14 
    #define PIN_NUM_SS   15
    #else
    #include <SPIFFS.h>
    #endif
    
    
    
    // We will use wifi
    #include <WiFi.h>
    
    // Trying to handle captive portal crash
    //wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT();
    //wifi_init_config.ampdu_rx_enable = 0;
    //ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_config));
    
    
    // Working with c++ strings
    #include <string>
    
    // Define the name of the directory for public files in the SPIFFS parition
    //#define DIR_PUBLIC "/public"
    #define DIR_PUBLIC ""
    
    
    boolean locked = true;
    boolean auto_spray = false;
    
    const int relay_pin = 25;
    const int spray_pin = 19;
    const int button_pin = 26;
    
    // We need to specify some content-type mapping, so the resources get delivered with the
    // right content type and are displayed correctly in the browser
    char contentTypes[][2][32] = {
      {".html", "text/html"},
      {".css",  "text/css"},
      {".js",   "application/javascript"},
      {".json", "application/json"},
      {".png",  "image/png"},
      {".jpg",  "image/jpg"},
      {"", ""}
    };
    
    // Includes for the server
    #include <HTTPSServer.hpp>
    #include <HTTPServer.hpp>
    #include <SSLCert.hpp>
    #include <HTTPRequest.hpp>
    #include <HTTPResponse.hpp>
    #include <util.hpp>
    
    // Replace with your network credentials to create a hotspot
    const char* ssid     = "Fountain - password=sprayfun";
    const char* password = "sprayfun";
    
    IPAddress apIP(192, 168, 4, 1);
    
    //boolean dns_handled = false;
    
    // Start DNS server
    //DNSServer dnsServer;
    
    // The HTTPS Server comes in a separate namespace. For easier use, include it here.
    using namespace httpsserver;
    
    
    //SSLCert * getCertificate();
    SSLCert * getCertificate();
    void handleFile(HTTPRequest * req, HTTPResponse * res);
    void handleSpray(HTTPRequest * req, HTTPResponse * res);
    void handleLock(HTTPRequest * req, HTTPResponse * res);
    void handleUnlock(HTTPRequest * req, HTTPResponse * res);
    void setAutoSpray(HTTPRequest * req, HTTPResponse * res);
    
    
    // We just create a reference to the server here. We cannot call the constructor unless
    // we have initialized the SPIFFS and read or created the certificate
    HTTPSServer * secureServer;
    
    HTTPServer insecureServer = HTTPServer();
    
    
    void listAllFiles(){
    
    #ifdef USE_SD
      Serial.println("in listAllFiles from SD card");
      File root = SD.open("/");
    #else
      Serial.println("in listAllFiles from SPIFFS");
      File root = SPIFFS.open("/");
    #endif
    
      File file = root.openNextFile();
     
      while(file){
        Serial.print("FILE: ");
        Serial.println(file.name());
        file = root.openNextFile();
      }
     
    }
    
    
    void setup() {
    
      // For logging
      Serial.begin(115200);
      Serial.println("Hello world, I am a water fountain");
    
      pinMode(LED,OUTPUT);
    
      for (int i = 0; i <= 10; i++) { // to visually indicate that the device has rebooted (unwantedly).
        delay(200);
        digitalWrite(LED,HIGH);
        delay(200);
        digitalWrite(LED,LOW);
      }
      
    
    #ifdef USE_SD
      SPI.begin(PIN_NUM_SCK, PIN_NUM_MISO, PIN_NUM_MOSI, PIN_NUM_SS);
    
      if(!SD.begin(PIN_NUM_SS)){
        Serial.println("Card Mount Failed");
        return;
      }
      uint8_t cardType = SD.cardType();
    
      if(cardType == CARD_NONE){
        Serial.println("No SD card attached");
        return;
      }
    
      Serial.print("SD Card Type: ");
      if(cardType == CARD_MMC){
        Serial.println("MMC");
      } else if(cardType == CARD_SD){
        Serial.println("SDSC");
      } else if(cardType == CARD_SDHC){
        Serial.println("SDHC");
      } else {
        Serial.println("UNKNOWN");
      }
    
      uint64_t cardSize = SD.cardSize() / (1024 * 1024);
      Serial.printf("SD Card Size: %lluMB\n", cardSize);
    
      //fileSystem = &SD;
    
    #else
      // Try to mount SPIFFS without formatting on failure
      if (!SPIFFS.begin(false)) {
        // If SPIFFS does not work, we wait for serial connection...
        while(!Serial);
        delay(1000);
    
        // Ask to format SPIFFS using serial interface
        Serial.print("Mounting SPIFFS failed. Try formatting? (y/n): ");
        while(!Serial.available());
        Serial.println();
    
        // If the user did not accept to try formatting SPIFFS or formatting failed:
        if (Serial.read() != 'y' || !SPIFFS.begin(true)) {
          Serial.println("SPIFFS not available. Stop.");
          while(true);
        }
        Serial.println("SPIFFS has been formated.");
      }
      Serial.println("___SPIFFS has been mounted. Files:___");
       
       //fileSystem = &SPIFFS;
    
    #endif // end of USE_SD
    
    
      pinMode(relay_pin, OUTPUT);
      pinMode(spray_pin, OUTPUT);
      
    
      listAllFiles();
      Serial.println("___End of files list___");
    
      // Now that SPIFFS is ready, we can create or load the certificate
      SSLCert *cert = getCertificate();
      if (cert == NULL) {
        Serial.println("Could not load certificate. Stop.");
        while(true);
      }
    
    
      // Connect to WiFi
      Serial.println("Setting up WiFi");
      
      /*
      WiFi.begin(WIFI_SSID, WIFI_PSK);
      while (WiFi.status() != WL_CONNECTED) {
        Serial.print(".");
        delay(500);
      }
      Serial.print("Connected. IP=");
      Serial.println(WiFi.localIP());
      */
      
      Serial.println("Setting AP (Access Point)…");
      
      WiFi.disconnect();   //added to start with the wifi off, avoid crashing
      //WiFi.mode(WIFI_OFF);
      //WiFi.mode(WIFI_AP);
      WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); // Workaround: makes setHostname() work
      WiFi.setHostname("fountain");
    
      //WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
      WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 0, 0)); // DNS example used this?
      WiFi.softAP(ssid, password); // Remove the password parameter, if you want the AP (Access Point) to be open
    
      IPAddress IP = WiFi.softAPIP();
      Serial.print("AP IP address: "); Serial.println(IP);
    
      dnsServer.start(DNS_PORT, "*", apIP);
      
      //server.begin();
      Serial.print("HOSTNAME: "); Serial.println(WiFi.getHostname());
      
      // Create the server with the certificate we loaded before
      secureServer = new HTTPSServer(cert);
    
      // We register the file handler as the default node, so every request that does
      // not hit any other node will be redirected to the file system.
      ResourceNode * fileNode = new ResourceNode("", "", &handleFile);
      secureServer->setDefaultNode(fileNode);
    
      //ResourceNode * nodeRoot = new ResourceNode("/", "GET", &handleRoot);
      //secureServer->registerNode(nodeRoot);
      
      // Spray handler
      ResourceNode * spray = new ResourceNode("/spray", "GET", &handleSpray);
      secureServer->registerNode(spray);
    
      // Lock and unlock handler
      ResourceNode * unlock = new ResourceNode("/unlock", "GET", &handleUnlock);
      secureServer->registerNode(unlock);
      ResourceNode * lock = new ResourceNode("/lock", "GET", &handleLock);
      secureServer->registerNode(lock);
    
      ResourceNode * full_spray = new ResourceNode("/auto", "GET", &setAutoSpray);
      secureServer->registerNode(full_spray);
      
    
      Serial.println("Starting server...");
      secureServer->start();
      if (secureServer->isRunning()) {
        Serial.println("Secure server ready.");
      }
    
    
      ResourceNode * nodeForward = new ResourceNode("/", "GET", &handleForward);
      //HTTPServer insecureServer = HTTPServer();
      //insecureServer.registerNode(nodeRoot);
      insecureServer.setDefaultNode(nodeForward);
      insecureServer.start();
    }
    
    
    
    void loop() {
      dnsServer.processNextRequest();
      // This call will let the server do its work
      secureServer->loop();
      insecureServer.loop();
    
      /*
      boolean button_state = digitalRead(button_pin);
      if ( button_state == HIGH ){
        Serial.println("button high");
        sprayNow();
      }
      */
      if( millis() - last_unlock_time > 4000){
        if(locked == false){
          locked = true;
          Serial.println("locked");
        }
        
        digitalWrite(relay_pin, LOW);
        digitalWrite(spray_pin, LOW);
      }
      
      delay(1);
    }
    
    
    
    void handleForward(HTTPRequest * req, HTTPResponse * res) {
      res->setHeader("Content-Type", "text/html");
    
      res->println("<!DOCTYPE html>");
      res->println("<html>");
      res->println("<head><title>Water fountain</title></head>");
      if (req->isSecure()) {
    
      }else{
        res->println("<meta http-equiv=\"Refresh\" content=\"0; url='https://192.168.4.1/index.html'\" />");
      }
      res->println("<body>");
      res->println("<h1>Forwarding...</h1>");
    
      // You can check if you are connected over a secure connection, eg. if you
      // want to use authentication and redirect the user to a secure connection
      // for that
      if (req->isSecure()) {
        res->println("<p>You are connected via <strong>HTTPS</strong>.</p>");
      } else {
        res->println("<p>You are connected via <strong>HTTP</strong>.</p>");
      }
    
      res->println("</body>");
      res->println("</html>");
    }
    
    // there are some functions to handle files here, which I've removed for brevity.
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions