Con questo articolo voglio iniziarne una serie dedicata ad alcune delle innumerevoli possiblità di networking che possiamo sfruttare con un ESP32 e le giuste librerie.

Io li chiamo “frammenti”, piccoli progetti che svolgono una singola e specifica funzione, da riutilizzare in progetti più complessi al presentarsi di quella necessità. Con il progetto di oggi andiamo a vedere come realizzare un semplice Captive portal.

Ma cos’è un captive portal? come riporta wikipedia:

Un Captive portal è una pagina web che viene mostrata agli utenti di una rete di telecomunicazioni quando tentano di connettersi ad Internet mediante una richiesta http del loro browser.[1] Questo meccanismo forza la visualizzazione di tale pagina (usualmente finalizzata all’autenticazione degli utenti) prima di poter accedere alle risorse internet, tipicamente la fruizione del web.

La classica modalità che sicuramente vi sarà capitata colegandovi ad hotpost pubblici (in un ristorante, un hotel, ecc) che, prima di permettervi di iniziare a navigare, dirotta il vostro browser su una pagina custom del gestore della rete.

Realizzare un captive portal con ESP32 può essere molto utile perchè ci permette di leggere i valori di sensori, controllare luci, inviare comandi, modificare configurazioni, direttamente dallo smartphone (o dal pc) e senza altri dispositivi intermedi o connessione internet. L’ESP32 in questo caso funge da router wifi al quale collegarsi.

Il codice è molto semplice ed autoesplicativo, ma entrano in gioco diverse librerie:

  • WiFi: la prima cosa da fare è ovviamente attivare il wifi il modalità WIFI_AP, ovvero access point, per permetterci di collegarci all’ESP32.
  • DNSServer e ESPmDNS: andiamo poi ad avviare un server DNS, necessario per intercettare tutte le richieste e reindirizzarle verso la pagina web ospitata sul nostro ESP32
  • AsyncTCP ed ESPAsyncWebServer: il server web che gestisce le richieste e restituisce le nostre pagine.

Queste ultime due sono librerie esterne, quindi le includiamo nel file di configurazione platformio.ini (oppure le scarichiamo dal gestore delle librerie nel caso di Arduino IDE):

lib_deps      = AsyncTCP
                ESP Async WebServer

Quando il codice viene eseguito, viene resa disponibile una rete wifi chiamata ZioTesterLab al quale connettersi senza password. Una volta effettuata la connessione con lo smartphone, ci verrà suggerito di aprire la pagina del captive portal.

Di seguito riporto il codice completo, che trovi anche all’interno del repository ESP32-networking pronto da essere clonato e compilato con Platform.IO. Informazioni dettagliate su come clonare un repository remoto le trovi nell’apposito articolo.

Non aggiungo altro perchè mi sembra tutto abbastanza semplice… ma nel caso, sono a disposizione nei commenti! :smile:

#include <Arduino.h>
#include <AsyncTCP.h>
#include <DNSServer.h>
#include <ESPmDNS.h>
#include <WiFi.h>

#include "ESPAsyncWebServer.h"

const byte DNS_PORT = 53;
IPAddress apIP(8, 8, 8, 8);
IPAddress subnet(255, 255, 255, 0);

DNSServer dnsServer;
AsyncWebServer server(80);

class CaptiveRequestHandler : public AsyncWebHandler {
 public:
  CaptiveRequestHandler() {
    server.on("/app", HTTP_GET, [](AsyncWebServerRequest *request) {
      request->send(200, "text/plain", "Hello, world");
    });
  }
  virtual ~CaptiveRequestHandler() {}

  bool canHandle(AsyncWebServerRequest *request) {
    // request->addInterestingHeader("ANY");
    return true;
  }

  void handleRequest(AsyncWebServerRequest *request) {
    AsyncResponseStream *response = request->beginResponseStream("text/html");
    response->print("<!DOCTYPE html><html><head><title>Captive Portal</title></head><body>");
    response->print("<p>This is out captive portal front page.</p>");
    response->printf("<p>You were trying to reach: http://%s%s</p>", request->host().c_str(), request->url().c_str());
    response->printf("<p>Try opening <a href='http://%s/app'>this link</a> instead</p>", WiFi.softAPIP().toString().c_str());
    response->print("</body></html>");
    request->send(response);
  }
};

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_AP);
  WiFi.softAP("ZioTesterLab");
  WiFi.softAPConfig(apIP, apIP, subnet);
  dnsServer.start(DNS_PORT, "*", WiFi.softAPIP());

  if (!MDNS.begin("ziotester")) {
    Serial.println("Error starting mDNS");
    return;
  }

  server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER);
  server.begin();
  IPAddress IP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(IP);
}

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