Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Captive portal example #97

Open
flatsiedatsie opened this issue Aug 31, 2020 · 3 comments · May be fixed by #99
Open

Captive portal example #97

flatsiedatsie opened this issue Aug 31, 2020 · 3 comments · May be fixed by #99
Assignees
Labels

Comments

@flatsiedatsie
Copy link

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.

@fhessel fhessel added the feature label Sep 3, 2020
@fhessel fhessel linked a pull request Sep 4, 2020 that will close this issue
@fhessel
Copy link
Owner

fhessel commented Sep 5, 2020

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.

@fhessel fhessel self-assigned this Sep 5, 2020
@flatsiedatsie
Copy link
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.

@flatsiedatsie
Copy link
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
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants