DÍA 204 DE 365

Fase 4: FreeRTOS y Conectividad IoT

📖 Teoría a Estudiar

El Latido Activo vs. El Latido Pasivo

¿Cómo sabe nuestro STM32 que el ESP32 perdió la conexión a Internet?

El Algoritmo de "Backoff Exponencial"

Si la red se cae, el instinto novato es poner un while(1) que intente reconectar a máxima velocidad. Si haces esto, tu microcontrolador bombardeará de peticiones al router. Si hay 20 despachadores de gas haciendo lo mismo, tumbarán al router por ataque de saturación (DDoS interno).
La solución industrial es el Backoff Exponencial: Si la reconexión falla, esperas 1 segundo. Si vuelve a fallar, esperas 2 segundos. Luego 4, 8, 16... hasta un máximo razonable (ej. 1 minuto). Esto le da tiempo al router de la gasolinera de reiniciarse tranquilamente sin ser asfixiado por las máquinas.

BUCLE DE SUPERVIVENCIA DE RED (SELF-HEALING AUTOCONNECT) 1. OPERACIONAL (Enviando Telemetría) 2. LINK CHECK AT+PING="AWS" 3. LINK PERDIDO Fallo detectado 4. RECONECTAR Espera Exponencial Timer: Cada 30 seg Ping OK Timeout o ERROR Incrementar Retry_Count Conexión Restaurada Fallo: Espera 1s -> 2s -> 4s... HARD RESET (EN=0) Retry > MAX Módulo reiniciado. Empezar de cero.

⚙️ Ejercicios Prácticos

Circuito: Mantenemos el STM32 (UART TX/RX) conectado al ESP32. El pin de control PIN_ESP32_EN sigue conectado a la alimentación del módem para hacerle un Power Cycle (Apagado forzoso) en caso de que el ESP32 se niegue a responder a los pings por completo.

Ejercicio 1: Evolucionaremos la FSM del Día 203. Agregaremos el estado ESTADO_VERIFICAR_RED que despertará periódicamente para interrogar al ESP32. También implementaremos el Backoff Exponencial calculando el osDelay en base al número de intentos fallidos.


/**
 * @file wifi_autoconnect_rtos.c
 * @brief FSM Avanzada con Heartbeat Activo y Backoff Exponencial para ESP-AT
 */

#include "stm32f401xe.h"
#include "cmsis_os2.h"

// Estados de la FSM de Red
typedef enum {
    ESTADO_WIFI_HARD_RESET,
    ESTADO_WIFI_INIT,
    ESTADO_WIFI_CONECTAR,
    ESTADO_WIFI_OPERACIONAL,
    ESTADO_WIFI_VERIFICAR,     // Nuevo: Monitoreo Activo
    ESTADO_WIFI_RECONECTAR     // Nuevo: Manejo de Caídas
} FSM_WiFi_t;

// Función de envío AT segura (Implementada en el Día 203)
extern bool AT_Enviar_Comando(const char* comando, uint32_t timeout_ms);

// =======================================================
// LA TAREA DE RED (Prioridad Baja - Tolerante a Fallos)
// =======================================================
void Thread_WiFi_Modem(void *argument) {
    
    FSM_WiFi_t estado_actual = ESTADO_WIFI_HARD_RESET;
    
    uint32_t intentos_fallidos = 0;
    uint32_t tiempo_backoff_ms = 1000; // Inicia esperando 1 segundo
    
    // Variables para el Timer de Monitoreo
    uint32_t ultimo_ping_tick = osKernelGetTickCount();
    const uint32_t INTERVALO_PING_MS = 30000; // Verificar cada 30 segundos
    
    for (;;) {
        switch (estado_actual) {
            
            case ESTADO_WIFI_HARD_RESET:
                UART_Enviar_String("[WIFI] Cortando energía al ESP32...\r\n");
                HAL_GPIO_WritePin(GPIOB, PIN_ESP32_EN, GPIO_PIN_RESET);
                osDelay(1000); 
                HAL_GPIO_WritePin(GPIOB, PIN_ESP32_EN, GPIO_PIN_SET);
                osDelay(2500); // Tiempo de Boot del ESP32
                
                intentos_fallidos = 0;
                tiempo_backoff_ms = 1000;
                estado_actual = ESTADO_WIFI_INIT;
                break;
                
            case ESTADO_WIFI_INIT:
                if (AT_Enviar_Comando("AT\r\n", 500) && AT_Enviar_Comando("ATE0\r\n", 500)) {
                    estado_actual = ESTADO_WIFI_CONECTAR;
                } else {
                    estado_actual = ESTADO_WIFI_HARD_RESET;
                }
                break;
                
            case ESTADO_WIFI_CONECTAR:
                UART_Enviar_String("[WIFI] Intentando Conexion AWS...\r\n");
                // Comando ficticio unificado de conexión
                if (AT_Enviar_Comando("AT+MQTT_CONNECT_ALL\r\n", 10000)) {
                    UART_Enviar_String("[WIFI] ¡Enlace Operacional!\r\n");
                    intentos_fallidos = 0; // Reseteamos la penalización
                    tiempo_backoff_ms = 1000;
                    ultimo_ping_tick = osKernelGetTickCount();
                    estado_actual = ESTADO_WIFI_OPERACIONAL;
                } else {
                    estado_actual = ESTADO_WIFI_RECONECTAR;
                }
                break;
                
            case ESTADO_WIFI_OPERACIONAL:
                // Tarea Principal: Enviar telemetría si existe...
                // Procesar_Mailbox_Y_Enviar();
                
                // ¿Ya pasaron 30 segundos desde el último check?
                if ((osKernelGetTickCount() - ultimo_ping_tick) >= INTERVALO_PING_MS) {
                    estado_actual = ESTADO_WIFI_VERIFICAR;
                }
                
                osDelay(100); // Dormir para no ahogar la CPU
                break;
                
            case ESTADO_WIFI_VERIFICAR:
                // Monitoreo Activo: Preguntarle al ESP32 si AWS sigue vivo
                // AT+CIPSTATUS o AT+PING
                if (AT_Enviar_Comando("AT+PING=\"8.8.8.8\"\r\n", 3000)) {
                    // La red vive. Regresamos a operación normal.
                    ultimo_ping_tick = osKernelGetTickCount();
                    estado_actual = ESTADO_WIFI_OPERACIONAL;
                } else {
                    UART_Enviar_String("[WIFI-ALERTA] ¡Caída de Red Detectada!\r\n");
                    estado_actual = ESTADO_WIFI_RECONECTAR;
                }
                break;
                
            case ESTADO_WIFI_RECONECTAR:
                intentos_fallidos++;
                
                if (intentos_fallidos > 6) {
                    // Ya intentamos demasiado, el ESP32 podría estar colgado de memoria.
                    // Aplicar la "Opción Nuclear".
                    estado_actual = ESTADO_WIFI_HARD_RESET;
                } else {
                    // BACKOFF EXPONENCIAL
                    // Espera: 1s, 2s, 4s, 8s, 16s...
                    char msg[64];
                    snprintf(msg, sizeof(msg), "[WIFI] Intento %lu fallido. Esperando %lu ms...\r\n", intentos_fallidos, tiempo_backoff_ms);
                    UART_Enviar_String(msg);
                    
                    osDelay(tiempo_backoff_ms); 
                    
                    tiempo_backoff_ms *= 2; // Duplicamos el castigo
                    
                    estado_actual = ESTADO_WIFI_CONECTAR;
                }
                break;
        }
    }
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Autonomía de Flota M2M (Machine-to-Machine) en Redes Inestables.

Imagina que instalas 50 Despachadores de Gas LP en zonas rurales de México donde el proveedor de Internet sufre de intermitencias diarias. Si tu firmware de red fuera estático, tendrías que pagarle a un técnico para que viajara cientos de kilómetros solo para presionar el botón de "Reset" de las máquinas cada vez que el internet parpadee. Una pesadilla logística y financiera.

Al implementar el **Bucle de Self-Healing con Backoff Exponencial**, has dotado a tu máquina de inteligencia artificial básica de supervivencia. Si un operador en la oficina corta la luz de la gasolinera por accidente y el router se apaga, la máquina de estados del STM32 detectará el fallo en su siguiente "Link Check". Automáticamente pasará a reconexión. Al aplicar el retraso exponencial, asegura que tu flota de 50 despachadores no inunde al pobre router con 50 peticiones simultáneas cuando vuelva a encender, permitiéndole asignar IPs ordenadamente. Todo esto ocurre mientras tu Tarea de Metrología (Día 199) sigue midiendo gas con precisión milimétrica, sin inmutarse por la guerra cibernética que libra el STM32 en segundo plano. Tu máquina ahora exige cero mantenimiento de red.

📝 Resumen del Día