DÍA 227 DE 365

Fase 4: FreeRTOS y Conectividad IoT

📖 Teoría a Estudiar

El Protocolo WebSocket (WS/WSS)

A pesar del nombre, WebSockets no es HTTP. Es un protocolo independiente diseñado para superar la ineficiencia de HTTP en transmisiones en tiempo real.

El ciclo de vida de un WebSocket consta de dos fases:

  1. El Handshake (Apretón de Manos): El celular se conecta al ESP32 haciendo una petición HTTP normal, pero le inyecta un encabezado especial: Connection: Upgrade y Upgrade: websocket. Le está diciendo a tu ESP32: "Dejemos de hablar HTTP y cambiemos a un protocolo más rápido".
  2. El Túnel Bi-direccional (Full-Duplex): Si el ESP32 acepta, responde HTTP 101 Switching Protocols. En ese instante, HTTP "muere". La conexión se convierte en un tubo directo de sockets TCP (como el UART). Ya no hay encabezados masivos ni peticiones "GET". El ESP32 puede disparar datos (Frames) al celular en cualquier milisegundo sin que el celular se los pida.

El Reto en FreeRTOS: Async Push

En el framework ESP-IDF, el Servidor HTTP corre en una Tarea de FreeRTOS (Core 0). Pero las ventas fluyen desde tu STM32 hacia la Tarea UART (Core 1). No puedes simplemente enviar un paquete al socket del celular desde la tarea UART porque causarías un Crash de concurrencia. Debes utilizar la función httpd_ws_send_frame_async(), que delega la escritura del paquete de forma segura a la Tarea del Servidor Web.

STREAMING LOCAL: HTTP POLLING VS WEBSOCKETS HTTP POLLING (Asfixia el ESP32) App Celular ESP32 Server GET /api/litros (Abrir TCP) 200 OK: {"L":1.5} (Cerrar TCP) GET /api/litros (Abrir TCP) 200 OK: {"L":2.0} (Cerrar TCP) GET /api/litros (Abrir TCP) 200 OK: {"L":2.5} (Cerrar TCP) Alto Overhead de Red (Crashea el Router) WEBSOCKETS (Túnel Persistente) App Celular ESP32 WS Server Handshake HTTP (101 Switching) CANAL TCP ABIERTO Y DIRECTO PUSH: {"L":1.5} PUSH: {"L":2.0} PUSH: {"L":2.5} PUSH: {"L":3.0} Latencia Milimétrica. Sin Overhead.

⚙️ Ejercicios Prácticos

Circuito Lógico: Recordemos el Dual-Core (Día 223). El STM32 envía las tramas de venta constante por UART. La Tarea UART (Core 1) las recibe, y las pasa mediante una Queue hacia el Core 0. El Core 0 ahora no solo las mandará a AWS, sino que hará un "Broadcast" (Difusión) empujándolas a todos los celulares conectados localmente por WebSockets.

Ejercicio 1: Registraremos un URI de tipo WebSocket en nuestro `esp_http_server` y crearemos la función asíncrona para inyectar datos en la conexión desde otra Tarea de FreeRTOS.


/**
 * @file ws_telemetry.c
 * @brief Servidor WebSocket para Streaming Local Zero-Latency
 */

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

// Variables Globales para WebSockets
static httpd_handle_t global_server = NULL;
static int ws_client_fd = -1; // Guardamos el "File Descriptor" del celular conectado

// =======================================================
// 1. MANEJADOR DEL WEBSOCKET (URI Handler)
// =======================================================
esp_err_t websocket_handler(httpd_req_t *req) {
    
    // Fase 1: El Handshake (La primera conexión)
    if (req->method == HTTP_GET) {
        printf("[WS] ¡Celular Conectado! Handshake 101 completado.\n");
        // Guardamos el ID de este cliente para poder "Empujarle" datos más tarde
        ws_client_fd = httpd_req_to_sockfd(req);
        return ESP_OK;
    }

    // Fase 2: Recepción de datos (Si el celular envía comandos a la máquina)
    httpd_ws_frame_t ws_pkt;
    uint8_t buf[128] = {0};
    memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
    ws_pkt.type = HTTPD_WS_TYPE_TEXT;
    ws_pkt.payload = buf;

    // Leemos lo que nos mandó el navegador JS
    esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 128);
    if (ret == ESP_OK) {
        printf("[WS] Recibido de celular: %s\n", ws_pkt.payload);
        // Aquí podríamos recibir comandos manuales como {"cmd":"corte_valvula"}
    }
    return ret;
}

// =======================================================
// 2. FUNCIÓN DE DIFUSIÓN (ASYNC PUSH)
// Llamada desde la Tarea de UART (Core 1) cuando el STM32 manda una trama nueva
// =======================================================
/**
 * @brief Empuja un texto plano (JSON) directamente al celular conectado.
 */
void Transmitir_Telemetria_WS(const char* json_data) {
    
    // Si el servidor web no está activo o no hay nadie viendo el Dashboard, abortamos
    if (global_server == NULL || ws_client_fd == -1) return;

    httpd_ws_frame_t ws_pkt;
    memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
    ws_pkt.payload = (uint8_t*)json_data;
    ws_pkt.len = strlen(json_data);
    ws_pkt.type = HTTPD_WS_TYPE_TEXT;

    // MAGIA ESP-IDF: No podemos usar httpd_ws_send_frame normal porque esta función 
    // está siendo llamada por la Tarea de UART (Core 1), y chocaríamos con la 
    // memoria de la Tarea HTTPD (Core 0).
    // Usamos _async, el cual encola el trabajo de forma Thread-Safe hacia el Core 0.
    
    esp_err_t ret = httpd_ws_send_frame_async(global_server, ws_client_fd, &ws_pkt);
    
    if (ret != ESP_OK) {
        // Si el envío falla, asumimos que el celular se alejó o se apagó
        printf("[WS] El cliente se desconectó.\n");
        ws_client_fd = -1;
    }
}

// =======================================================
// 3. REGISTRO EN EL SERVIDOR (Inicialización)
// =======================================================
void registrar_websocket(httpd_handle_t server) {
    global_server = server; // Guardamos el puntero global

    httpd_uri_t ws_uri = {
        .uri        = "/ws",
        .method     = HTTP_GET,
        .handler    = websocket_handler,
        .user_ctx   = NULL,
        .is_websocket = true // <--- Obligatorio para activar el protocolo 101
    };
    httpd_register_uri_handler(server, &ws_uri);
    printf("[WS] Socket Bidireccional Registrado en ruta /ws\n");
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Calibración en Tiempo Real y Panel "Headless" Síncrono.

Por ley, los medidores de flujo másico de Gas LP deben calibrarse mecánicamente periódicamente ("Jarreo"). El técnico oficial (UVA) debe pararse junto al dispensador físico, inyectar gas en un recipiente graduado y comparar visualmente los litros que marca la pantalla LCD física del Despachador contra los litros reales en su bidón.

Si la pantalla física de tu máquina se quiebra, la gasolinera tendría que detener operaciones. Al implementar **WebSockets en el ESP32**, has convertido cualquier teléfono inteligente en un Clon Inalámbrico de la pantalla física de la máquina. El técnico puede estar parado a 5 metros de distancia sosteniendo el bidón. El STM32 lee los pulsos del medidor I2C, se los pasa al ESP32 (Core 1) por el protocolo binario, y el ESP32 (Core 0) usa la función `httpd_ws_send_frame_async` para disparar el JSON `{"litros": 20.05}` al navegador del teléfono del técnico. Gracias al WebSocket, los números en la pantalla del celular giran y se actualizan cada 50 milisegundos fluidamente, sin recargas web, con latencia nula. Acabas de crear un **HMI (Human-Machine Interface) Inalámbrico y Headless** de grado industrial.

📝 Resumen del Día