Auto reindirizzamento su ESP32 in AP mode con AsyncWebServer

Ciao a tutti,
introduco il problema: ho due ESP32 uno configurato in AP mode e l'altro configurato in STA mode che si collega alla rete creata dal primo; ognuno di questi hosta un AsyncWebServer e grazie alla AsyncElegantOTA anche una pagina per l'upload di nuovi firmware.
il mio obbiettivo è quello di poter creare nel firmware del AP un sistema che mi reindirizzi alla pagina root di se stesso dove ci programmerò due pulsanti con il link alle rispettive pagine di aggiornamento.
Ho provato a vedere l'esempio che c'è nella libreria AsyncWebServer ma non ci ho capito molto; qualcuno può spiegarmi come funziona, e soprattutto come possa modificarlo per ottenere ciò di cui sopra (non chiedo un codice funzionante ma le dritte per potermelo modificare) .
Probabilmente avrei bisogno anche di nozionistica sul principio di funzionamento del sistema per poterne rispondere in caso...

Capisco la complessità della domanda, perciò ringrazio sentitamente tutti coloro che proveranno a dire la loro.

Alan

Ciò di cui stai parlando si chiama in gergo "captive portal"
Non ho ben capito quale libreria stai usando, ma se si tratta di questa c'è un esempio specifico su come realizzare un captive portal.

@cotestatnt grazie mille della risposta!

ho scritto che ho guardato l'esempio; il problema che non ci ho capito praticamente nulla...
Vedo che definiscono una classe figlia di AsyncWebHandler (che non so a che serva), poi zero...
sapresti aiutarmi a capire?
Credo che mi servirebbe anche avere delle guide per quanto riguarda la teoria dei server web, perché tutte queste classi credo servano appositamente.

Anche perché mi piacerebbe reindirizzare alla root del server e non come fanno ad una pagina scritta nella classe!

Alan

Si, non mi era chiaro se ti riferivi a questa libreria specifica o altro.

Comunque in sostanza il principio è quello di definire un handler per ciascun tipo di richiesta web.
Nell'esempio in questione hanno definito un unico handler che gestisce tutte le richieste passandogli la classe custom CaptiveRequestHandler derivata dalla generica AsyncWebHandler definita nella libreria.

Questo però non è l'unico modo per gestire le richieste del browser. Se guardi gli altri esempi, potrai vedere che più spesso usa il metodo on()

Ad esempio questo codice alla richiesta dell'homepage "/" del webserver risponde con un bel "Hello, world".
Al metodo vengono passati come parametri l'indirizzo per cui deve "rispondere", il tipo di richiesta (GET, POST etc etc) e la funzione di callback associata. In questo caso la funzione è definita come lambda direttamente all'interno della chiamata del metodo.

server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
   request->send(200, "text/plain", "Hello, world");
});

Se ti risulta più comprensibile, puoi riscrivere in questo modo:

void homeHandler(AsyncWebServerRequest *request) {
   request->send(200, "text/plain", "Hello, world");
}

.....
void setup() {
  ....
  server.on("/", HTTP_GET, homeHandler);

In questo modo invece, alla richiesta dell'home carica la pagina /index.htm (precedentemente caricata sulla flash dell'ESP formattata con filesystem SPIFFS) perché l'ha impostata come pagina di default.
Se invece tu ad esempio chiedessi la pagina /help.htm, dovrebbe essere servita direttamente (se presente nel filesystem).

server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm");

Con questo non ho capito esattamente cosa intendi. Le pagine saranno ospitate direttamente nella memoria flash dell'ESP? In tal caso potresti partire dall'esempio e modificare la classe per fare in modo che venga servito il file invece del codice HTML creato a runtime.

A quel punto aggiungi anche l'handler per servire i file statici.

Comunque se il tuo webserver dovrà servire principalmente dei file presenti nella flash, io ho sviluppato una libreria proprio per semplificare un po' la gestione, che fa uso solo delle classi e delle librerie già presenti nel core ESP per Arduino.

La libreria include anche un WiFi manager per impostare SSID, password ed altre eventuali variabili ed un web editor (una pagina web inclusa di default, ma comunque opzionale) che ti consente di fare l'upload o apportare modifiche ai file HTML, Javascript e CSS direttamente dal browser senza dover ricompilare ogni volta il firmware.

Ecco, esattamente cosi voglio fare, solo che al posto di

void homeHandler(AsyncWebServerRequest *request) {
   request->send(200, "text/plain", "Hello, world");
}

metterò

void homeHandler(AsyncWebServerRequest *request) {
   request->send(SPIFFS, "/index.html", "text/plain");
}

resta il fatto che quando un dispositivo si collega all'AP, vorrei che il DNS (?) mi reindirizzi esattamente a quella pagina!

Alan

Allora, ho approfondito un po' il discorso del re-indirizzamento automatico con apertura automatica del browser (e ho aggiunto la funzionalità anche alla mia libreria).

In poche parole quando ti colleghi all'ESP configurato come access point, ci sono dei servizi che fanno una serie di web request ad indirizzi specifici.
Per poter intercettare queste richieste è necessario far "girare" un DNS server sull'ESP configurato sulla porta prevista per gestire tutti i tipi di richieste su qualsiasi dominio.

Ricapitolando sull'esp è necessario:

  • Avviare un DNS server che intercetta le richieste verso qualsiasi dominio e le rigira sull'indirizzo IP dell'ESP.
  • Avviare un Web server che gestisce le richieste /redirect , /connecttest.txt, /hotspot-detect.html, /generate_204 reindirizzando alla pagina di setup voluta.

Queste richieste vengono eseguite dai servizi che gestiscono la connessione all'hot-spot e quindi lo specifico indirizzo usato dipende dal sistema operativo.

Al momento io ho identificato questi 4 indirizzi usati su Windows, iPhone, Android facendo prove e stampando sul monitor seriale tutte le richieste ricevute dai client. Devo ancora verificare qual è l'indirizzo richiesto su sistemi Linux, ma presumo sia lo stesso usato con Android.

DNSServer dnsServer;

void setup() {
  dnsServer.start(53, "*", WiFi.softAPIP());
  ...
}

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

Invece, nell'esempio della libreria ESPAsyncWebserver, viene usato un approccio diverso ovvero se si trova in modalità AP reindirizza tutto a prescindere.
Francamente non mi piace granché, perché perdi l'automatismo offerto dai suddetti servizi che aprono il Captive Portal e soprattutto non puoi navigare il webserver per raggiungere eventuali altre pagine perché verrai sempre reindirizzato alla stessa.

Per questo motivo nella mia libreria ho deciso di gestire in questo modo (non far caso a std::bind, serve per associare il metodo captivePortal() incluso nella stessa libreria come funzione di callback). Potresti replicare il funzionamento di base anche nel tuo caso.

    // Captive Portal redirect
    webserver->on("/redirect", HTTP_GET, std::bind(&FSWebServer::captivePortal, this));
    webserver->on("/connecttest.txt", HTTP_GET, std::bind(&FSWebServer::captivePortal, this));
    webserver->on("/msdownload", HTTP_GET, std::bind(&FSWebServer::captivePortal, this));

Il metodo captivePortal() è quello che si occupa del redirect vero e proprio ed è scritto cosi (webserver è un puntatore alla classe WebServer esp32 o esp8266, per questo c'è la notazione ->):


// Redirect to captive portal if we got a request for another domain. 
bool FSWebServer::captivePortal() {
  IPAddress ip = webserver->client().localIP();
  char serverLoc [sizeof("https:://255.255.255.255/")+ sizeof(pageName) + 1];
  snprintf(serverLoc, sizeof(serverLoc), "http://%d.%d.%d.%d%s", ip[0], ip[1], ip[2], ip[3] );

  // redirect only if hostheader is different from server ip
  if (strcmp(serverLoc, webserver->hostHeader().c_str())) {
    webserver->sendHeader(F("Location"), serverLoc, true);
    webserver->send(302, F("text/html"), "");       // Empty content inhibits Content-length header so we have to close the socket ourselves.
    webserver->client().stop();                     // Stop is needed because we sent no content length
    return true;
  }
  return false;
}

OK, non sto capendo proprio nulla...
Mi consigli quindi di abbandonare la mia AsyncElegantOTA e di passare alla tua libreria quindi?
Al momento non ho nient'altro da fare come "web app", quindi potrebbe essere fattibile.
[edit]
L'unico requisito è però di poter aggiornare SPIFFS e Sketch da WEB, mi confermi che con la tua libreria è possibile?
[/edit]

Restando invece sul mio problema delle conoscenze troppo infime della parte di web service, cosa puoi consigliarmi?
Nel senso, credo proprio di non avere le basi per poter programmare delle interfacce WEB su ESPxxxx proprio per quello.
Diciamo che nei miei progetti ho spostato il problema integrando MQTT e facendo le GUI in NodeRED su Raspberry.
Questa curiosità nel riuscire a programmare "Stand-Alone" gli ESPxxxx però mi è sempre rimasta.

Grazie in ogni caso!!
Alan

No, ho messo il come ho fatto io proprio per farti capire il meccanismo cosi da poterlo reimplementare come preferisci.

La libreria io l'ho scritta a mio beneficio perché uso molto spesso webserver integrati nel firmware ESP come interfaccia e mi volevo evitare di fare copia incolla ogni volta, ma ovviamente se la vorrai testare e dare un po' di feedback ne sarò ben lieto :slightly_smiling_face:

Per quanto riguarda l'aggiornamento del firmware c'è un esempio specifico che mi è stato richiesto, ma che al momento è compilabile solo con ESP8266. Non ci sono vincoli tecnici per farlo andare anche con ESP32, appena possibile lo aggiorno. L'avevo già fatto e nemmeno mi ricordavo :rofl:

Per l'aggiornamento del contenuto della flash invece, come ti avevo già scritto è possibile farlo direttamente dal browser andando all'indirizzo http://<il_tuo_indirizzo>/edit

Riguardo i consigli sulla formazione, non saprei che dirti;
Anche io non sono uno sviluppatore web e non ho un background specifico, ma sono tecnologie che mi hanno sempre affascinato molto e quindi cerco di informarmi (principalmente sul web) e provare tutto con progetti pratici. Proprio come ho fatto in questo caso perché non avevo mai approfondito quali sono i meccanismi che fanno aprire il Captive Portal in automatico fino ad ora.

L'esempio incluso nella libreria è stato pensato in questo modo (perché queste erano le richieste):

  • Nella pagina ospitata sul webserver viene impostato un link di verifica firmware che rimanda ad un JSON (nell'esempio fa riferimento direttamente al repository Github dove c'è l'esempio caricato).

  • Nel file JSON ci sono due valori, il link remoto (ma potrebbe essere benissimo un link ad una LAN locale) al file binario .bin del firmware vero e proprio e una stringa che rappresenta la versione.

In questo modo è possibile fare in modo che l'ESP controlli in automatico se ci sono nuove versioni e solo in caso positivo procede all'update. Sarà cura dello sviluppatore ovviamente andare a compilare il nuovo firmware, fare l'upload sul suo servizio di hosting ed aggiornare la versione riportata nel JSON.

Se tu hai bisogno di un funzionamento diverso, fammi sapere.

Allora,
la libreria di partenza fa esattamente ciò che mi serve in 6 righe....
Avendo pochissime competenze web, mi serve una cosa SUPER SEMPLICE, paradossalmente il problema del DNS è secondario avendo comunque due IP statici, mi piaceva l'idea però che l'utente finale non dovesse mettere mano agli IP. (cosa non così problematica eh...)

Restando sul problema della prog. web forse non sono stato chiaro con le domande, e quindi ti rivolgo domande più specifiche e credo personali:

  1. Tutta la parte di funzionamento del "sistema web server", quindi il server in se, i dispositivi che si collegano, le richieste che fanno, ect.... dove l'hai imparata?
  2. Dal momento che capisco la risposta alla domanda prec, è facile incominciare a fare programmazione di quel tipo?

Io non ho idea di come facciano a comunicare due dispositivi in rete, come faccio a creare pagine web, come faccio a gestire i dispositivi. Quindi faccio fatica poi a programmare tutto ciò che deve intrinsecamente gestire tutta l'infrastruttura.

Ovviamente, se vado a guardare esempi, magari ne salto fuori ma senza capirne il motivo, senza capire il funzionamento, e senza essere in grado di arrangiarmi e customizzare le varie applicazioni!

Continuo a ringraziarti per le risposte (ormai tante, anche in vari topic)
Alan

Ora, cerco di farti delle domande per provare a capire un po' di cose... (spero solo di avvicinarmi alla soluzione e di non aumentare la mia già tanta confusione):

  1. In sostanza queste sono le richieste che vengono fatte dal nuovo client appena si connette?
  2. E quindi decido di inoltrare queste richieste alla pagina che voglio io?

Il server DNS:

  • cosa fa?
  • A cosa serve?
  • E' lui che gestisce il captive portal?
  • E' lui che reindirizza ad una pagina?
    Se SI,
  • come fa a reindirizzarmi?
  • e come fa a sapere a quale pagina reindirizzare?

Bene, dopo questa carrellata di domande spero di riuscire ad arrangiarmi esulando dalla strategia implementativa...

Per quanto riguarda la parte del WEB credo rimanderò nuovamente il problema (se ne vengo fuori) ....

Continuo a ringraziarti (spero di poter restituire i favori anche se ne dubito fortemente)
Alan

L'argomento è vasto, spiegarlo in due parole non è semplice e soprattutto di sicuro non sono la persona più "titolata" in merito. Provo comunque a fare un piccolo riepilogo.

I protocolli di comunicazione TCP/IP sono sviluppati secondo un modello organizzato a livelli o layer.

  • Alla base c'è il livello di accesso alla rete ovvero le interfacce fisiche con cui eseguiamo la connessione: WiFi, Ethernet etc etc
  • A seguire c'è Il livello di rete IP - Internet Protocol ovvero tutto ciò che ha il compito di "instradare" correttamente i dati ricevuti verso il layer sottostante e viceversa.
  • Poi si passa al livello di trasporto TCP - Transmission Control Protocol dove ci sono tutti i protocolli necessari per il controllo della trasmissione dei dati
  • Infine c'è il livello di applicazione che è in sostanza dove ci andiamo ad inserire noi con il nostro sketch.

Il livello di applicazione prevede moltissimi protocolli e servizi tra cui quelli di cui abbiamo parlato in questo lungo post :sweat_smile: HTTP, HTTPS, DNS, FTP, etc etc.

cosa fa? / a cosa serve?

Il servizio DNS è responsabile di "risolvere" il nome testuale che digiti nel browser nel corrispondente indirizzo IP fisicamente associato al dispositivo.
Quando nel browser digiti ad esempio https://forum.arduino.cc/ questa stringa verrà inviata ad un server DNS remoto (tipicamente definito all'interno del tuo router) che risponderà con un indirizzo IP corrispondente alla macchina presso cui gira il webserver su cui è ospitato il forum.

Nel momento in cui sei connesso direttamente ad un ESP che fa da Access Point non hai accesso al DNS esterno ovviamente perché l'ESP non è più connesso a sua volta alla rete remota.
Quindi ne devi far girare uno in locale che va a "risolvere" gli indirizzi che digiti nel browser.

La tecnica del Captive Portal è quella di fare in modo che tutte le richieste web (siano esse digitate manualmente nel browser o richieste in automatico da un servizio del sistema operativo) vengano inoltrate all'indirizzo IP dell'ESP in modo che qualcuno possa "rispondere".

Di tutto questo se ne occupa l'oggetto DNSServer dnsServer; e il suo compito finisce qui.

E' lui che gestisce il captive portal?

No.

E' lui che reindirizza ad una pagina? come fa a reindirizzarmi?

No, il DNS informa il client su quale indirizzo IP vanno instradate le richieste. In pratica è un lungo elenco di coppie "nome di dominio" -> " indirizzo IP"

// Ciascun dominio ha il suo indirizzo IP
google.com       -> 172.217.0.46
forum.arduino.cc -> 184.104.202.141
etc etc

Il DNS che facciamo girare sull'esp invece è configurato ad esempio in questo modo (l'indirizzo IP sarà ovviamente quello corrispondente all'esp).

 // Tutte le richiesta verranno inoltrare all'indirizzo a prescindere dal dominio
* -> 192.168.4.1 // Indirizzo IP di default dell'ESP.

La risposta vera e propria invece sarà fornita dall'oggetto "web server" che implementa il servizio a livello applicazione HTTP .
Nel caso della prima libreria è realizzato con la classe C++ ESPAsyncWebServer.

Nella mia libreria io invece ho scelto di usare la classe già inclusa nel core ESP per Arduino ESP8266WebServer con MCU ESP8266 e WebServer con le ESP32 per non avere troppe dipendenze esterne.

E' questo oggetto che si occupa di mandare tutti i dati HTTP e quindi anche la pagina del Captive Portal.
Se guardi il metodo bool captivePortal(), aldilà del controllo iniziale per vedere se è il caso o meno di reindirizzare, puoi vedere che alla fine al client viene inviata la risposta HTTP 302

come fa a sapere a quale pagina reindirizzare?

Il codice di stato HTTP 302 informa il client che la risorsa richiesta esiste ma è stata spostata ad un altro indirizzo che noi abbiamo definito nella variabile "serverLoc" e che nel nostro caso punterà alla pagina di setup.

I codici di stato HTTP sono tanti come puoi vedere nel link; ad esempio uno che avrai visto sicuramente è il classico HTTP 404 Not Found quando metti un indirizzo che non esiste sul web server.

Spero di non aver aggiunto altra confusione :sweat_smile:, ma come dicevo l'argomento merita il giusto tempo ed impegno per essere sviscerato.

Assolutamente no! Anzi, finalmente un po' di chiarezza....

Il captive portal, funziona grazie al fatto che è il SO del client ad inviare delle specifiche richieste al server giusto? che sarebbero: "\redirect" "\connecttest.txt" "\msdownload" ad esempio?

Ancora grazie, sei stato super gentile e disponibile oltre ad essere competente....

Alan

Se intendi l'apertura automatica del browser si.
Il service che gira sul SO invia le richieste e se c'è risposta apre il browser di default all'indirizzo indicato.

Caro @cotestatnt ti annuncio che ho fatto esattamente ciò di cui avevo bisogno!

Nuovo problema!!
Se alla rete del mio ESP32 ne collego un altro che però ha IP statico crasha tutto!!
Il mio obiettivo è quello di creare appunto UNA rete, e una sola pagina web con due pulsanti, uno che reindirizza a "\update" sul server AP, mentre l'altro dovrebbe reindirizzare a "192.168.1.2\update" ma non funziona più nulla appena la seconda scheda si collega!
Problema di DHCP o di DNS?
Si può fare ciò che dico o mi faccio due reti e via, magari con SSID nascosto?

Alan

Non mi è chiara la topologia della rete che vuoi fare.
Riusciresti a fare uno schema grafico con funzioni ed indirizzi dei dispositivi?

Con ESP32/ESP8266 puoi anche valutare il protocollo proprietario ESP-NOW pensato proprio per far dialogorare tra loro più schede Espressif

Allora, contestualizzo un po' per dare un'idea:
ho due schede, una pulsantiera ed un tabellone segnapunti;
la prima invia via ESP-NOW gli stati dei pulsanti fisici che legge, il tabellone li riceve, li elabora modificando i punteggi e spedisce sempre via ESP-NOW dei valori per controllare dei led fisici sulla puls.

Questo era fatto precedentemente tramite WiFiServer e WiFiClient, solo che era lento e mi dava dei problemi e sono passato a ESP-NOW.

Nel mentre ho scoperto la possibilità di flashare direttamente da WiFi senza dover collegarmi fisicamente alla scheda che nel caso di un tabellone segna punti di una palestra non è male essendo appeso!

Ora sto facendo esattamente questo:
Creo una rete con il tabellone (IP statico: 192.168.1.1) alla quale mi vorrei collegare con la pulsantiera (IP statico: 192.168.1.2) per poter hostare le pagine di aggiornamento del firmware!
Il WiFi lo uso solo per l'aggiornamento dei programmi!

Troppo contorta come cosa?

Ma se si tratta solo di flashare l'ESP poco raggiungibile che è anche quello che fa da AP, a questo punto ti conviente usare la libreria ArduinoOTA

Quando è necessario fare l'upload del nuovo firmware ti colleghi con il PC alla rete WiFi del tabellone e fai l'upload wireless direttamente. Fine, non serve altro.

No no, tutto sto sproloquio per niente, ora vogliamo (it's must) il captive portal, che tradotto sarebbe portamelo cattivo. :grinning:

Ciao.