← Índice del curso

Internet de las Cosas · Tema 32

Reglas y umbrales en el dispositivo

«Si la temperatura supera 8 °C, avisar» parece una regla completa. En un dispositivo real faltan varias decisiones: cuánto tiempo debe durar el exceso, cuándo termina la alarma, qué ocurre si el sensor falla y cómo evitar un mensaje por segundo. Vamos a convertir esa frase en una regla que se pueda programar, explicar y probar sin depender de la nube.

01 · Punto de partida

De una lectura a una decisión

En el tema 31 vimos que procesar localmente permite filtrar, resumir y reaccionar aunque Internet se corte. Ahora desarrollamos esa última parte: una regla transforma mediciones válidas en un estado y produce un evento cuando ese estado cambia. La red transporta el resultado; no decide si la condición se cumplió.

Seguimos con una cámara frigorífica, pero usamos una traza corta y determinista para poder revisar cada paso. No pretende representar la inercia térmica de una instalación: sirve para poner a prueba los límites de la lógica. Los valores de 8,0 y 7,5 °C son parámetros didácticos, elegidos para este ejemplo.

Una condición

temperatura >= 8.0 responde una pregunta sobre la lectura actual. No recuerda lo que pasó antes ni sabe cuánto duró.

Una regla

Recuerda si la alarma está activa, confirma cuánto dura una condición y emite «activada» o «recuperada» solo cuando corresponde cambiar de estado.

El problema no se resuelve aumentando el número de decimales. Una lectura de 7,99 seguida por otra de 8,01 puede ser solo ruido. La regla necesita memoria y una política explícita para tratar esa incertidumbre.

02 · Primero, la especificación

Escribir una regla sin ambigüedades

Antes del código, escribí una frase que dos programadores puedan implementar de la misma manera:

Contrato del ejemplo: lectura nominal cada segundo y temperatura expresada en décimas de grado.
ParámetroValorQué define
Umbral alto80 décimas = 8,0 °CInicia la entrada con c >= 80
Umbral de rearme75 décimas = 7,5 °CInicia la salida con c <= 75
Confirmación de entrada3000 msDuración mínima observada para activar
Confirmación de salida2000 msDuración mínima observada para recuperar
Separación máxima1500 msCancela el período pendiente si falta continuidad
Rango válido del ejemplo−40,0 a 125,0 °CRechaza datos fuera del rango admitido por este modelo

La unidad forma parte de la regla: 80 podría significar 80 °C, 8,0 °C o una cuenta de ADC. Usar décimas enteras evita depender de la representación binaria de los decimales justo en los límites. No mejora la precisión del sensor: solo fija la resolución con la que trabaja el programa.

También hay que fijar qué significa «superar»: en este tema usamos >=, de modo que 8,0 °C ya cuenta. En el tema 31 el ejemplo usaba un cruce estricto; copiar el número sin copiar la comparación cambia el comportamiento.

03 · Memoria en amplitud

Histéresis: dos umbrales

Con un único umbral, la secuencia 7,9 → 8,1 → 7,9 → 8,1 alterna el estado a cada lectura. Si cada cambio activa una notificación o un relé, el ruido produce mensajes repetidos o maniobras innecesarias.

La histéresis usa un umbral para entrar y otro para salir. Para una alarma por temperatura alta, la activación está arriba del rearme:

Inactiva: iniciar entrada si c ≥ 80
Activa: iniciar salida si c ≤ 75
Ancho de histéresis = 8,0 − 7,5 = 0,5 °C

Entre 7,5 y 8,0 °C se conserva el estado previo. A 7,8 °C la alarma puede estar inactiva si venía de abajo o activa si venía de arriba. Eso es exactamente la memoria que buscamos, y no una contradicción.

Dos umbrales sin confirmación temporal, para aislar el efecto de la histéresis.
LecturaEstado anteriorResultado
7,9 °CInactivaSigue inactiva
8,1 °CInactivaSe activa
7,9 °CActivaSigue activa: no llegó al rearme
8,1 °CActivaSigue activa: no hay un nuevo evento
7,5 °CActivaSe recupera

La histéresis evita la oscilación alrededor de un límite, pero no rechaza por sí sola un pico aislado: con una única lectura de 9 °C ya se activaría. Para eso agregamos una condición de tiempo.

Elegir el ancho

Partí del ruido observado con el sistema estable, la resolución y la incertidumbre de la medición del tema 10. Un ancho demasiado chico permite oscilaciones; uno demasiado grande mantiene la alarma activa cuando el proceso ya se acercó al valor normal. Si la decisión controla un actuador, también importan su desgaste y la dinámica física.

En una alarma por valor bajo se invierte la dirección: se entra al caer por debajo del umbral inferior y se recupera al subir por encima de uno superior. Un sensor de tanque vacío necesita esa variante; no alcanza con cambiar el nombre de la regla de temperatura alta.

04 · Memoria en el tiempo

Confirmación temporal

La confirmación temporal exige que la condición siga apareciendo durante un intervalo antes de aceptar la transición. También se llama persistencia o antirrebote temporal. Si una lectura deja de cumplirla, se cancela el período y la próxima lectura favorable empieza de cero.

En nuestro ejemplo, 8,1 °C en t = 2 s inicia una entrada. La lectura de 7,9 °C en t = 3 s la cancela. La de 8,2 °C en t = 4 s inicia otra: si las lecturas de t = 5, 6 y 7 s siguen en 8,0 °C o más, la alarma se activa en t = 7 s.

Tiempo observado = ahora − inicio de la condición
Activar cuando tiempo observado ≥ 3000 ms
Con muestras cada 1 s: t = 4, 5, 6 y 7 → 4 muestras, 3 segundos

Tres muestras separadas por un segundo cubren dos segundos, no tres. Contar muestras solo equivale a medir tiempo si se conoce y mantiene el período. Con un programa que a veces tarda más por la red o por el sensor, conviene usar marcas de tiempo monótonas.

Lo que se confirma es lo observado

El firmware no sabe qué pasó entre dos mediciones. Si el sensor toma una muestra por segundo, puede perder un pico que empieza y termina entre ellas. Por eso esta regla exige, además, que la separación no supere 1,5 s: si aparece un hueco de cinco segundos, el tiempo anterior no se considera evidencia de continuidad.

Si la condición física comienza en un instante cualquiera y permanece, con período de 1 s y confirmación de 3 s la detección tarda aproximadamente entre 3 y menos de 4 segundos, sin contar variaciones del planificador y de la adquisición. Se suma la espera hasta la primera muestra. En general, con período fijo Δ y persistencia P, el retraso está entre ceil(P / Δ) × Δ y ese valor más Δ.

Confirmar también la salida

Si la recuperación se acepta con una lectura sola, un descenso breve hasta 7,5 °C puede rearmar la alarma antes de tiempo. Nuestro ejemplo espera 2 s por debajo o exactamente en el rearme. Los tiempos de entrada y salida pueden ser distintos: se eligen según lo que cuesta una falsa activación o una falsa recuperación.

05 · El modelo

La máquina de estados

Para explicar la regla usamos cuatro estados lógicos. La implementación puede representarlos con un booleano (activa) y una marca opcional (desde): no hace falta una variable diferente por cada caja.

Máquina de estados de una alarma con confirmación Normal pasa a confirmando entrada cuando la temperatura alcanza ocho grados; si se sostiene tres segundos pasa a activa, y si baja antes vuelve a normal. Activa pasa a confirmando salida al llegar a siete coma cinco grados o menos; si se sostiene dos segundos vuelve a normal y si sube antes vuelve a activa. Los datos inválidos cancelan la confirmación sin cambiar el estado estable. NORMALCONFIRMANDO ENTRADA ACTIVACONFIRMANDOSALIDA c ≥ 8,0 °C: iniciar tiempoc < 8,0 °C: cancelar 3 sactivar c ≤ 7,5 °C: iniciar tiempoc > 7,5 °C: cancelar 2 srecuperar
Transiciones con datos válidos y continuidad suficiente.
EstadoCondiciónQué hacer
Normalc ≥ 80Iniciar confirmación de entrada
Confirmando entradac < 80Cancelar y volver a normal
Confirmando entradac ≥ 80 y pasaron 3 sActivar; emitir un evento
Activac ≤ 75Iniciar confirmación de salida
Confirmando salidac > 75Cancelar y seguir activa
Confirmando salidac ≤ 75 y pasaron 2 sRecuperar; emitir un evento

Durante la confirmación de entrada, activa sigue en falso. Durante la confirmación de salida, sigue en verdadero. Esto permite que una salida local refleje el estado confirmado y no cambie mientras la transición todavía está en evaluación.

Si la persistencia se configura en cero, la transición ocurre en la primera lectura que cumple. Si una lectura rompe la condición justo cuando iba a cumplirse el tiempo, se cancela primero: el tiempo transcurrido no alcanza sin una lectura actual que todavía cumpla.

06 · Comunicar decisiones

Estado, evento y acción

Son tres cosas relacionadas, pero distintas. El estado responde «¿la alarma está activa ahora?». Un evento dice «acaba de activarse» o «acaba de recuperarse». La acción es lo que el dispositivo hace con ese resultado: encender un indicador, registrar el cambio o intentar publicarlo.

Si la alarma sigue activa durante media hora, el programa puede evaluarla 1800 veces y conservar un solo evento de activación. Publicar cada vez que activa == true vuelve a convertir una regla local en un flujo repetitivo. Los recordatorios periódicos, si se necesitan, son otra política con su propio intervalo.

Un mensaje que explique qué cambió

{
  "v": 1,
  "device": "camara07",
  "boot_id": "arranque-42",
  "seq": 17,
  "rule": "temperatura_alta",
  "cfg_v": 1,
  "event": "activada",
  "uptime_ms": 7000,
  "value_dC": 84,
  "active": true
}

value_dC explicita décimas de grado Celsius. uptime_ms es tiempo desde el arranque, no una fecha UTC. Si existe un reloj sincronizado se puede agregar una fecha, junto con la calidad de esa sincronización. La duración de la regla se sigue midiendo con un reloj monótono, aunque se corrija la hora de Internet.

La combinación dispositivo + identificador de arranque + secuencia permite reconocer reintentos del mismo evento. El identificador de arranque debe ser único con suficiente fiabilidad dentro del historial que conserva el servidor; la secuencia aumenta por evento. Reintentar un envío conserva su identificación: generar otra al reintentar impediría deduplicar.

Con MQTT, el estado actual puede publicarse retenido en casa/camara07/state/alarma; las transiciones, en casa/camara07/event/temperatura_alta, sin retención. Así, un suscriptor nuevo obtiene la situación actual y no confunde una activación antigua con un evento recién ocurrido. Es la separación del tema 29.

QoS 1 admite duplicados; el receptor debe reconocer la identidad del evento si una segunda entrega produciría una segunda notificación. Además, la confirmación MQTT se refiere al intercambio de protocolo, no a que una persona haya visto la alarma. Ver la especificación MQTT 3.1.1 de OASIS, sección 4.3.

Alarma no significa control automático

Que una cámara esté caliente no implica que el firmware deba encender un compresor directamente. Una alarma informa una condición; el controlador de un equipo necesita además interbloqueos, estados de falla y tiempos mínimos entre maniobras. Si el ejemplo enciende un LED, el LED representa la alarma confirmada, no un diseño completo de refrigeración.

07 · Lo que una lectura no dice

Datos inválidos, silencios y reinicios

Una lectura fallida no equivale a cero grados. Ese reemplazo podría recuperar una alarma de temperatura alta justo cuando el sensor dejó de funcionar. Tampoco alcanza con que el número esté dentro del rango: un controlador de sensor puede devolver su último valor junto con una indicación de error.

Políticas explícitas para situaciones que el sensor y la red pueden producir.
SituaciónPolítica del ejemploQué agregar en el dispositivo
Valor inválido, no finito o adquisición fallidaCancelar el período pendiente y conservar activaReportar calidad y diagnosticar la causa
Separación entre muestras > 1500 msCancelar el período anterior; la muestra actual puede iniciar otroSupervisar el período real de adquisición
No llega ninguna muestraLa función de evaluación no se ejecutaUn supervisor independiente detecta el dato vencido
Reinicio del dispositivoLa instancia nueva empieza inactiva y sin dato evaluadoPublicar «inicializando» y aplicar una política de arranque
Se corta InternetLa regla sigue evaluando y actuando localmenteGuardar eventos pendientes con capacidad y descarte definidos

Calidad y alarma son dos dimensiones. Se puede tener «temperatura alta activa, dato actual inválido». Cancelar la confirmación no significa recuperar la alarma. En el laboratorio, «SIN DATO» describe la calidad de esa evaluación, y el campo «Alarma confirmada» conserva el estado anterior.

Detectar un sensor silencioso

Guardá el instante de la última lectura válida. Una tarea que corre aunque no lleguen muestras compara ese instante con el reloj: si supera el tiempo máximo admitido, activa una regla separada de «sensor sin datos». Las lecturas inválidas no actualizan ese instante. Recuperar ese diagnóstico puede requerir varias lecturas válidas consecutivas.

El límite de 1,5 s de nuestro evaluador sirve para no sumar evidencia a través de un hueco. No es un detector autónomo de silencio: recién advierte el salto cuando llega otra evaluación. El código de este tema mantiene pequeño el motor de umbrales y deja ese supervisor como una tarea independiente.

Reiniciar no es demostrar que todo está normal

El valor inicial activa = false es una decisión de este simulador. El sistema debe distinguir ese arranque de una recuperación confirmada. Según el proceso puede restaurar el estado persistido, esperar datos válidos antes de habilitar una salida o aplicar un estado de arranque definido. Restaurar una alarma no habilita a restaurar también un cronómetro de persistencia: después de un reinicio no hay evidencia de continuidad.

Reconocer una alarma desde un panel tampoco la recupera: «reconocida por un operador» y «condición física recuperada» son campos distintos. Si el equipo exige un rearme manual, se agrega un estado enclavado y una orden de rearme válida; no se fuerza el sensor a informar un valor normal.

08 · Parámetros y prioridades

Configurar y combinar reglas

Una regla útil suele tener parámetros remotos, como vimos en el tema 29. El mensaje debe indicar unidad, dirección, versión y duraciones; su recepción no implica que ya sea válido:

{
  "v": 1,
  "cfg_v": 2,
  "rule": "temperatura_alta",
  "unit": "dC",
  "high": 80,
  "reset": 75,
  "enter_ms": 3000,
  "exit_ms": 2000,
  "max_gap_ms": 1500
}
  1. Validar completo: tipos, campos obligatorios, unidad conocida y límites admitidos por el producto. Para una regla alta, reset < high; los tiempos no pueden ser negativos.
  2. Aplicar de manera atómica: no cambiar el umbral alto en una evaluación y el de rearme en la siguiente. Preparar una configuración completa y reemplazarla entre evaluaciones.
  3. Definir la transición: en esta política, conservar la alarma estable y cancelar cualquier confirmación pendiente al cambiar parámetros. La siguiente muestra se evalúa con los nuevos valores.
  4. Confirmar la versión aplicada: informar aceptación o rechazo y motivo. Los eventos siguientes incluyen cfg_v, para explicar bajo qué parámetros ocurrieron.
  5. Persistir con criterio: guardar una configuración aceptada y distinta, no en cada iteración. Si la nueva se rechaza, sigue vigente la última válida.

No modifiques el estado confirmado simplemente porque bajó el umbral: eso debe pasar por la política definida y quedar registrado. Si cambiar reglas durante una alarma no es admisible para el equipo, se rechaza o difiere la actualización. Lo importante es que el resultado no dependa del orden accidental en que se asignaron variables.

Más de una condición

Una regla puede combinar condiciones: «temperatura alta Y puerta cerrada» para distinguir una falla persistente de una apertura, o «temperatura alta O sensor sin datos» para encender un indicador general. Hay que especificar si la persistencia se aplica a cada condición o al resultado combinado: son diseños diferentes.

Por ejemplo, A se cumple durante 2 s y luego B durante otros 2 s. El resultado A O B puede mantenerse verdadero durante 4 s aunque ninguna condición individual haya durado 4 s. Confirmar primero cada regla no produce necesariamente lo mismo que confirmar la expresión combinada.

Si varias reglas proponen acciones sobre la misma salida, una única parte del programa resuelve prioridades: un bloqueo por falla puede prevalecer sobre una orden remota de encendido. Dos callbacks escribiendo el mismo GPIO producen un resultado dependiente de quién corrió último.

09 · Firmware

Implementación en C++

Este motor recibe una lectura en décimas y una marca monótona en milisegundos. valida proviene de la adquisición; se agrega la comprobación de rango del modelo. No lee un sensor ni publica MQTT: devuelve una transición para que el programa principal decida qué hacer.

#include <stdint.h>

enum class Evento { Ninguno, Activada, Recuperada };

struct ReglaAlta {
  int16_t alta = 80, baja = 75;  // décimas de °C
  uint32_t entradaMs = 3000, salidaMs = 2000;
  uint32_t maxSaltoMs = 1500;
  bool activa = false;
  bool pendiente = false, hayUltimo = false;
  uint32_t desde = 0, ultimo = 0;

  Evento evaluar(uint32_t ahora, int16_t c, bool valida) {
    if (hayUltimo && uint32_t(ahora - ultimo) > maxSaltoMs) {
      pendiente = false;
    }
    hayUltimo = true;
    ultimo = ahora;

    if (!valida || c < -400 || c > 1250) {
      pendiente = false;  // conservar activa; calidad va por separado
      return Evento::Ninguno;
    }

    const bool cumple = activa ? c <= baja : c >= alta;
    if (!cumple) {
      pendiente = false;
      return Evento::Ninguno;
    }
    if (!pendiente) {
      pendiente = true;
      desde = ahora;
    }
    const uint32_t espera = activa ? salidaMs : entradaMs;
    if (uint32_t(ahora - desde) < espera) {
      return Evento::Ninguno;
    }
    activa = !activa;
    pendiente = false;
    return activa ? Evento::Activada : Evento::Recuperada;
  }
};

ReglaAlta temperaturaAlta;

// Llamar una vez por nueva muestra; nunca con el último dato repetido.
void procesarMuestra(uint32_t ahora, int16_t c, bool valida) {
  const Evento evento = temperaturaAlta.evaluar(ahora, c, valida);
  // El indicador local puede reflejar temperaturaAlta.activa.
  if (evento != Evento::Ninguno) {
    // Encolar {evento, ahora, c, cfg_v, boot_id, seq}.
    // El envío y sus reintentos se hacen fuera de esta función.
  }
}

En Arduino/ESP32, una tarea de adquisición llama, por ejemplo, a procesarMuestra(uint32_t(millis()), c, valida) cuando tiene una muestra nueva. El fragmento es el núcleo de la regla, no un sketch con setup(), sensor y pines específicos. La adquisición convierte a décimas después de verificar que el valor sea finito y representable; no se convierte un NaN a entero.

Por qué no usar delay(3000)

Dormir tres segundos después de detectar una lectura alta impide comprobar las lecturas intermedias y retrasa otras tareas. Este motor solo guarda el instante inicial y retorna enseguida. La reconexión y la adquisición también deben evitar esperas largas: si bloquean el bucle, no habrá muestras con las que confirmar.

El reloj que vuelve a cero

millis() devuelve un contador sin signo que vuelve a cero aproximadamente cada 50 días, según la referencia oficial de Arduino. Con marcas de 32 bits, uint32_t(ahora - desde) calcula el tiempo transcurrido incluso al cruzar ese retorno. Evitá comparar con ahora >= desde + espera, porque la suma puede desbordar antes que ahora.

La resta presupone que no pasa un ciclo completo de 232 ms sin observar el reloj. Mantené intervalos cortos respecto de ese ciclo y ejecutá la evaluación regularmente. Esta técnica no permite medir una pausa arbitrariamente larga ni conservar tiempos entre reinicios. El simulador Python incluye una prueba que cruza el retorno a cero.

La configuración se supone validada antes de construir o actualizar la regla. Si se cambia, copiá el bloque completo entre evaluaciones y hacé pendiente = false. No uses el valor cero de desde como «no hay período»: t = 0 es un instante válido, por eso C++ tiene un booleano separado.

10 · Simulación reproducible

Simulación y pruebas en Python

El programa reproduce la misma lógica sin placa ni red. La traza base es la del laboratorio. También comprueba igualdad en los límites, cancelación por lectura inválida, huecos entre muestras, persistencia nula y el retorno del reloj a cero. Podés editarlo y ejecutarlo en el navegador.

Python en el navegador. La primera ejecución descarga Pyodide. El cálculo se hace en tu navegador y no necesita un ESP32. Si preferís trabajar localmente, copiá el bloque en un archivo y ejecutalo con Python 3.

class Regla:
    def __init__(self, alta=80, baja=75, entrada=3000,
                 salida=2000, max_salto=1500):
        if not (-400 <= baja < alta <= 1250):
            raise ValueError("Umbrales inválidos")
        if not (0 <= entrada < 2**31 and 0 <= salida < 2**31
                and 0 < max_salto < 2**31):
            raise ValueError("Tiempos inválidos")
        self.alta, self.baja = alta, baja
        self.entrada, self.salida = entrada, salida
        self.max_salto = max_salto
        self.activa = False
        self.desde = None
        self.ultimo = None

    def evaluar(self, t, c):
        t = t & 0xFFFFFFFF
        if (self.ultimo is not None and
                ((t - self.ultimo) & 0xFFFFFFFF) > self.max_salto):
            self.desde = None
        self.ultimo = t
        if type(c) is not int or not -400 <= c <= 1250:
            self.desde = None
            return None, "SIN DATO"
        cumple = c <= self.baja if self.activa else c >= self.alta
        if not cumple:
            self.desde = None
            return None, "ACTIVA" if self.activa else "NORMAL"
        if self.desde is None:
            self.desde = t
        espera = self.salida if self.activa else self.entrada
        if ((t - self.desde) & 0xFFFFFFFF) < espera:
            return None, ("CONFIRMANDO SALIDA" if self.activa
                          else "CONFIRMANDO ENTRADA")
        self.activa = not self.activa
        self.desde = None
        return ("activada", "ACTIVA") if self.activa else ("recuperada", "NORMAL")


def eventos(traza, regla=None):
    regla = regla if regla is not None else Regla()
    resultado = []
    for t, c in traza:
        evento, _ = regla.evaluar(t, c)
        if evento:
            resultado.append((t, evento))
    return resultado


valores = [40, 79, 81, 79, 82, 81, 83, 84, 79, 76, 75, 74, 73]
traza = [(i * 1000, c) for i, c in enumerate(valores)]
regla = Regla()
print("t(s)  °C   estado                  evento")
for t, c in traza:
    evento, estado = regla.evaluar(t, c)
    print(f"{t / 1000:4.0f}  {c / 10:4.1f}  {estado:23} {evento or '-'}")

# Traza base: se cancela la primera entrada, luego se activa y recupera.
assert eventos(traza) == [(7000, "activada"), (12000, "recuperada")]

# Igualdad: 8,0 °C entra y 7,5 °C sale.
limites = [(0, 80), (1000, 80), (2000, 80), (3000, 80),
           (4000, 75), (5000, 75), (6000, 75)]
assert eventos(limites) == [(3000, "activada"), (6000, "recuperada")]

# Una lectura inválida corta la evidencia de entrada.
falla = [40, 90, None, 90, 90, 90, 90, 70, 70, 70]
assert eventos([(i * 1000, c) for i, c in enumerate(falla)]) == [
    (6000, "activada"), (9000, "recuperada")]

# Un hueco no cuenta como tiempo confirmado.
salto = [(0, 40), (1000, 90), (2000, 90), (7000, 90),
         (8000, 90), (9000, 90), (10000, 90)]
assert eventos(salto) == [(10000, "activada")]

# El dato inválido conserva una alarma activa.
r = Regla(entrada=0, salida=0)
assert r.evaluar(0, 90)[0] == "activada"
assert r.evaluar(1000, None) == (None, "SIN DATO")
assert r.activa
assert r.evaluar(2000, 75)[0] == "recuperada"

# Un valor intermedio corta la recuperación, pero no apaga la alarma.
r = Regla(entrada=0)
assert eventos([(0, 90), (1000, 75), (2000, 76), (3000, 75),
                (4000, 75), (5000, 75)], r) == [
    (0, "activada"), (5000, "recuperada")]

# Retorno del reloj de 32 bits: cuatro muestras cubren 3000 ms.
inicio = 0xFFFFFFFF - 1500
reloj = [((inicio + i * 1000) & 0xFFFFFFFF, 80) for i in range(4)]
assert eventos(reloj) == [(1499, "activada")]

print("\n7 pruebas superadas.")
print("Traza base: activada a los 7 s; recuperada a los 12 s.")

Resultado inicial: en t = 2 s empieza una entrada que se cancela en t = 3 s. La entrada iniciada en t = 4 s se confirma en t = 7 s, con 8,4 °C. La salida empieza en t = 10 s y se confirma en t = 12 s, con 7,3 °C. Se generan dos eventos y las siete pruebas terminan sin errores.

Usar la simulación para revisar una regla

Cambiá la lectura de t = 6 s de 8,3 a 7,9 °C. La alarma ya no se activa en t = 7 s: el período se canceló un segundo antes. La primera prueba debe fallar, porque la traza cambió y el resultado esperado sigue siendo el anterior. Ese fallo demuestra que la prueba verifica el comportamiento, no solo que el programa termina.

Después diseñá la nueva expectativa antes de modificar la prueba. Para evaluar un cambio de firmware, conservá trazas de incidentes y comprobá que la versión nueva produce las transiciones previstas. No hacen falta miles de valores aleatorios para descubrir un error de igualdad o un segundo de diferencia: una secuencia breve bien elegida lo expone mejor.

11 · Exploración interactiva

Seguir cada transición

El laboratorio aplica tres estrategias a las mismas muestras: un único umbral sin espera, histéresis sin espera y la regla completa. Elegí los parámetros y presioná «Aplicar y preparar». Luego avanzá una muestra o evaluá toda la traza. «Reiniciar recorrido» vuelve al principio conservando la configuración ya aplicada.

Alarma local de temperatura alta

La configuración aplicada aparecerá aquí.

Blanco: temperatura válida. Líneas discontinuas: umbrales. Naranja: eventos de la traza completa. Los huecos y las muestras inválidas interrumpen la línea. La tabla inferior avanza con los botones.

Última muestra—
Temperatura—
Evaluación actualSIN EVALUAR
Alarma confirmadaNo
Eventos emitidos0

Preparando recorrido.

Muestras evaluadas: el evento aparece una sola vez por transición.
t (s)°CEvaluaciónEvento

Recorrido guiado

  1. Con los valores iniciales, aplicá y evaluá toda la traza. Hay dos eventos: activada a los 7 s y recuperada a los 12 s. La alarma no entra por el cruce breve de t = 2 s.
  2. Pasá a Un umbral, sin espera. En la misma traza hay cuatro eventos: activada a los 2 s, recuperada a los 3 s, activada a los 4 s y recuperada a los 8 s. No publicamos en cada lectura: incluso esta estrategia solo emite transiciones.
  3. Pasá a Histéresis, sin espera. Hay dos eventos: activada a los 2 s y recuperada a los 10 s. El ruido ya no rearma la alarma, pero el primer pico breve todavía la activa.
  4. Elegí Ruido alrededor de 8 °C. Con un umbral hay ocho eventos; con histéresis hay dos; con la regla completa no hay ninguno. La persistencia rechaza las entradas breves.
  5. Volvé a la regla completa y elegí Lectura inválida. La entrada de t = 1 s se cancela en t = 2 s. La confirmación vuelve a empezar en t = 3 s y se activa a los 6 s; recupera a los 9 s.
  6. Elegí Hueco de cinco segundos. La lectura de t = 7 s inicia un período nuevo; no activa contando desde t = 1 s. La activación ocurre a los 10 s.
  7. Con la traza base y la regla completa, subí la entrada a 5 segundos. No llega a activarse: la lectura de t = 8 s rompe la condición antes de confirmar. Bajá a 0 s para observar una entrada inmediata.

En el modo de un solo umbral se activa con c ≥ alta y se recupera con c < alta. Repetir exactamente el límite mantiene la alarma activa, pero oscilar por arriba y por abajo sigue generando transiciones. Con décimas enteras, c < 80 equivale a c ≤ 79; el motor aprovecha esa equivalencia para reutilizar la misma lógica. Los campos numéricos del laboratorio se limitan a 0–20 °C para explorar estas trazas.

Desafío: ¿una regla que emite menos eventos siempre es mejor?

No. Una confirmación de cinco segundos da cero eventos en la traza base, porque la subida no dura lo suficiente. Puede ser lo deseado si esos cambios son inocuos, o una falla si debían avisarse. Compará falsas activaciones, eventos omitidos y retraso con el requisito del proceso. El objetivo no es minimizar mensajes a cualquier costo.

12 · Criterio de diseño

Método y confusiones frecuentes

  1. Definí la consecuencia: qué significa alarma y qué acción se ejecuta. Especificá el máximo retraso aceptable.
  2. Fijá unidades y comparaciones: alta, baja, igualdad y rango admitido. Documentá cómo llega el dato y con qué calidad.
  3. Medí ruido y período: elegí la histéresis, los tiempos de confirmación y la separación máxima con esas mediciones.
  4. Dibujá estados y cancelaciones: incluí retorno a normal, pérdida de datos y arranque. Evitá usar un único «normal» para todo.
  5. Separá evaluación, acción y transporte: la regla no espera una respuesta del servidor para cambiar su estado local.
  6. Versioná la configuración: cada evento debe poder explicarse con la regla que estaba aplicada en ese momento.
  7. Probá los bordes: igualdad, una muestra antes de confirmar, un hueco, dato inválido, reloj que retorna a cero y reinicio durante una alarma.
  8. Repetí trazas reales: compará la lógica nueva con incidentes conocidos antes de aplicarla a todos los dispositivos.
«Histéresis y banda muerta son lo mismo»

La banda muerta del tema 31 decide si conviene enviar una lectura comparándola con la última enviada. La histéresis de este tema conserva un estado entre dos umbrales. Una cámara puede usar banda muerta para la telemetría e histéresis para su alarma, con parámetros distintos.

«Si filtro antes, ya no necesito confirmación»

Un promedio puede suavizar el ruido y también esconder picos o retrasar la respuesta. No expresa por sí solo que la condición deba durar tres segundos. Elegí qué señal usa cada regla y medí el retraso total de la cadena.

«Una lectura inválida recupera la alarma»

Solo una lectura válida que cumpla la condición de salida puede aportar evidencia de recuperación. La calidad inválida cancela el período pendiente y mantiene la alarma confirmada. El diagnóstico de sensor se reporta aparte.

«QoS 1 ya guarda lo que generé sin conexión»

QoS describe la entrega de los mensajes que participan del intercambio MQTT. Los eventos que el firmware genera mientras está desconectado necesitan una cola local con capacidad y política de reintento; para sobrevivir a un corte de energía debe ser persistente. Una sesión del broker no almacena por sí sola lo que el dispositivo nunca le entregó.

«Esperar tres segundos confirma tres segundos»

Esperar sin medir no prueba que la condición se mantuvo. La confirmación requiere muestras válidas intermedias con separación acotada. Si el programa se bloquea y vuelve cinco segundos después, el ejemplo cancela el período y empieza de nuevo.

«Reconocer la alarma desde el panel la devuelve a normal»

El reconocimiento indica que alguien recibió el aviso. La recuperación describe la medición. Si se necesita un rearme manual, se modela explícitamente y solo se acepta bajo las condiciones previstas para ese equipo.

13 · Práctica guiada

Predecí antes de comprobar

1. El valor en el límite

La alarma está inactiva y recibe 8,0 °C en t = 0, 1, 2 y 3 s. Luego recibe 7,5 °C en t = 4, 5 y 6 s. ¿Cuándo emite eventos con la regla del ejemplo?

Ver solución

Activa en t = 3 s y recupera en t = 6 s. La igualdad cumple ambas condiciones en su estado correspondiente. Cuatro muestras cubren los tres segundos de entrada y tres muestras cubren los dos de salida.

2. Una muestra cancela

Las lecturas de t = 0, 1 y 2 s son 8,3 °C; la de t = 3 s es 7,9 °C. ¿Se activa por haber pasado tres segundos desde la primera?

Ver solución

No. En t = 3 s la condición de entrada ya no se cumple, así que se cancela antes de comprobar la duración. No hubo tres segundos confirmados con una lectura actual que todavía sostuviera la condición.

3. La lectura que falta

Hay 9,0 °C en t = 1 y 2 s. La siguiente muestra llega en t = 7 s y también es 9,0 °C. ¿Puede activar ahí?

Ver solución

No con la separación máxima de 1,5 s. El hueco es de cinco segundos y cancela el período anterior. En t = 7 s empieza otro; con lecturas válidas altas en t = 8, 9 y 10 s, activa a los 10 s.

4. ¿Qué significa 7,8 °C?

Un dispositivo marca 7,8 °C y alarma activa; otro, 7,8 °C y alarma inactiva. Ambos usan alta = 8,0 °C y rearme = 7,5 °C. ¿Hay necesariamente un error?

Ver solución

No. El valor está entre los dos umbrales y conserva la historia. El primero puede haber activado antes y todavía no haber recuperado; el segundo puede no haber activado nunca. Para diagnosticarlo hacen falta el estado anterior y los eventos, no solo la última lectura.

5. Presupuesto de retraso

Un requisito pide avisar en menos de 4 s ante una condición sostenida. Se mide cada 2 s y se confirman 3 s. ¿Alcanza, aun sin red ni filtrado?

Ver solución

No. Desde la primera muestra favorable se necesitan ceil(3 / 2) × 2 = 4 s. A eso se suma hasta casi 2 s de espera por la primera muestra: el retraso físico puede acercarse a 6 s. Se debe revisar el período, la persistencia o el requisito.

6. Confirmación de salida interrumpida

La alarma ya está activa. Recibe 7,5 °C en t = 10 s, 7,6 °C en t = 11 s y luego 7,4 °C en t = 12, 13 y 14 s. ¿Cuándo se recupera?

Ver solución

En t = 14 s. La lectura de 7,6 °C cancela la primera recuperación porque está por encima de 7,5 °C. El período nuevo empieza en t = 12 s y llega a dos segundos en t = 14 s. La alarma se mantiene activa durante ambos intentos.

7. Duplicados y desconexión

Un evento se genera sin Wi-Fi, se encola y luego se entrega dos veces con QoS 1. ¿Cuántas activaciones locales y cuántos registros lógicos debería producir?

Ver solución

Una activación local y un registro lógico. La transición ocurre antes del transporte. El reintento conserva device, boot_id y seq; el servidor usa esa identidad para reconocer el duplicado. Si hay un reinicio antes de enviar, conservar el evento requiere una cola persistente.

8. Alarma por tanque vacío

Se desea activar al bajar a 20 % y recuperar al subir a 30 %. ¿Qué comparaciones corresponden? ¿Cuál es el ancho de histéresis?

Ver solución

Con la alarma inactiva se confirma entrada si nivel <= 20; con la alarma activa se confirma salida si nivel >= 30. El ancho es 10 puntos porcentuales. También hay que elegir duraciones y calidad del sensor de nivel; no se reutilizan automáticamente los tiempos de temperatura.

14 · Síntesis

Ideas para recordar

  • Una regla completa define unidades, comparaciones, tiempos, calidad del dato, transiciones y política de arranque.
  • La histéresis usa dos umbrales y conserva el estado entre ellos; la confirmación temporal rechaza condiciones demasiado breves.
  • Con muestras cada segundo, cuatro muestras cubren tres segundos. Usar tiempo transcurrido evita confundir duración con cantidad.
  • Un dato inválido o un hueco cancela la confirmación pendiente y conserva la alarma estable. La calidad del sensor se informa aparte.
  • Estado, evento y acción tienen funciones distintas. Un evento se genera al cambiar de estado; el envío no debe bloquear ni revertir la decisión local.
  • La configuración se valida y aplica como un bloque completo, con versión. La política ante un cambio de parámetros debe ser explícita.
  • En la traza base, la regla completa activa a los 7 s y recupera a los 12 s; el comparador simple produce cuatro transiciones.
  • Las pruebas con trazas breves permiten comprobar límites, ruido, huecos, errores de sensor y retorno del reloj a cero antes de usar hardware.

¿Listo para el tema 33?

Antes de avanzar, escribí una regla para un sensor de tu proyecto. Anotá entrada, salida, duraciones, calidad y acciones. Prepará una traza normal, una con ruido y una con una lectura perdida, y predecí cada transición antes de ejecutarla.

En el próximo tema, Almacenamiento local, trabajamos con flash, LittleFS y tarjeta SD para conservar lecturas y eventos cuando no hay conexión. Una regla ya puede decidir sola; el siguiente paso es que también pueda recordar lo que ocurrió aunque el dispositivo se reinicie.