DÍA 210 DE 365

Fase 4: FreeRTOS y Conectividad IoT

📖 Teoría a Estudiar

El Patrón "Store & Forward" (Almacenar y Reenviar)

Este es el principio fundamental de la telemetría tolerante a fallos espaciales e industriales. Consta de dos fases inseparables:

  1. STORE (Almacenar Localmente): Cuando la Tarea de Metrología termina una venta de gas, **jamás** se la pasa directamente a la Tarea WiFi. En su lugar, envía la estructura de venta a una Tarea de EEPROM (Memoria No Volátil) a través de una Cola. La EEPROM guarda los bytes físicamente en silicio, marcando un Byte de Estado como 0x00 (Pendiente de Envío). Si en ese instante cae un rayo y la máquina se apaga, la venta ya es inmortal.
  2. FORWARD (Reenviar a la Nube): La Tarea WiFi (cuando está en su estado IDLE_READY), revisa constantemente la EEPROM buscando tickets cuyo estado sea 0x00. Si encuentra uno, lo lee, lo convierte a JSON y lo envía a AWS. Solo cuando AWS responde con un 200 OK, la Tarea WiFi accede a la EEPROM y cambia el Byte de Estado a 0x01 (Enviado Exitosamente).

Desacoplamiento Absoluto

Con este patrón, la Tarea de Metrología no sabe ni le importa si hay internet. Su trabajo termina al guardar en EEPROM. La Tarea WiFi actúa como un "barrendero" de fondo (Sweeper), que poco a poco va subiendo a la nube todas las ventas que se hayan acumulado durante el día, respetando los límites de velocidad del ESP32.

PATRÓN STORE & FORWARD (RESILIENCIA OFFLINE) Task: Metrología Genera Venta Nueva Task: EEPROM_Log Escribe I2C Físico EEPROM (AT24C256) T#1: $100 [SYNC: 1] T#2: $500 [SYNC: 0] T#3: $250 [SYNC: 0] Task: WiFi IoT El "Barrendero" AWS Cloud HTTP 200 OK osMessageQueuePut I2C Write (0) Busca SYNC:0 AT+CIPSEND I2C Rewrite (SYNC: 1) APAGÓN RAM Se borra. EEPROM sobrevive.

⚙️ Ejercicios Prácticos

Circuito: Este patrón requiere un chip de memoria no volátil, típicamente una EEPROM I2C externa (como la AT24C256) conectada a los pines I2C del STM32 (SCL, SDA). La escritura a este chip está protegida por un Mutex (bus_hardware_mutex_id), garantizando que si la Tarea Metrología quiere guardar un nuevo ticket, la Tarea WiFi no interfiera si estaba leyendo un ticket viejo.

Ejercicio 1: Modificaremos el flujo de nuestra Tarea IoT (WiFi). En el estado ESTADO_IDLE_READY, la tarea ya no leerá una Cola de RAM. Ahora llamará a una función de la capa de acceso a datos (EEPROM) para buscar el ticket más antiguo no sincronizado. Si el envío es un éxito, sobrescribirá el byte de estado de ese ticket específico.


/**
 * @file store_and_forward.c
 * @brief Implementación del patrón de resiliencia Store & Forward en RTOS
 */

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

// El Mutex global para no chocar en el bus I2C (Día 190)
extern osMutexId_t bus_hardware_mutex_id;

// =======================================================
// CAPA DE ACCESO A DATOS (EEPROM Driver Abstracto)
// =======================================================
// Definimos los estados del Ticket en la memoria física
#define TICKET_PENDIENTE 0x00
#define TICKET_ENVIADO   0x01

// Estructura adaptada para alineación de Memoria Física
#pragma pack(push, 1)
typedef struct {
    uint8_t estado_sync;  // 1 byte: 0=Pendiente, 1=Enviado
    uint32_t ticket_num;  // 4 bytes
    float litros_venta;   // 4 bytes
    float monto_total;    // 4 bytes
    uint32_t timestamp;   // 4 bytes (Fecha NTP UNIX)
} Ticket_Fisico_t;        // Total: 17 bytes por registro
#pragma pack(pop)

/**
 * @brief Escanea la EEPROM I2C buscando el primer ticket con estado TICKET_PENDIENTE
 */
bool EEPROM_Buscar_Ticket_No_Sincronizado(Ticket_Fisico_t* buffer_destino, uint16_t* direccion_memoria) {
    bool encontrado = false;
    
    // Adquirimos el candado I2C (Esperamos máximo 500ms si alguien más lo usa)
    if (osMutexAcquire(bus_hardware_mutex_id, 500) == osOK) {
        
        // Simulación: Escaneamos los 1000 espacios de memoria disponibles en la AT24C256
        for (uint16_t mem_addr = 0; mem_addr < 17000; mem_addr += sizeof(Ticket_Fisico_t)) {
            
            // Leemos 1 solo byte (el estado_sync) de la EEPROM
            uint8_t estado;
            I2C_EEPROM_Read(mem_addr, &estado, 1);
            
            if (estado == TICKET_PENDIENTE) {
                // ¡Bingo! Encontramos un ticket atorado. Lo leemos completo.
                I2C_EEPROM_Read(mem_addr, (uint8_t*)buffer_destino, sizeof(Ticket_Fisico_t));
                *direccion_memoria = mem_addr; // Guardamos su ubicación para luego marcarlo
                encontrado = true;
                break; 
            }
        }
        osMutexRelease(bus_hardware_mutex_id);
    }
    return encontrado;
}

/**
 * @brief Cambia el byte 'estado_sync' a TICKET_ENVIADO en la memoria física
 */
void EEPROM_Marcar_Ticket_Enviado(uint16_t direccion_memoria) {
    if (osMutexAcquire(bus_hardware_mutex_id, 500) == osOK) {
        uint8_t nuevo_estado = TICKET_ENVIADO;
        // Sobrescribimos exclusivamente el primer byte de ese registro
        I2C_EEPROM_Write(direccion_memoria, &nuevo_estado, 1);
        osMutexRelease(bus_hardware_mutex_id);
    }
}

// =======================================================
// LA TAREA IOT (El Barrendero "Store & Forward")
// =======================================================
// Asumimos que la Tarea WiFi llegó al estado ESTADO_IDLE_READY (Túnel abierto)
void Tarea_WiFi_Forward(void) {
    
    Ticket_Fisico_t ticket_perdido;
    uint16_t ubicacion_eeprom;
    
    // 1. Preguntarle al hardware físico si quedó algo pendiente tras el apagón
    if (EEPROM_Buscar_Ticket_No_Sincronizado(&ticket_perdido, &ubicacion_eeprom)) {
        
        UART_Enviar_String("[FORWARD] Ticket pendiente encontrado. Intentando enviar...\r\n");
        
        // 2. Convertir a JSON y enviar (Rutina del Día 209)
        // Simulamos la llamada a la función que empaqueta y hace el AT+CIPSEND
        bool exito_nube = Sincronizar_Venta_Nube_Estructura(&ticket_perdido);
        
        // 3. LA CONFIRMACIÓN CRÍTICA
        if (exito_nube == true) {
            // AWS respondió 200 OK. La plata está segura en la base de datos central.
            // AHORA Y SÓLO AHORA, borramos (marcamos) el ticket localmente.
            EEPROM_Marcar_Ticket_Enviado(ubicacion_eeprom);
            UART_Enviar_String("[FORWARD] Sincronización sellada en EEPROM.\r\n");
        } else {
            // AWS falló (Timeout, 500 Internal Error, o cable roto).
            // NO HACEMOS NADA. El ticket sigue marcado como PENDIENTE (0x00).
            // En el próximo ciclo (tras la reconexión de red), volveremos a intentarlo.
            UART_Enviar_String("[FORWARD] Falla de nube. Conservando ticket offline.\r\n");
        }
    }
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Cumplimiento de la Norma Oficial Mexicana (Bitácora Inalterable) y Tolerancia Total a Fallos Eléctricos.

Las autoridades fiscales y metrológicas auditan los despachadores de combustible buscando una cosa: la certeza de que el medidor físico coincide con la contabilidad reportada. Si diseñas una máquina que "olvida" las ventas cuando se va el Internet o se corta la luz, estás diseñando una máquina ilegal.

Al migrar la arquitectura de tu RTOS al patrón **Store & Forward**, has blindado el proyecto. Tu sistema prioriza la física sobre la red: el gas no se despacha hasta que la EEPROM garantiza que hay espacio, y la venta se graba en silicio (Store) en nanosegundos al cerrar la válvula. Si un huracán destruye las antenas celulares de la región, tu despachador seguirá vendiendo gas localmente durante semanas, acumulando hasta 17,000 registros seguros en su chip AT24C256. El día que un técnico conecte un cable de red funcional, la Tarea WiFi (Forward) despertará, barrerá la EEPROM y vaciará el historial completo de ventas atrasadas hacia el servidor de Amazon, validando cada una contra un `HTTP 200 OK`. Has construido una infraestructura a prueba de desastres naturales.

📝 Resumen del Día