DÍA 168 DE 365

Fase 3: Comunicaciones y Redes Industriales

📖 Teoría a Estudiar

El Vector de Precios Dinámico (TOU Pricing)

El Time-of-Use (TOU) Pricing implica dividir la semana en una matriz de "reglas". Cada regla dicta una hora de inicio, una hora de fin, un precio asignado y a qué días de la semana aplica.

Para hacer que esta matriz sea digerible para un microcontrolador y fácil de transmitir por Modbus, transformamos la abstracción del "tiempo" en un escalar lineal: Los Minutos desde la Medianoche (0 - 1439).

Si nuestro RTC integrado del Día 141 nos dice que son las `14:30`, el STM32 solo calcula (14 * 60) + 30 = 870 y luego barre la tabla buscando qué regla cubre el minuto `870` para el día actual.

Las Máscaras de Bits (Bitmasking) para los Días

En lugar de usar 7 booleanos para guardar si una tarifa aplica para los diferentes días de la semana, usamos **un solo Byte (8 bits)**. Asignamos el Lunes al Bit 0, el Martes al Bit 1, etc. Así, si una tarifa aplica de Lunes a Viernes, el byte será 0001 1111 (0x1F). Comprobar si hoy aplica una regla es tan fácil y rápido como hacer una operación AND (regla.dias_semana & (1 << dia_actual)).

MATRIZ HORARIA DE PRECIOS Y RESOLUCIÓN ALGORÍTMICA 00:00 (Min 0) 06:00 (Min 360) 18:00 (Min 1080) 23:59 (Min 1439) T0: $10.20 T1 (Día Pico): $11.50 T2: $10.80 RTC Actual (Hardware) MIE 14:30:00 Min 870 Evaluador Bare-Metal (Búsqueda Lineal) for(int i=0; i<NUM_TARIFAS; i++) { if( (tarifas[i].dias & (1 << HOY)) && (min_actual >= tarifas[i].start && min_actual < tarifas[i].end) ) return tarifas[i].precio_centavos;

⚙️ Ejercicios Prácticos

Ejercicio 1: Definiremos el objeto ReglaTarifa_t asegurándonos de empaquetarlo (__attribute__((packed))) para que nuestro Servidor SCADA pueda enviarnos un arreglo de 10 tarifas a través del Modbus sin que se engorde por el Memory Padding (Día 154). Luego, escribiremos el escáner de tarifas que la máquina ejecutará justo al levantar la boquilla, antes de encender la bomba.


/**
 * @file pricing_engine.c
 * @brief Gestor Autónomo de Tarifas Horarias (Time-of-Use Pricing)
 */

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

// Dependencia de nuestro RTC (Día 141)
extern void RTC_ObtenerFechaHora(uint8_t *dia_sem, uint8_t *hora, uint8_t *minuto);

// =========================================================================
// DEFINICIÓN DE LA ESTRUCTURA DE LA REGLA
// =========================================================================

#pragma pack(push, 1)
typedef struct {
    uint16_t min_inicio;     // 0 a 1439
    uint16_t min_fin;        // 0 a 1439
    uint8_t  dias_semana;    // Lunes=Bit0, Martes=Bit1... Domingo=Bit6. Bit7=Activo.
    uint32_t precio_centavos; // Ej: $10.55 = 1055
} ReglaTarifa_t;
#pragma pack(pop)

// Tamaño total = 2 + 2 + 1 + 4 = 9 bytes por regla. Súper compacto.

#define MAX_TARIFAS 5

// Simulación de la memoria Flash (La cual es actualizada por Modbus OTA)
ReglaTarifa_t matriz_tarifas[MAX_TARIFAS] = {
    // 00:00 a 06:00 (Turno Nocturno L-V, $10.20)
    { 0,   360,  0x9F, 1020 }, // 0x9F = 1001 1111 (Bit7 Activo, L a V activos)
    
    // 06:00 a 18:00 (Turno Pico L-V, $11.50)
    { 360, 1080, 0x9F, 1150 }, 
    
    // 18:00 a 23:59 (Turno Tarde L-V, $10.80)
    { 1080,1440, 0x9F, 1080 },
    
    // 00:00 a 23:59 (Fin de semana S-D, $11.00 fijo)
    { 0,   1440, 0xE0, 1100 }, // 0xE0 = 1110 0000 (Bit7 Activo, S y D activos)
    
    // Tarifa inactiva / vacía
    { 0, 0, 0x00, 0 } 
};

// Precio por defecto si la base de datos se corrompe o hay un vacío
#define PRECIO_DEFAULT_FALLBACK 1200 // $12.00 (Caro por seguridad)

// =========================================================================
// MOTOR DE RESOLUCIÓN DE PRECIOS
// =========================================================================

/**
 * @brief Analiza el reloj y la tabla para devolver el precio aplicable en este instante.
 */
uint32_t Tarifas_Calcular_Precio_Vigente(void) {
    
    uint8_t dia_actual, hora, minuto;
    
    // 1. Preguntarle al Hardware RTC qué hora exacta es
    RTC_ObtenerFechaHora(&dia_actual, &hora, &minuto);
    
    // Normalizar día del 0 al 6 (Lunes=0) según nuestro estándar interno
    // Asumiendo que el RTC devuelve 1 para Lunes.
    dia_actual = dia_actual - 1; 
    
    // Convertir la abstracción de horas/minutos a un plano lineal
    uint16_t minuto_lineal_actual = (hora * 60) + minuto;
    
    // 2. BARRIDO DE LA MATRIZ DE REGLAS
    for (int i = 0; i < MAX_TARIFAS; i++) {
        
        // Verificar si la regla está habilitada (Bit 7)
        if ((matriz_tarifas[i].dias_semana & (1 << 7)) == 0) continue;
        
        // Verificar si la regla aplica para el día de hoy
        if (matriz_tarifas[i].dias_semana & (1 << dia_actual)) {
            
            // Verificar si el minuto actual cae dentro de la ventana de tiempo
            if (minuto_lineal_actual >= matriz_tarifas[i].min_inicio && 
                minuto_lineal_actual < matriz_tarifas[i].min_fin) {
                
                // ¡Match Perfecto! Hemos encontrado el precio vigente.
                return matriz_tarifas[i].precio_centavos;
            }
        }
    }
    
    // 3. FALLBACK DE SEGURIDAD (Si el despachador fue configurado con "huecos" horarios)
    // El principio Fail-Safe demanda proteger la ganancia del negocio.
    return PRECIO_DEFAULT_FALLBACK;
}

// =========================================================================
// SÚPER BUCLE
// =========================================================================

int main(void) {
    
    // inicializar_hardware();
    
    uint32_t precio_fijado_para_esta_venta = 0;
    
    while(1) {
        
        // El cajero no despacha hasta que se levante físicamente la boquilla
        // if (estado_maquina == ESTADO_ESPERANDO_BOQUILLA && sensor_boquilla_up()) {
            
            // Justo en este nanosegundo, fijamos el precio inmutable para la venta.
            // Si el precio cambia mientras carga gas, NO afectará a este cliente.
            // precio_fijado_para_esta_venta = Tarifas_Calcular_Precio_Vigente();
            
            // lcd_imprimir_precio(precio_fijado_para_esta_venta);
            // estado_maquina = ESTADO_MOTOR_ENCENDIDO;
        // }
    }
    
    return 0;
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Autonomía Transaccional e Independencia de Red.

Muchas gasolineras modernas tienen un "Cerebro Central" (PC en la oficina) que dicta los precios de las 10 bombas. Si esa PC Windows falla o se actualiza, todas las bombas dejan de vender gas. Esto causa cuellos de botella terribles y pérdidas económicas.

Al dotar a tu Despachador de una **Matriz de Tarifas Interna (TOU)**, lo has independizado del Servidor. El servidor SCADA de tu Tesis solo interviene una vez a la semana: le envía por RS485 el bloque binario de 45 bytes (5 reglas x 9 bytes) y tu máquina lo guarda en la Flash. A partir de ahí, el Despachador sobrevive solo. Si son las 05:59 a.m. y el cliente levanta la manguera, el RTC integrado de la placa evalúa la tabla, fija el precio barato de la madrugada, y el PID despacha el gas con total normalidad, incluso si el cable Modbus principal ha sido cortado por una excavadora.

📝 Resumen del Día