DÍA 224 DE 365

Fase 4: FreeRTOS y Conectividad IoT

📖 Teoría a Estudiar

El Truco Psicológico: Secuestro de DNS (DNS Spoofing)

¿Alguna vez te has conectado al WiFi de un Starbucks o un aeropuerto y tu celular abre una página web automáticamente pidiéndote aceptar los términos y condiciones? Ese es un Portal Cautivo.

En el ESP32 con ESP-IDF, este flujo de aprovisionamiento de WiFi no es magia, requiere tres componentes orquestados simultáneamente en el Core 0:

  1. Modo AP (Access Point): El ESP32 apaga su antena receptora y enciende un transmisor creando la red abierta "DISPENSADOR_SETUP". Se asigna a sí mismo la IP local 192.168.4.1.
  2. El Secuestrador DNS (Puerto 53 UDP): Cuando el teléfono del técnico se conecta a la máquina, el sistema operativo del teléfono (iOS o Android) intenta probar si hay internet haciendo ping a páginas como captive.apple.com o connectivitycheck.gstatic.com. Tu ESP32 intercepta esa pregunta y miente descaradamente: responde "Sí, esa página soy yo, mi IP es 192.168.4.1".
  3. Servidor HTTP Asíncrono (Puerto 80 TCP): Al ser engañado por el DNS, el celular abre un mini-navegador y solicita la página web. El ESP32 le devuelve un formulario HTML limpio donde el técnico puede escribir la contraseña del WiFi de la gasolinera.

NVS: Memoria a Prueba de Apagones

Cuando el técnico pulsa "Guardar", el celular hace una petición HTTP POST. El ESP32 atrapa los datos y los graba permanentemente en el NVS (Non-Volatile Storage). Luego de guardar, el ESP32 se reinicia, lee la memoria NVS, pasa a modo STA (Station/Cliente) y se conecta victoriosamente a la red de la gasolinera.

ARQUITECTURA DE PROVISIONAMIENTO: EL PORTAL CAUTIVO Setup Gas LP SSID Gasolinera Password CONECTAR ESP32 (Modo Access Point) IP: 192.168.4.1 | Red: "DISPENSADOR_SETUP" DNS Server Puerto 53 (UDP) Secuestra dominios HTTP Server Puerto 80 (TCP) Sirve HTML / Recibe POST NVS Flash Memoria Física (Sobrevive al reinicio) 1. Se conecta al WiFi "DISPENSADOR..." 2. DNS: ¿IP de apple.com? R= ¡Soy yo! (192.168.4.1) 3. HTTP GET (Saca el Pop-up) R= Formulario HTML Web 4. HTTP POST (SSID + Pass) 5. Save 6. esp_restart()

⚙️ Ejercicios Prácticos

Configuración ESP-IDF: ESP-IDF incluye un componente muy robusto llamado esp_http_server. Es ligero, corre sobre LwIP y está diseñado específicamente para servir páginas web asíncronas desde la memoria ROM sin bloquear el procesador.

Ejercicio 1: En el Core 0 de nuestro ESP32, programaremos los "Manejadores de Petición" (URI Handlers). Uno responderá al GET enviando la página web en texto crudo, y otro atrapará el POST cuando el técnico le dé al botón de Guardar.


/**
 * @file captive_portal.c
 * @brief Servidor HTTP Asíncrono para Aprovisionamiento Local (Zero-Touch)
 */

#include <string.h>
#include "esp_wifi.h"
#include "esp_http_server.h"
#include "nvs_flash.h"

// =======================================================
// LA PÁGINA WEB (HTML/CSS) HARDCODEADA EN MEMORIA FLASH
// =======================================================
// Al usar 'const char', forzamos al compilador a guardar este texto en la memoria 
// ROM del ESP32, salvando valiosa RAM.
const char* html_setup_page = 
    "<!DOCTYPE html><html><head><meta name='viewport' content='width=device-width, initial-scale=1'>"
    "<style>body{font-family:Arial; background:#2c3e50; color:#fff; text-align:center; padding:50px;}"
    "input{width:80%; padding:10px; margin:10px 0; border-radius:5px; border:none;}"
    "button{background:#e67e22; color:#fff; padding:15px; border:none; border-radius:5px; width:85%; font-weight:bold;}</style></head>"
    "<body><h2>Configuración de Red</h2><p>Despachador Gas LP</p>"
    "<form action='/guardar' method='POST'>"
    "<input type='text' name='ssid' placeholder='Nombre del WiFi (SSID)' required><br>"
    "<input type='password' name='pass' placeholder='Contraseña' required><br><br>"
    "<button type='submit'>GUARDAR Y CONECTAR</button>"
    "</form></body></html>";


// =======================================================
// MANEJADORES HTTP (ROUTERS)
// =======================================================

// 1. Manejador del Método GET (Sirve la página cuando el celular se conecta)
esp_err_t portal_get_handler(httpd_req_t *req) {
    // Definir el tipo de contenido como HTML
    httpd_resp_set_type(req, "text/html");
    
    // Enviar el HTML hardcodeado. HTTP-Server del ESP calcula el Content-Length automáticamente.
    httpd_resp_send(req, html_setup_page, strlen(html_setup_page));
    return ESP_OK;
}

// 2. Manejador del Método POST (Recibe los datos del formulario web)
esp_err_t portal_post_handler(httpd_req_t *req) {
    char buf[100];
    int ret = httpd_req_recv(req, buf, sizeof(buf));
    
    if (ret <= 0) return ESP_FAIL;
    buf[ret] = '\0'; // Asegurar terminador nulo

    // Parseo básico de URL Encoded ("ssid=MiWiFi&pass=1234")
    // En código de producción usaríamos una función robusta de parseo de strings.
    char ssid_local[32];
    char pass_local[64];
    // ... Lógica de extracción de 'buf' hacia ssid_local y pass_local omitida por brevedad ...

    printf("[PORTAL] Recibidas nuevas credenciales. Guardando en memoria NVS...\n");

    // Abrimos la "Bóveda" de NVS en modo lectura/escritura
    nvs_handle_t nvs_handle;
    ESP_ERROR_CHECK(nvs_open("storage", NVS_READWRITE, &nvs_handle));
    
    // Grabamos los strings
    nvs_set_str(nvs_handle, "wifi_ssid", ssid_local);
    nvs_set_str(nvs_handle, "wifi_pass", pass_local);
    nvs_commit(nvs_handle); // Commit obliga a la Flash física a escribirse
    nvs_close(nvs_handle);

    // Responderle al navegador del usuario
    httpd_resp_send(req, "Guardado exitosamente. La maquina se reiniciara.", HTTPD_RESP_USE_STRLEN);

    // Damos tiempo a que el navegador reciba la página de éxito y reiniciamos
    vTaskDelay(pdMS_TO_TICKS(2000));
    esp_restart();
    
    return ESP_OK;
}

// =======================================================
// ARRANQUE DEL SERVIDOR (Llamado desde app_main si NVS está vacío)
// =======================================================
httpd_handle_t iniciar_servidor_web(void) {
    
    httpd_handle_t server = NULL;
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    
    // Mapeo de Rutas (URI)
    httpd_uri_t uri_get = { .uri = "/", .method = HTTP_GET, .handler = portal_get_handler, .user_ctx = NULL };
    
    // Capturamos el POST del formulario
    httpd_uri_t uri_post = { .uri = "/guardar", .method = HTTP_POST, .handler = portal_post_handler, .user_ctx = NULL };
    
    // MAGIA DNS SPOOFING: Hacemos que CUALQUIER error de página 404 redireccione al portal
    // config.uri_match_fn = httpd_uri_match_wildcard; // Requiere config DNS interna extra
    
    if (httpd_start(&server, &config) == ESP_OK) {
        httpd_register_uri_handler(server, &uri_get);
        httpd_register_uri_handler(server, &uri_post);
        printf("[PORTAL] Servidor Web de Configuración Local iniciado en Puerto 80.\n");
    }
    
    return server;
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Aprovisionamiento de despliegue sin contacto (Zero-Touch Provisioning) y Plug & Play.

En el modelo arcaico, instalar un equipo IoT requería que el técnico llevara una laptop, instalara drivers USB a Serial, conectara un cable físicamente a la tarjeta lógica y usara una terminal de comandos (Putty) para inyectar la clave del WiFi de la gasolinera. Un proceso que tardaba 20 minutos, costaba dinero en capacitación del personal, y si se cambiaba el módem meses después, había que enviar al técnico de nuevo.

Al implementar el **Portal Cautivo de Aprovisionamiento**, has elevado tu máquina a un estándar comercial "Plug & Play", al nivel de los focos inteligentes de Philips o los altavoces de Amazon Echo. Cuando el técnico energiza el Despachador de Gas LP, la máquina verifica su memoria NVS. Al verla vacía, enciende automáticamente su antena como *Access Point* y crea un escudo local de red. El técnico saca su smartphone, se conecta al WiFi de la máquina, y gracias al secuestro de servidor DNS, una ventana emergente profesional (HTML) aparece en su pantalla. Ingresa los datos, la máquina los sella en el silicio, se reinicia como Estación Cliente (STA) y contacta a AWS de inmediato. El costo de instalación, la fricción del usuario y el tiempo de despliegue se reducen a segundos.

📝 Resumen del Día