DÍA 212 DE 365

Fase 4: FreeRTOS y Conectividad IoT

📖 Teoría a Estudiar

El Árbol de Tópicos (Topic Tree)

En MQTT, un "Tópico" es simplemente una cadena de texto (UTF-8) que sirve como dirección de enrutamiento. A diferencia de las variables normales en C que son planas (ej. int ventas;), los Tópicos MQTT utilizan la Barra Diagonal (/) para crear niveles jerárquicos, como las carpetas en tu computadora.

Un error clásico es publicar todo en un tópico llamado datos. Esto es un desastre arquitectónico. La regla de oro en IoT industrial es estructurar el tópico de lo General a lo Específico:

empresa / pais / region / estacion_id / equipo_id / tipo_mensaje

Los Comodines de Suscripción (Wildcards)

La verdadera magia de usar barras diagonales es que el dueño del corporativo (La Nube AWS) no tiene que suscribirse uno por uno a los 10,000 despachadores. MQTT soporta comodines:

Nota crítica: Las máquinas (tu STM32) JAMÁS publican usando comodines. Las máquinas publican en tópicos absolutos y explícitos. Los comodines son un privilegio exclusivo de quien se suscribe para leer.

TAXONOMÍA MQTT: ESTRUCTURA JERÁRQUICA DE TÓPICOS gas_corp mexico guadalajara monterrey disp_01 telemetria/ventas Dirección: PUSH (Salida) estado/salud Latido de Vida control/precio Suscripción (Entrada) Filtros AWS Cloud (Wildcards) # Para recibir ventas de TODAS las máquinas en GDL: gas_corp/mexico/guadalajara/+/telemetria/ventas # Para auditar a Monterrey entero (Ventas, salud, todo): gas_corp/mexico/monterrey/#

⚙️ Ejercicios Prácticos

Circuito: Este es un diseño arquitectónico de software. No implica hardware nuevo, pero requiere que nuestro código C del STM32 sea capaz de concatenar strings dinámicamente, leyendo su propio "ID de Equipo" desde la EEPROM, para no hardcodear la ciudad o el nombre en el firmware y que cada placa pueda ser universal.

Ejercicio 1: Construiremos una librería interna en C (mqtt_topics.c) que alojará las constantes base y expondrá una función para generar dinámicamente el comando AT+MQTTPUB usando nuestro JSON Builder (Día 207) y nuestra FSM.


/**
 * @file mqtt_topics.c
 * @brief Generador Dinámico de Tópicos y Comandos AT para MQTT
 */

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

// =======================================================
// CONSTANTES DE TAXONOMÍA (Jerarquía Base)
// =======================================================
#define MQTT_ROOT_COMPANY  "gas_corp"
#define MQTT_REGION        "jalisco"
#define MQTT_CITY          "guadalajara"

// Esta variable debe ser leída de la EEPROM al arrancar la máquina.
// Si quemamos 100 placas de circuito, cada placa debe tener un ID único.
char DEVICE_ID[16] = "disp_01"; // Valor por defecto

// =======================================================
// CONSTRUCTOR DEL COMANDO AT+MQTTPUB
// =======================================================
/**
 * @brief  Ensambla el comando AT completo para publicar un mensaje en un tópico específico
 * @param  sub_topic: La ruta final del mensaje (ej. "telemetria/ventas")
 * @param  json_payload: El JSON validado con los datos
 * @param  buffer_salida: El buffer donde se escribirá el comando AT final
 * @param  tam_maximo: Seguridad de límite de memoria
 * @retval bool: Éxito en la construcción
 */
bool Construir_Comando_MQTT_Publicar(const char* sub_topic, const char* json_payload, char* buffer_salida, size_t tam_maximo) {
    
    // 1. Armamos primero el Tópico Dinámico
    // Resultado: "gas_corp/jalisco/guadalajara/disp_01/telemetria/ventas"
    char topic_absoluto[128];
    int len_topic = snprintf(topic_absoluto, sizeof(topic_absoluto), 
                             "%s/%s/%s/%s/%s", 
                             MQTT_ROOT_COMPANY, MQTT_REGION, MQTT_CITY, DEVICE_ID, sub_topic);
                             
    if (len_topic < 0 || (size_t)len_topic >= sizeof(topic_absoluto)) return false;

    // 2. Armamos el Comando AT del ESP32
    // Sintaxis Oficial: AT+MQTTPUB=<LinkID>,"<topic>","<data>",<qos>,<retain>
    // LinkID: 0 (Normalmente es 0 en modo single)
    // QoS: 1 (Obliga al Broker a confirmar de recibido - At Least Once)
    // Retain: 0 (No queremos que se quede pegado el último mensaje)
    
    int len_at = snprintf(buffer_salida, tam_maximo,
                          "AT+MQTTPUB=0,\"%s\",\"%s\",1,0\r\n",
                          topic_absoluto, json_payload);
                          
    if (len_at < 0 || (size_t)len_at >= tam_maximo) return false;
    
    return true;
}

// =======================================================
// EJEMPLO DE USO EN LA TAREA DE RED (Store & Forward)
// =======================================================
/*
void Enviar_Venta_MQTT(Venta_GasLP_t* venta_fisica) {
    char json_buffer[128];
    char comando_at_buffer[256];
    
    // Generamos el JSON (Día 207)
    if (Generar_Payload_JSON(venta_fisica, json_buffer, sizeof(json_buffer))) {
        
        // Ensamblamos el Comando MQTT completo con su Tópico Dinámico
        if (Construir_Comando_MQTT_Publicar("telemetria/ventas", json_buffer, comando_at_buffer, sizeof(comando_at_buffer))) {
            
            // Enviamos al ESP32 por UART
            // Como usamos QoS=1, AT+MQTTPUB responderá "OK" SOLO cuando AWS confirme la recepción.
            if (AT_Enviar_Comando(comando_at_buffer, 5000)) {
                
                // Éxito extremo-a-extremo. 
                // Marcamos la EEPROM como SYNC=1 (Día 210)
                EEPROM_Marcar_Ticket_Enviado(venta_fisica->ubicacion);
            }
        }
    }
}
*/
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Diseño Escalable de Metadatos y Organización Estructural (Data Governance).

Imagina que eres el Arquitecto de Software de una empresa gasera con operaciones a nivel nacional. Si a todos tus despachadores les hardcodeas el tópico `ventas` en su firmware, el día que enciendas 100 máquinas, el Broker MQTT se ahogará en un mar de JSONs y no tendrás forma de saber qué ticket pertenece a qué estado o ciudad. La base de datos colapsará intentando clasificar la información.

Al diseñar e implementar esta **Taxonomía Jerárquica Dinámica**, has garantizado que el mismo firmware exacto `.bin` compilado sirva para cualquier máquina en la República Mexicana. Al momento de la instalación, el técnico simplemente programa el `DEVICE_ID` y la ciudad en la EEPROM física de la placa. A partir de ahí, el STM32 se encarga de autogenerar su ruta absoluta (Ej. `gas_corp/jalisco/guadalajara/disp_01/telemetria/ventas`). Esto permite que tu servidor backend en AWS utilice "Filtros de Tópicos" súper eficientes para analizar los datos. Puedes preguntarle a AWS: *"¿Cuánto gas vendimos en Jalisco hoy?"* simplemente suscribiendo tu base de datos a `gas_corp/jalisco/#`. El orden estructural es lo que separa a un prototipo de estudiante de una flota de clase mundial.

📝 Resumen del Día