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().
snprintfA 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: \".
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);
}
}
*/
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.
snprintf()**. Permite inyectar flotantes y enteros directamente en una plantilla de caracteres.\").snprintf(). Si la longitud devuelta es mayor o igual al tamaño del búfer suministrado, significa que la cadena JSON se ha **truncado** silenciosamente (le falta el cierre }), y el paquete debe ser descartado antes de enviarlo por la red para evitar bloqueos del lado del servidor.