DÍA 246 DE 365

Fase 5: Diseño Industrial y Proyecto Final

📖 Teoría a Estudiar

El Paradigma Offline-First

Una máquina Offline-First asume que el internet no existe por defecto. En lugar de preguntar "hacia arriba" si una tarjeta RFID es válida, la Nube "empuja" (Push) una lista actualizada de clientes autorizados hacia abajo cada noche o cada vez que hay conexión. El Despachador guarda esta lista en su memoria local no volátil (EEPROM o Flash). Cuando un cliente acerca su tarjeta, el STM32 busca el ID en su propio cerebro y responde en milisegundos.

El Costo de la Búsqueda: De O(n) a O(log n)

Si tienes una base de datos local de 5,000 tarjetas (Whitelists de choferes y clientes VIP), y utilizas un bucle for tradicional para buscar una por una (Búsqueda Lineal), el procesador tardaría demasiado y el usuario sentiría que la máquina "se trabó". Peor aún, bloquearías las interrupciones del RTOS.

La ingeniería dicta que la Nube (AWS) debe enviarte el arreglo de IDs matemáticamente ordenado de menor a mayor. Al estar ordenado, podemos utilizar en C el algoritmo de Búsqueda Binaria (Binary Search). En lugar de 5,000 intentos, el STM32 encontrará (o rechazará) la tarjeta en un máximo de 13 saltos lógicos. El consumo de CPU es imperceptible.

ARQUITECTURA OFFLINE-FIRST Y AUTENTICACIÓN LOCAL PROCESO ASÍNCRONO DE SINCRONIZACIÓN (BACKGROUND SYNC) AWS IoT DB Principal (Array Ordenado) MQTT (LTE) Módem Copro. Recibe JSON Pasa Trama Binaria UART DMA EEPROM Local [1010] Saldo: 50.0L [3045] Saldo: 10.0L [8842] Saldo: 0.0L OPERACIÓN EN TIEMPO REAL (SIN INTERNET) Lector RFID Crypto Mifare 1K ID: 3045 Consulta O(log n) STM32 (RTOS Task) Busqueda_Binaria(3045) Acceso Local EEPROM Lectura I2C Rápida ¡AUTORIZADO! Abrir Válvula Tiempo: < 20 ms Cero Latencia Red

⚙️ Ejercicios Prácticos

Estructuras en C para Bases de Datos: Para que nuestro sistema consuma poca RAM y aproveche la EEPROM de forma secuencial, definiremos una estructura estricta (`struct`). En C, esta estructura ocupa bytes contiguos en memoria, simulando perfectamente las filas de una tabla de base de datos SQL.

Ejercicio 1: Búsqueda Binaria MISRA-C. Escribiremos el código para buscar a un cliente dentro de un arreglo en memoria. Cumpliremos la Regla 15.5 de MISRA-C ("Una función debe tener un único punto de salida en su final") para garantizar la trazabilidad de la ejecución en entornos críticos.


/**
 * @file db_local_rfid.c
 * @brief Base de Datos Local y Autenticación Offline-First (MISRA-C)
 */

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

// =========================================================================
// ESTRUCTURA DE LA BASE DE DATOS (Filas de la Tabla)
// =========================================================================
// Empacamos la estructura para no desperdiciar Bytes en la EEPROM
#pragma pack(push, 1)
typedef struct {
    uint32_t id_cliente;      // ID Criptográfico leído del Sector Mifare (Día 245)
    uint32_t saldo_max_litros;// Saldo prepagado multiplicado x10 para evitar floats
    uint8_t  estado_bloqueo;  // 0 = Activo, 1 = Tarjeta Robada/Suspendida
} FilaCliente_t;
#pragma pack(pop)

// Simulación de la memoria EEPROM ya cargada por AWS (Siempre ORDENADA de menor a mayor por ID)
#define TOTAL_CLIENTES_LOCALES (5U)

static const FilaCliente_t db_clientes_eeprom[TOTAL_CLIENTES_LOCALES] = {
    { 1010U, 500U, 0U },  // Cliente 1010, Saldo 50.0L, Activo
    { 2450U, 0U,   0U },  // Cliente 2450, Saldo 0.0L, Activo (Sin saldo)
    { 3045U, 120U, 0U },  // Cliente 3045, Saldo 12.0L, Activo
    { 6780U, 900U, 1U },  // Cliente 6780, Saldo 90.0L, BLOQUEADO (Robo)
    { 9912U, 450U, 0U }   // Cliente 9912, Saldo 45.0L, Activo
};

// =========================================================================
// MOTOR DE BÚSQUEDA BINARIA (O(log n))
// =========================================================================
/**
 * @brief  Busca un ID Criptográfico en la DB local usando algoritmo Binario.
 * @param  id_objetivo: El ID decodificado de la tarjeta física.
 * @param  p_saldo_salida: Puntero para devolver el saldo disponible si existe.
 * @retval bool: TRUE si el cliente existe Y está autorizado.
 */
bool Verificador_Tokens_Local(uint32_t id_objetivo, uint32_t* p_saldo_salida) {
    
    // Variables de control de la Búsqueda Binaria
    int32_t limite_izquierdo = 0;
    int32_t limite_derecho = (int32_t)(TOTAL_CLIENTES_LOCALES - 1U);
    
    // MISRA-C 15.5: Único punto de salida (Single exit point)
    bool acceso_concedido = false; 
    bool busqueda_terminada = false;
    
    // Asignación segura por defecto
    *p_saldo_salida = 0U; 

    // Bucle de partición a la mitad
    while ((limite_izquierdo <= limite_derecho) && (!busqueda_terminada)) {
        
        // Calcular el punto medio para cortar la tabla en dos
        int32_t punto_medio = limite_izquierdo + ((limite_derecho - limite_izquierdo) / 2);
        
        uint32_t id_leido = db_clientes_eeprom[punto_medio].id_cliente;
        
        if (id_leido == id_objetivo) {
            
            // ¡MATCH ENCONTRADO! 
            // Evaluamos la lógica de negocio (Saldo y Bloqueo)
            if (db_clientes_eeprom[punto_medio].estado_bloqueo == 0U) {
                if (db_clientes_eeprom[punto_medio].saldo_max_litros > 0U) {
                    *p_saldo_salida = db_clientes_eeprom[punto_medio].saldo_max_litros;
                    acceso_concedido = true;
                }
            }
            // Forzamos salida del bucle
            busqueda_terminada = true; 
            
        } else if (id_leido < id_objetivo) {
            // El objetivo está en la mitad superior de la tabla
            limite_izquierdo = punto_medio + 1;
        } else {
            // El objetivo está en la mitad inferior de la tabla
            limite_derecho = punto_medio - 1;
        }
    }
    
    // Único return de toda la función
    return acceso_concedido;
}

// =========================================================================
// INTEGRACIÓN CON LA TAREA RFID (Día 245)
// =========================================================================
/*
    // ... dentro del estado RFID_ESTADO_LEYENDO ...
    uint32_t id_cliente_hex = (buffer[0]<<24) | (buffer[1]<<16) | (buffer[2]<<8) | buffer[3];
    uint32_t limite_litros_disponibles = 0U;
    
    if (Verificador_Tokens_Local(id_cliente_hex, &limite_litros_disponibles)) {
        // LCD_Mostrar_Mensaje("ACCESO OK");
        // Despachador_Iniciar_Venta(limite_litros_disponibles);
    } else {
        // LCD_Mostrar_Mensaje("TARJETA RECHAZADA O SIN SALDO");
    }
*/
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Alta Disponibilidad (99.99% Uptime) y Computación en el Borde (Edge Computing).

Vender una solución IoT a una corporación gasera requiere garantizar la operación. Si su flotilla de camiones llega a cargar gas en la madrugada y tu Despachador no abre la válvula porque el proveedor de internet "está en mantenimiento", perderán miles de dólares en logística. El cliente te devolverá el equipo.

Al implementar la arquitectura **Offline-First**, has trasladado la inteligencia de la Nube hacia el "Borde" (Edge Computing). El despachador consulta una réplica microscópica de la base de datos dentro de su propia EEPROM mediante un algoritmo de **Búsqueda Binaria `O(log n)`**. Esto permite que la autenticación tarde menos de 1 milisegundo. Si un chofer reporta el robo de su tarjeta, el sistema de AWS enviará por MQTT una actualización delta que modificará el bit de `estado_bloqueo` en la EEPROM local (Background Sync). Esta robustez híbrida (Inteligencia Local + Actualización Global Asíncrona) es lo que distingue al hardware amateur de grado "Arduino" de un verdadero activo industrial de misión crítica.

📝 Resumen del Día