DÍA 203 DE 365

Fase 4: FreeRTOS y Conectividad IoT

📖 Teoría a Estudiar

Anatomía de una FSM (Finite State Machine)

Una Máquina de Estados Finita es un modelo matemático donde el sistema solo puede existir en un estado a la vez. Dependiendo de los eventos externos (como recibir un "OK" o sufrir un Timeout), la máquina evalúa a qué estado debe transicionar a continuación.

En el contexto de nuestra Tarea IoT (WiFi), no podemos escribir código secuencial de arriba hacia abajo esperando conectar. Debemos estructurarlo como un switch(estado). Si estamos en el estado ESTADO_CONECTAR_WIFI, enviamos el comando AT+CWJAP y esperamos. Si falla 3 veces seguidas, pasamos al ESTADO_ERROR_FATAL, donde podríamos encender un pin para apagar físicamente la energía del ESP32 y forzar un reinicio físico (Hard Reset).

La Magia No Bloqueante: Event Flags + UART ISR

¿Cómo sabemos que el ESP32 respondió "OK"?
En la interrupción (ISR) del puerto UART1 del STM32, iremos metiendo los caracteres que llegan en un buffer. Si detectamos los caracteres 'O' y luego 'K', la propia interrupción disparará un Event Flag (Día 195) llamado FLAG_AT_OK.
Nuestra Tarea de Estado Principal simplemente llamará a osEventFlagsWait(flags, FLAG_AT_OK, osWaitForever, timeout_ms). Durante esos milisegundos de espera, la tarea consumirá 0% de CPU, dejando el procesador libre para medir gas.

MÁQUINA DE ESTADOS (FSM) DEL DRIVER AT WIFI INIT Send: AT WIFI_CONN AT+CWJAP TCP_CONN AT+CIPSTART IDLE_SEND AT+CIPSEND (Despachador en Línea) HARD RESET Cortar Energía OK OK OK (Conectado a AWS) Timeout (Retry < 3) Falla INIT Falla WiFi Falla TCP CLOSED (Conexión Perdida) 1 seg. de apagado, reanuda

⚙️ Ejercicios Prácticos

Circuito: El STM32 y el ESP32 operan a nivel lógico de 3.3V, por lo que puedes conectar sus UARTs directamente de forma cruzada (TX_STM a RX_ESP y viceversa). Sin embargo, la magia de la resiliencia física consiste en conectar un PIN GPIO del STM32 a la base de un Transistor NPN (o un pin Enable del ESP) que controle la alimentación de 3.3V del ESP32. Si el ESP32 se bloquea por completo, el STM32 apagará el pin, cortará la corriente al ESP32 por 1 segundo (Hard Reset) y lo resucitará de entre los muertos limpio y fresco.

Ejercicio 1: Implementaremos el esqueleto en C de nuestra FSM. Crearemos la función de espera bloqueante inteligente AT_Enviar_Comando utilizando CMSIS-OS v2, y la Tarea de Control Principal.


/**
 * @file driver_esp_at.c
 * @brief Máquina de Estados para Control de ESP32 AT Firmware mediante RTOS
 */

#include "stm32f401xe.h"
#include "cmsis_os2.h"
#include <string.h>

// Banderas de Eventos alimentadas por la ISR del UART (No incluidas aquí)
#define FLAG_AT_OK       (1U << 0)
#define FLAG_AT_ERROR    (1U << 1)

extern osEventFlagsId_t uart_rx_flags_id;
extern UART_HandleTypeDef huart1;

// =======================================================
// LA FUNCIÓN CORE: Envío y Espera Inteligente (Latencia Cero)
// =======================================================
bool AT_Enviar_Comando(const char* comando, uint32_t timeout_ms) {
    
    // 1. Limpiar banderas viejas por si acaso
    osEventFlagsClear(uart_rx_flags_id, FLAG_AT_OK | FLAG_AT_ERROR);
    
    // 2. Transmitir el comando por Hardware UART
    // Ejemplo: "AT+CWJAP=\"Red\",\"Pass\"\r\n"
    HAL_UART_Transmit(&huart1, (uint8_t*)comando, strlen(comando), 100);
    
    // 3. MAGIA RTOS: Ponemos el Hilo a dormir.
    // Consumo de CPU = 0%. La ISR del UART nos despertará si llega un OK.
    uint32_t flags = osEventFlagsWait(uart_rx_flags_id, 
                                      FLAG_AT_OK | FLAG_AT_ERROR, 
                                      osFlagsWaitAny, 
                                      timeout_ms);
                                      
    // 4. Analizamos por qué despertamos
    if (flags & FLAG_AT_OK) {
        return true;  // ¡Éxito! El ESP32 obedeció.
    } 
    else {
        // Pudo haber despertado por FLAG_AT_ERROR o por osErrorTimeout
        return false; // Fracaso.
    }
}

// =======================================================
// LA MÁQUINA DE ESTADOS FINITA (FSM)
// =======================================================
typedef enum {
    ESTADO_HARD_RESET,
    ESTADO_INIT,
    ESTADO_WIFI_CONN,
    ESTADO_TCP_CONN,
    ESTADO_IDLE_READY
} Estado_IoT_t;

void Thread_WiFi_Modem(void *argument) {
    
    Estado_IoT_t estado_actual = ESTADO_HARD_RESET;
    int reintentos = 0;
    
    for (;;) {
        switch (estado_actual) {
            
            case ESTADO_HARD_RESET:
                // Apagamos la alimentación física del ESP32
                HAL_GPIO_WritePin(GPIOB, PIN_ESP32_EN, GPIO_PIN_RESET);
                osDelay(1000); // Lo dejamos morir 1 segundo
                // Lo encendemos
                HAL_GPIO_WritePin(GPIOB, PIN_ESP32_EN, GPIO_PIN_SET);
                osDelay(2000); // Tiempo para que cargue su Bootloader interno
                
                reintentos = 0;
                estado_actual = ESTADO_INIT;
                break;
                
            case ESTADO_INIT:
                // Hacemos ping con Timeout de 500ms
                if (AT_Enviar_Comando("AT\r\n", 500)) {
                    // Quitamos Echo y transicionamos
                    AT_Enviar_Comando("ATE0\r\n", 500);
                    estado_actual = ESTADO_WIFI_CONN;
                } else {
                    reintentos++;
                    if(reintentos > 3) estado_actual = ESTADO_HARD_RESET;
                }
                break;
                
            case ESTADO_WIFI_CONN:
                // Conectar al WiFi (Timeout largo de 10 segundos)
                // Usamos la red de la gasolinera
                if (AT_Enviar_Comando("AT+CWJAP=\"Gasolinera_Norte\",\"Pass123\"\r\n", 10000)) {
                    estado_actual = ESTADO_TCP_CONN;
                } else {
                    estado_actual = ESTADO_HARD_RESET; // Si no conecta, reiniciamos todo el ciclo
                }
                break;
                
            case ESTADO_TCP_CONN:
                // Conectarse al servidor AWS / API REST
                if (AT_Enviar_Comando("AT+CIPSTART=\"TCP\",\"api.aws.com\",80\r\n", 5000)) {
                    estado_actual = ESTADO_IDLE_READY; // ¡Llegamos a la meta!
                } else {
                    estado_actual = ESTADO_HARD_RESET;
                }
                break;
                
            case ESTADO_IDLE_READY:
                // Aquí el sistema está conectado operando.
                // Revisamos si la Metrología dejó telemetría en el Mailbox,
                // la empaquetamos y usamos AT+CIPSEND para enviarla.
                
                // Si en algún momento AT+CIPSEND devuelve falso (Se cayó la nube):
                // estado_actual = ESTADO_HARD_RESET;
                
                osDelay(100);
                break;
        }
    }
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Resiliencia Extrema de Red con Auto-Recuperación (Self-Healing).

En el mundo real, los módems de telecomunicaciones se "cuelgan". Ocurre en tu casa y ocurre en la industria aeroespacial. La diferencia es que en tu casa tú puedes desconectar el router de la pared para reiniciarlo; en un Despachador de Gas LP de aluminio herméticamente sellado atornillado a una placa de concreto en la carretera, nadie puede "desconectarlo" sin llamar a mantenimiento.

Al implementar la **FSM de Auto-Recuperación (Self-Healing)** dentro de la Tarea WiFi de tu STM32, has construido un técnico de mantenimiento virtual en tu software. Si el ESP32 pierde la conexión por una falla del router o se bloquea por un desbordamiento de su propia memoria criptográfica, la Máquina de Estados del STM32 detectará el *Timeout*, pasará al estado `ESTADO_HARD_RESET`, cortará la energía física al pin `EN` del módem mediante un transistor NPN, y orquestará la reconexión total desde cero. Y lo más impresionante: durante los 13 segundos que dura este proceso de reinicio de la red, tu hilo de `Task_Metrología` jamás se entera. Sigue ejecutándose perfectamente, cerrando las válvulas a tiempo y guardando las ventas localmente en su EEPROM. La nube puede caerse, pero la venta jamás se detiene.

📝 Resumen del Día