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
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:
+): Si AWS se suscribe a gas_corp/mexico/jalisco/+/telemetria/ventas, recibirá las ventas de TODOS los despachadores que existan en Jalisco, sin importar el ID del equipo.#): Si AWS se suscribe a gas_corp/mexico/jalisco/guadalajara/#, recibirá absolutamente todos los mensajes (ventas, errores, alarmas de gas, estatus de batería) de cualquier máquina operando en Guadalajara. El símbolo # solo puede ir al final del tópico.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.
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);
}
}
}
}
*/
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.
/) para separar niveles de granularidad (Ej. Empresa / Región / Ciudad / Dispositivo / Acción).snprintf para concatenar el Tópico en tiempo de ejecución (Runtime), leyendo los parámetros específicos (como el Device ID) desde una memoria no volátil (EEPROM).AT+MQTTPUB inyecta directamente el mensaje a la nube, requiriendo el Tópico como segundo argumento. Si se configura el parámetro QoS en 1 (Quality of Service: At Least Once), el comando no devolverá OK hasta que el Broker de la nube no confirme matemáticamente la recepción del paquete, supliendo la función del HTTP 200 OK en una fracción del tiempo.