DÍA 214 DE 365

Fase 4: FreeRTOS y Conectividad IoT

📖 Teoría a Estudiar

La Petición Asíncrona (URC de Suscripción)

Una vez que el ESP32 está conectado al Broker (AWS IoT), nuestra Tarea de Red debe enviarle un comando de inicialización: AT+MQTTSUB=0,"gas/control/#",1. A partir de este momento, el ESP32 vigilará todo lo que llegue a ese tópico.

Cuando AWS envía un cambio de precio, el firmware ESP-AT interrumpe cualquier cosa que esté haciendo y dispara por el cable TX hacia tu STM32 una cadena cruda (URC - Unsolicited Result Code) con la siguiente estructura:

+MQTTSUBRECV:0,"gas/control/precio",13,{"p":10.80}

El Riesgo del Búfer Circular (Ring Buffer)

Si tu código en el STM32 utiliza la función bloqueante HAL_UART_Receive() intentando "adivinar" cuándo llegará el mensaje, fallará catastróficamente. El mensaje puede llegar mientras el STM32 está guardando datos en la EEPROM.

La solución definitiva es usar la Interrupción de Recepción de Hardware (UART_RX_ISR) combinada con una Cola de Mensajes de FreeRTOS. La interrupción de silicio atrapará cada carácter (byte) de forma instantánea, sin importar qué esté haciendo la CPU, y lo depositará en la Cola. Una Tarea Parsea de baja prioridad irá sacando los caracteres de la Cola y construirá los enunciados. Al detectar la palabra clave +MQTTSUBRECV, extraerá el JSON y actualizará la variable global del precio protegiéndola con un Mutex.

ARQUITECTURA DE RECEPCIÓN ASÍNCRONA (MQTT SUBSCRIBE) AWS IoT PUBLISH a Topic {"p":10.80} TCP PUSH ESP32 (Modem) Ensambla URC de texto plano ASCII +MQTTSUBRECV:0,"gas...",... STM32 (Recepción Hardware) UART RX ISR ¡Atrápalo ya! RTOS Queue (RAM) Task_Parser (Low) Byte a byte strstr(buf, "+MQTTS") sscanf(JSON) Precio = $10.80

⚙️ Ejercicios Prácticos

Circuito: Debes habilitar la Interrupción Global del UART1 (USART1 global interrupt) en el NVIC de tu STM32. Si trabajas con CubeMX, asegúrate de marcar la casilla correspondiente. Esto activará la función de silicio que salta automáticamente cada que un byte de voltaje toca el pin RX.

Ejercicio 1: Crearemos el manejador de interrupciones para salvar los bytes en una Cola de FreeRTOS, y escribiremos la Tarea Parser que armará esos bytes en enunciados completos para cazar actualizaciones de comandos.


/**
 * @file mqtt_subscribe_parser.c
 * @brief Recepción Asíncrona y Parseo de Comandos MQTT mediante ISR y RTOS Queue
 */

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

// =======================================================
// RECURSOS GLOBALES
// =======================================================
extern UART_HandleTypeDef huart1;
osMessageQueueId_t uart_rx_queue_id; // Cola para salvar caracteres de la ISR
uint8_t rx_byte;                     // Buffer temporal de 1 byte para el hardware

// Variable Global del Negocio protegida
float precio_litro_gas = 10.50f;
extern osMutexId_t data_mutex_id;

// =======================================================
// 1. LA INTERRUPCIÓN (Hardware Level)
// Se ejecuta cada que llega 1 solo carácter del ESP32
// =======================================================
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    if (huart->Instance == USART1) {
        
        // Salvavidas: Metemos el byte en la Cola de FreeRTOS sin bloquear (Timeout 0)
        // Usar la versión "_ISR" para que el Kernel no crashee por violación de contexto
        osMessageQueuePut(uart_rx_queue_id, &rx_byte, 0, 0);
        
        // Rearmamos la trampa para atrapar el SIGUIENTE byte
        HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
    }
}

// =======================================================
// 2. EL PARSER EXTRACTOR DE JSON
// Busca el payload dentro del cochinero de texto de Espressif
// =======================================================
void Procesar_Mensaje_MQTT(char* linea_cruda) {
    
    // El texto esperado luce así:
    // +MQTTSUBRECV:0,"gas_corp/mexico/gdl/disp_01/control/precio",13,{"p":10.80}
    
    // Buscamos si la línea es efectivamente una recepción de suscripción MQTT
    if (strstr(linea_cruda, "+MQTTSUBRECV") != NULL) {
        
        // Truco de Punteros: Buscamos la primera llave de apertura '{'
        char* inicio_json = strchr(linea_cruda, '{');
        
        if (inicio_json != NULL) {
            float nuevo_precio = 0.0f;
            
            // Usamos sscanf para extraer el valor flotante del JSON crudo
            if (sscanf(inicio_json, "{\"p\":%f}", &nuevo_precio) == 1) {
                
                // ¡Éxito! Aplicamos el comando a nuestra máquina protegiendo 
                // la memoria con el Mutex para que Metrología no lea basura.
                osMutexAcquire(data_mutex_id, osWaitForever);
                precio_litro_gas = nuevo_precio;
                osMutexRelease(data_mutex_id);
                
                char msg[64];
                snprintf(msg, sizeof(msg), "[CMD] Nuevo precio aceptado: $%.2f\r\n", precio_litro_gas);
                UART_Enviar_String(msg);
            }
        }
    }
}

// =======================================================
// 3. LA TAREA PARSER (Software Level)
// Trabaja en la sombra, sacando bytes de la cola (Baja Prioridad)
// =======================================================
void Thread_UART_Parser(void *argument) {
    
    char buffer_linea[256];
    int indice = 0;
    uint8_t byte_recibido;
    
    for (;;) {
        // La tarea duerme aquí gastando 0% CPU hasta que la ISR meta un byte a la cola
        if (osMessageQueueGet(uart_rx_queue_id, &byte_recibido, NULL, osWaitForever) == osOK) {
            
            // Si llega un salto de línea, terminamos la oración y la analizamos
            if (byte_recibido == '\n' || byte_recibido == '\r') {
                if (indice > 0) {
                    buffer_linea[indice] = '\0'; // Terminador nulo C
                    Procesar_Mensaje_MQTT(buffer_linea); // Mandar a analizar
                    indice = 0; // Reiniciar buffer
                }
            } 
            else {
                // Almacenamos el byte armando la palabra, cuidando no desbordar RAM
                if (indice < sizeof(buffer_linea) - 1) {
                    buffer_linea[indice++] = (char)byte_recibido;
                }
            }
        }
    }
}

// Nota en el main.c: 
// Recordar iniciar el primer gatillo de la interrupción antes de arrancar el RTOS:
// HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Canal de Control Remoto de Misión Crítica (Remote Command & Control).

Un sistema de telemetría de una sola vía (que solo envía datos y no recibe órdenes) no califica como Industria 4.0. Las normas oficiales (Profeco, NOM) exigen que las estaciones de servicio sincronicen y exhiban el precio de los combustibles al público de manera infalible y auditada.

Al implementar el **Manejador Asíncrono de Interrupciones (Parser MQTT)**, has convertido a tu Despachador de Gas LP en un nodo inteligente comandable remotamente. Tu máquina está oficialmente inscrita en la red global. Si en las oficinas corporativas en la Ciudad de México presionan el botón de "Actualizar Tarifa", un paquete JSON viajará por la red de fibra de AWS, se disparará a la antena 4G/WiFi de tu gasolinera en Guadalajara, el ESP32 sentirá el impacto y activará el pin RX de tu STM32. La Interrupción de hardware congelará la CPU un microsegundo, atrapará el precio nuevo, y el Tarea Parser cambiará la variable del display LCD instantáneamente. Sin botones, sin técnicos, sin retrasos. Acabas de construir un sistema de Control Supervisorio (SCADA) moderno y puro.

📝 Resumen del Día