DÍA 207 DE 365

Fase 4: FreeRTOS y Conectividad IoT

📖 Teoría a Estudiar

El Peligro de las Librerías JSON

Una librería estándar como cJSON es maravillosa en una Raspberry Pi o una PC, porque internamente crea una estructura de árbol enlazado usando decenas de llamadas a malloc() y free() para ir "anidando" los campos del JSON de forma dinámica.

En un STM32 bajo restricciones de Misión Crítica (MISRA-C), no puedes permitirte ese lujo. Si el servidor AWS te exige un JSON con 15 campos, cJSON podría fragmentar tu memoria Heap y causar un HardFault. Para construir paquetes de telemetría estructurados de manera determinista, usaremos la navaja suiza de la librería estándar de C: snprintf().

El Superpoder de snprintf

A diferencia del clásico y peligroso sprintf, la versión segura (con 'n') requiere que le especifiques el tamaño estricto del buffer de destino. Si intentas escribir 200 caracteres en un arreglo de 100, snprintf cortará la cadena automáticamente en el carácter 99 y pondrá el terminador nulo \0. Esto hace matemáticamente imposible sufrir un Buffer Overflow.

El único reto real de construir JSONs manualmente en C es el escape de comillas. Un JSON válido usa comillas dobles ("key":"value"). En C, para meter una comilla doble dentro de una cadena de texto, debes "escaparla" usando una barra invertida: \".

TRANSFORMACIÓN DETERMINISTA: DE ESTRUCTURA BINARIA A JSON ASCII Memoria STM32 (Binaria) char id[8] "BOMBA_1" float lts_gas 20.50 int folio 4082 snprintf Motor Formateador Evita Fragmentación Control de Límite Escape de Comillas Buffer Envío (ASCII) char payload_tx[128] { "disp_id": "BOMBA_1", "litros": 20.50, "ticket": 4082 } LÍMITE ESTRICTO DE 128 BYTES (Truncamiento Seguro) El Secreto de la Sintaxis en C: snprintf(buf, size, "{\"disp_id\":\"%s\",\"litros\":%.2f}", struct.id, struct.lts); La barra invertida (\") le indica al compilador que la comilla es parte del texto, no el final de la cadena.

⚙️ Ejercicios Prácticos

Circuito: Esto es puramente manipulación de memoria. No requiere hardware nuevo, pero es el paso previo obligatorio para enviarlo por el Socket TCP (Día 205).

Ejercicio 1: Vamos a crear una función robusta de "Ensamblaje JSON". Recibirá la estructura de telemetría de nuestra Tarea de Metrología y un buffer vacío. Retornará true si el ensamblaje fue un éxito, o false si el JSON quedó trunco (cortado) por sobrepasar el límite de bytes, previniendo así enviar datos corruptos a AWS.


/**
 * @file json_builder.c
 * @brief Ensamblador de Payloads JSON Determinista sin asignación dinámica (MISRA-C)
 */

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

// =======================================================
// ESTRUCTURA DE LA VERDAD (Binaria)
// (Originada en la Tarea de Metrología)
// =======================================================
typedef struct {
    char id_equipo[16];   // "DISP_GZ_01"
    uint32_t ticket_num;  // 14592
    float litros_venta;   // 25.40
    float monto_total;    // 350.50
    uint8_t estado;       // 0=Error, 1=Ok
} Venta_GasLP_t;

// =======================================================
// ENSAMBLADOR DE CADENA (String Formatter)
// =======================================================
/**
 * @brief  Convierte un struct de Venta en un string JSON válido
 * @param  datos_venta: Puntero a la estructura original
 * @param  buffer_salida: Arreglo de caracteres pre-alojado donde se escribirá
 * @param  tamaño_maximo: El límite máximo en bytes del buffer de salida
 * @retval bool: true si el JSON se formó completo, false si fue truncado
 */
bool Generar_Payload_JSON(Venta_GasLP_t* datos_venta, char* buffer_salida, size_t tamaño_maximo) {
    
    // Plantilla Base (Escapando las comillas dobles obligatorias del JSON):
    // {
    //   "equipo": "DISP_GZ_01",
    //   "ticket": 14592,
    //   "litros": 25.40,
    //   "total": 350.50,
    //   "status": 1
    // }
    
    // Ejecutamos la inyección de variables dentro de la plantilla ASCII
    int bytes_escritos = snprintf(buffer_salida, tamaño_maximo,
        "{\"equipo\":\"%s\",\"ticket\":%lu,\"litros\":%.2f,\"total\":%.2f,\"status\":%d}",
        datos_venta->id_equipo,
        datos_venta->ticket_num,
        datos_venta->litros_venta,
        datos_venta->monto_total,
        datos_venta->estado
    );
    
    // Análisis de Seguridad Estricta
    // Si bytes_escritos es negativo, hubo un error de codificación.
    if (bytes_escritos < 0) {
        return false; 
    }
    
    // Si los bytes_escritos son mayores o iguales al límite de nuestro arreglo,
    // significa que 'snprintf' tuvo que cortar la cadena para salvarnos de un Overflow.
    // Un JSON sin su llave de cierre "}" es un JSON corrupto, AWS lo rechazará.
    if ((size_t)bytes_escritos >= tamaño_maximo) {
        // Enviar Log de Error Local
        // UART_Enviar_String("[ERROR] Payload JSON Truncado por falta de Buffer.\r\n");
        return false; 
    }
    
    // Si pasamos las validaciones, el JSON es perfecto y está listo para transmitirse.
    return true;
}

// =======================================================
// USO DENTRO DE LA TAREA IOT (Fragmento)
// =======================================================
/*
void Thread_WiFi_Modem(void *argument) {
    
    Venta_GasLP_t ultima_venta;
    char buffer_payload[256]; // Límite generoso de la Pila estática
    
    for (;;) {
        // ... Asumiendo que sacamos datos del Mailbox o Cola ...
        
        // Ensamblar el JSON
        if (Generar_Payload_JSON(&ultima_venta, buffer_payload, sizeof(buffer_payload))) {
            
            // Si el ensamblaje fue exitoso, lo enviamos al Túnel TCP
            // (El comando AT+CIPSEND lo veremos mañana en el Día 208)
            // Enviar_Trama_ESP32(buffer_payload);
            
        } else {
            // Manejo de error local: Quizás encender LED de alarma de software
        }
        
        osDelay(100);
    }
}
*/
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Interoperabilidad Cloud Universal (Desacoplamiento Estricto).

Una de las peores deudas técnicas en proyectos de hardware es atar el firmware del microcontrolador al lenguaje de programación del servidor de la empresa. Si envías los datos en arreglos binarios puros de C (Struct Packing), el servidor de la Nube debe escribir scripts complejos en Python o C# para desempacar los bytes, preocupándose por si el STM32 era de 32-bits, si usaba Little-Endian, o si alineaba la memoria a 4 bytes. Y si el servidor se actualiza, la máquina entera deja de funcionar.

Al implementar este **Motor de Formateo JSON Manual**, has logrado el Desacoplamiento de Sistemas definitivo. Tu Despachador de Gas LP ahora habla el mismo lenguaje que los servidores de Netflix, Google o Amazon. Al servidor REST en la nube ya no le importa si tu máquina tiene un STM32, un PIC de Microchip o una simple Raspberry Pi; solo recibe un texto humano, legible y estandarizado con llaves y comillas. Y lo más importante: lo lograste usando cero bytes de memoria dinámica (`malloc`), manteniendo tu certificación RTOS SIL-3 intacta y protegiendo el Heap contra la fragmentación a largo plazo. Un código verdaderamente elegante, simple y mortalmente efectivo.

📝 Resumen del Día