Startseite nach Verbindung mit AP automatisch aufrufen

Hi zusammen,

ich versuch mir grad einen Sketch zusammenzubauen, bei dem sich der ESP nach Verbindung mit dem AP automatisch mit der Server-Startseite verbindet (so dass man sich spart, den Browser öffnen zu müssen, um die IP-Adresse einzugeben).

Dieses Beispiel hier habe ich als "Kopiervorlage" genommenm wobei ich in meinem Fall WIFI_AP_STA also Modus will.

Das oben genannte Beispiel funktioniert prima (wobei die Frage ist, ob ein iPhone oder ein PC auch auf die Startseite weitergeleitet wird, da in dem Beispiel von einer "default android DNS" die Rede ist.

Aber mein Versuch, die Inhalte des Beispiels in mein bestehendes Sketch-Konstrukt einzubauen klappt leider nicht:

const char indexPage[] PROGMEM = R"=====(
  <!DOCTYPE html>
  <html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Expires" content="0">
  </head>
    <body>
      <h1>Startseite</h1>
    </body>
  </html>
)=====";

const char anderePage[] PROGMEM = R"=====(
  <!DOCTYPE html>
  <html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Expires" content="0">
  </head>
    <body>
      <h1>Andere Seite</h1>
    </body>
  </html>
)=====";

#include <WiFi.h>
#include <WiFiClient.h>
#include <WiFiAP.h>
#include <WebServer.h>
WebServer server(80);

#include <DNSServer.h>
const byte DNS_PORT = 53;
IPAddress apIP(8,8,4,4); // The default android DNS
DNSServer dnsServer;



char APssid[25] = "MyAccesspoint";
char APpassword[20] = "123456789";
char ssid[14] = "MySSID-123456";
char password[10] = "MyPW12345";

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  server.on("/", display_root);
  server.on("/test", display_andereseite);

  WiFi.mode(WIFI_AP_STA);
  WiFi.begin(ssid, password);
  unsigned long pause=10000;
  unsigned long connTimer = millis();
  while (WiFi.status() != WL_CONNECTED && (millis()-connTimer < pause)) {
    Serial.print(".");
    delay(500);
  }
  if (WiFi.status() == WL_CONNECTED) {
    // Juhu, verbunden!
  } 
  WiFi.softAP("ESP32-DNSServer", "123456789");
  WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
  dnsServer.start(DNS_PORT, "*", apIP);
  server.begin(); // Server starten
}
void loop() {
  dnsServer.processNextRequest();
  server.handleClient();
}
void display_root() {
  const char * httpType PROGMEM = "text/html";
  server.send_P(200,  httpType, indexPage);
}
void display_andereseite() {
  const char * httpType PROGMEM = "text/html";
  server.send_P(200,  httpType, anderePage);
}

ich vermute mal, es liegt daran, dass diese Passage hier bei mir fehlt:

WiFiClient client = server.available();   // listen for incoming clients
  if (client) {
    String currentLine = "";
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        if (c == '\n') {
          if (currentLine.length() == 0) {
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();
            client.print(responseHTML);
            break;
          } else {
            currentLine = "";
          }
        } else if (c != '\r') {
          currentLine += c;
        }
      }
    }
    client.stop();
  }

Aber ich regle das ja über die server.on() Passagen, oder?
Denn wenn ich die Passage einbaue, bekomm ich eine Fehlermeldung.

Habt ihr wieder einen guten Tipp für mich? Der Wille ist da, der Geist ist müde ;-(

LG

Lass Dir die Aussage noch mal auf der Zunge zergehen. Ich glaube da ist einiges falsch formuliert. z.B. hat der ESP keinen Browser. Wer ist der AP?

Gruß Tommy

Hi Tommy,

AP = Accesspoint

Der User öffnet, nachdem er sich mit dem Accesspoint verbunden hat, einen Internetbrowser auf seinem Handy und gibt da dann ja normalerweise die SoftAP-IP-Adresse ein. Und das will ich quasi einsparen. Das Endgerät des Users soll nach der Verbindung mit dem AP automatisch die Startseite aufrufen. Und das zuerst genannte Beispiel funktioniert ja. D.h. es ist anscheinend möglich.

Aber ich schnall ned, warum es in meinem umgebauten Beispiel nicht geht.

Lg

Das funktioniert wohl; der DNS, der sich dahinter versteckt gehört google.
Ebenso kannst Du da 8.8.8.8 eintragen. Wäre auch ein google-DNS-Server.
Du kannst z.B. auch 9.9.9.9 eintragen.

Das was Du willst, ist eine "Vorschalt"Seite?

Hi,

Ja, im Prinzip ne Vorschaltseite, wobei diese Vorschaltseite direkt die Startseite des eigentlichen Servers sein soll.
Und genau hier scheitere ich ;-(

Ich probier dann mal noch bissl.
Falls ichs irgendwie hinbekomme, poste ich die Lösung.

Habe das für meine digitale Wasserwaage gebaut und für alle gängigen Betriebssysteme & Browser getestet.
Kannst ja mal reinschauen:

und hier der passende Thread dazu:

Letztendlich benötigst Du den AP mit der IP 8.8.8.8, einen Redirect bei der ersten Anfrage und dann ein paar "fake"-Seiten, die Android, Microsoft oder iOS anfragen.
Findest Du alles in der Webserver.ino Datei.

2 Likes

Saucool, les ich mich heut Abend mal rein.
Und darüberhinaus: Starkes Projekt überhaupt!

Hi,

Danke nochmal, funktioniert prima!
Man muss sich nur mit dem Accesspoint des ESP32 verbinden und wird dann direkt auf die Startseite weitergeleitet. Hier mal mein ursprünglicher Sketch angepasst an deine Vorlage @TriB
Evtl. kann das so ja mal jemand als Grundlage weiterverwenden (die mDNS-Geschichte kann man ja weglassen):

const char indexPage[] PROGMEM = R"=====(
  <!DOCTYPE html>
  <html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Expires" content="0">
  </head>
    <body>
      <h1>Startseite</h1>
    </body>
  </html>
)=====";

const char anderePage[] PROGMEM = R"=====(
  <!DOCTYPE html>
  <html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Expires" content="0">
  </head>
    <body>
      <h1>Andere Seite</h1>
    </body>
  </html>
)=====";

#include <WiFi.h>
#include <WiFiClient.h>
#include <WiFiAP.h>
#include <WebServer.h>
#include <ESPmDNS.h>


WebServer server(80);
const char* host = "myesp32";

#include <DNSServer.h>
const byte DNS_PORT = 53;
DNSServer dnsServer;

char APssid[14] = "MyAccesspoint";
char APpassword[10] = "123456789";
char ssid[14] = "MySSID-123456";
char password[10] = "MyPW12345";

unsigned long connTimer=0;


void setup() {
  Serial.begin(115200);
  server.on("/", display_root);
  server.on("/test", display_andereseite); 
  server.on("/generate_204", redirect);    //Android captive portal.
  server.on("/fwlink", redirect);   //Microsoft captive portal.

  WiFi.mode(WIFI_AP_STA);
  WiFi.begin(ssid, password);
  unsigned long pause=10000;
  unsigned long connTimer = millis();
  while (WiFi.status() != WL_CONNECTED && (millis()-connTimer < pause)) {
    Serial.print(".");
    delay(500);
  }
  if (WiFi.status() == WL_CONNECTED) {
    // Juhu, verbunden!
  } 

  IPAddress local_ip(8, 8, 8, 8);
  IPAddress gateway(8, 8, 8, 8);
  IPAddress subnet(255, 255, 255, 0);
  WiFi.softAP(APssid, APpassword);
  WiFi.softAPConfig(local_ip, gateway, subnet);

  dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
  dnsServer.start(DNS_PORT, "*", local_ip);
  Serial.println(WiFi.softAPIP());

  MDNS.begin(host);

  server.begin(); // Server starten
  MDNS.addService("http", "tcp", 80);
}

void loop() {
  dnsServer.processNextRequest();
  server.handleClient();
}

void display_root() {
  const char * httpType PROGMEM = "text/html";
  server.send_P(200,  httpType, indexPage);
}

void display_andereseite() {
  const char * httpType PROGMEM = "text/html";
  server.send_P(200,  httpType, anderePage);
}

String toStringIp(IPAddress ip) {
  Serial.println("IptoString");
  String res = "";
  for (int i = 0; i < 3; i++) {
    res += String((ip >> (8 * i)) & 0xFF) + ".";
  }
  res += String(((ip >> 8 * 3)) & 0xFF);
  return res;
}

void redirect(){
  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
}

Also danke dir nochmal @TriB

Schönen Abend allerseits.

LG

1 Like

@TriB eins ist mit grad noch aufgefallen:
Bei Windoof muss man nach wie vor einen Browser öffnen und die IP manuell eingeben (http://8.8.8.8)

Hab ich noch einen Fehler drin oder ist das einfach so?

Lg

Das Konzept mit http und 8.8.8.8 ist doch kaputt.
Da fehlt mir jedes Verständnis für.

Glaube da gibt es noch Unterschiede von Version zu Version.
Habe nur Windows 10 getestet und da auch nur den Build, der während meiner Tests installiert war. Dazu die drei Endpunkte:

 webServer.on("/fwlink", redirect);   //Microsoft captive portal.  
  webServer.on("/connecttest.txt", redirect); //www.msftconnecttest.com
  webServer.on("/hotspot-detect.html", redirect); //captive.apple.com
  
  webServer.on("/success.txt", handle_success); //detectportal.firefox.com/sucess.txt

Und der Letzte noch speziell für Firefox.

Mein Ziel war ja primär Smartphones zu unterstützen :man_shrugging:t2:
Man findet auf Wikipedia noch ein paar URL´s, die von Win10 abgefragt werden. Da kann man sicherlich noch den ein oder anderen Response einbauen.
Schaue ich mir am Montag mal genauer an und teste nochmal mit Windows 10 & 11.

Ist ja kein Problem Dir dieses Verständnis anzulesen. Z.B. weshalb TSL bei der Art von redirect nicht funktioniert (Stichwort Man in the middle). Oder andere Browser und Systeme eine proprietäre Lösung gebaut haben, die meine Umsetzung mit abdeckt.

Mir fehlt jedes Verständnis für unproduktive Kritik ohne Lösungsansätze. Alternativen oder Begründungen.

Dafür dann http?
Also nicht nur einen sondern alle?
Ok.
Und wenn "die Art von redirect" nicht funktioniert, ist ggfls. über die Art nachzudenken...

Hab jetzt noch bissl was geändert.
Nun gehts auch unter WIndows 10.
Ich glaub die Passage

server.onNotFound(handleNotFound);

die ich vergessen hatte, war das Problem.

Hier der aktuelle Sketch:

const char indexPage[] PROGMEM = R"=====(
  <!DOCTYPE html>
  <html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Expires" content="0">
  </head>
    <body>
      <h1>Startseite</h1>
    </body>
  </html>
)=====";

const char anderePage[] PROGMEM = R"=====(
  <!DOCTYPE html>
  <html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Expires" content="0">
  </head>
    <body>
      <h1>Andere Seite</h1>
    </body>
  </html>
)=====";

const char notFound[] PROGMEM = R"=====(
  <!DOCTYPE html>
  <html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Expires" content="0">
  </head>
    <body>
      <h1>Die Anmeldeseite kann nicht geöffnet werden</h1>
      <p>Bitte versuche, die App über die Adresse http://8.8.8.8 aufzurufen
    </body>
  </html>
)=====";

#include <WiFi.h>
#include <WiFiClient.h>
#include <WiFiAP.h>
#include <WebServer.h>
#include <ESPmDNS.h>


WebServer server(80);
const char* host = "myesp32";

#include <DNSServer.h>
const byte DNS_PORT = 53;
DNSServer dnsServer;

char APssid[14] = "MyAccesspoint";
char APpassword[10] = "123456789";
char ssid[14] = "MySSID-123456";
char password[10] = "MyPW12345";

unsigned long connTimer=0;


void setup() {
  Serial.begin(115200);
  server.on("/", display_root);
  server.on("/test", display_andereseite); 
  server.on("/generate_204", redirect);    //Android captive portal.
  server.on("/fwlink", redirect);   //Microsoft captive portal.
  server.on("/favicon.ico", redirect);
  server.on("/connecttest.txt", redirect); //www.msftconnecttest.com
  server.on("/hotspot-detect.html", redirect); //captive.apple.com
  server.on("/success.txt", handle_success); //detectportal.firefox.com/sucess.txt
  server.onNotFound ( handleNotFound );
  
  WiFi.mode(WIFI_AP_STA);
  WiFi.begin(ssid, password);
  unsigned long pause=10000;
  unsigned long connTimer = millis();
  while (WiFi.status() != WL_CONNECTED && (millis()-connTimer < pause)) {
    Serial.print(".");
    delay(500);
  }
  if (WiFi.status() == WL_CONNECTED) {
    // Juhu, verbunden!
  } 

  IPAddress local_ip(8, 8, 8, 8);
  IPAddress gateway(8, 8, 8, 8);
  IPAddress subnet(255, 255, 255, 0);
  WiFi.softAP(APssid, APpassword);
  WiFi.softAPConfig(local_ip, gateway, subnet);

  dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
  dnsServer.start(DNS_PORT, "*", local_ip);
  Serial.println(WiFi.softAPIP());

  MDNS.begin(host);

  server.begin(); // Server starten
  MDNS.addService("http", "tcp", 80);
}

void loop() {
  dnsServer.processNextRequest();
  server.handleClient();
}

void display_root() {
  const char * httpType PROGMEM = "text/html";
  server.send_P(200,  httpType, indexPage);
}

void display_andereseite() {
  const char * httpType PROGMEM = "text/html";
  server.send_P(200,  httpType, anderePage);
}

String toStringIp(IPAddress ip) {
  Serial.println("IptoString");
  String res = "";
  for (int i = 0; i < 3; i++) {
    res += String((ip >> (8 * i)) & 0xFF) + ".";
  }
  res += String(((ip >> 8 * 3)) & 0xFF);
  return res;
}

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;
}

void redirect(){
  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
}

void handleNotFound() { 
    if (captivePortal()) 
      { // If caprive portal redirect instead of displaying the error page.
        return;
      } 
    const char * httpType PROGMEM = "text/html";
    server.send_P(200,  httpType, notFound);
    server.client().stop(); // Stop is needed because we sent no content length  
}

// Die Funktion hier ist doppelt-gemoppelt, evtl. kann man das auch mit der bereits vorhandenen void redirect() zusammenfassen...
boolean captivePortal() {
  if (!isIp(server.hostHeader()) && server.hostHeader() != (String(host)+".local")) {
    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;
}

void handle_success(){
  Serial.println(F("Handle success.txt"));
  server.send(200, "text/plain", "success");
}

Schönen Sonntag allen!

Hi,

ich muss nochmal dieses Thema aufgreifen.

Doofe Frage:
Wenn ich Captive Portal einrichte (wie oben beschrieben) und jemand ein Gerät hat, das die Captive Portal Weiterleitung nicht unterstützt (z.B. irgendwann Windoof 12) - was kann schlimmstenfalls passieren:

a) Dass die Weiterleitung einfach nicht geöffnet wird (was ja nicht sooo schlimm wäre, da man sich ja auch manuell verbinden kann).
b) Dass die Weiterleitung angetriggert wird aber ins Leere geht, also quasi auf eine Art "Not Found"-Seite führt
c) Dass derjenige die Anmeldeseite GAR NICHT erreicht (auch wenn er sich manuell mit dem ESP verbindet und http://8.8.8.8 eingibt

c wäre doof und würde Captive Portal ausschließen, wenn man ein Consumer-Produkt entwickeln würde. Aber kann c eintreten?

Wie seht ihr das?

LG

Aus meiner Sicht passiert einfach a).
So war es auch, als ich manche Endpunkte noch nicht definiert habe, die speziell bei iOS, Android oder eben Windows 10 nötig waren.
Ansonsten kann man natürlich auch einen DNS starten, wo man den ESP32 über den Hostnamen erreichbar macht. Dann muss man nicht 8.8.8.8 tippen, sondern kann direkt den Namen eintragen.

c) wäre aus meiner Sicht nur ein Problem, wenn die Endgeräte alle nicht-SSL Verbindungen blockieren. Das ist noch nicht so, würde ich aber für die Zukunft nicht ausschließen.
Daher denke ich, wäre das ne gute Variante, auf https zu setzen.

8.8.8.8 ist aber eine IP, die nicht Dir gehört (oder Deinem AP), sondern einem DNS-Server von Google.

Gruß Tommy

Und wenn Google über den ESP32 erreichbar wäre, wäre das evtl. ein Problem. Isses aber nicht.
Dein Router hat auch gerade die 192.168.2.1.
Meiner auch.
Und der von basementmedia ebenfalls.
Wem "gehört" die IP jetzt? Und stellt das ein Problem dar?

In den meisten Routern, die ein Captive Portal anbieten, ist die 8.8.8.8 die Voreinstellung. Warum kann ich in der Tat nicht sagen. Findet sich aber auch in vielen Beispielcodes :man_shrugging:t2:

Du unterschlägst bei dieser Behauptung, dass die sogenannten "privaten IP" (wie z.B. die 192.168.xxx.yyy) von keinem Router nach außen weiter geleitet werden, die 8.8.8.8 schon.
Da ist schon ein wesentlicher Unterschied dazwischen.

Gruß Tommy

Aber ich.
So wird die DNS Filterung der Provider, des Default, umgangen.
Optimal für .... (sage ich nicht)

Nicht solange bis DNSsec flächendeckend Einzug gehalten hat.
Der Provider spart sich nur den Aufbau und den Betrieb eines eigenen Dienstes.
Mein Provider übermittelt z.B. 8.8.8.8 per default bei jeder Verbindung.