¿Alguna vez un sensor te despertó con una alarma a las 3 de la mañana por una lectura que duró un segundo y nunca pasó nada? Esos falsos positivos son el cáncer silencioso de cualquier sistema de monitoreo: gastan tu tiempo, te hacen desconfiar y, cuando llega la alerta de verdad, ya la estás ignorando. En esta guía vas a aprender a convertir un monitor de calidad de aire que dispara alertas por una sola medición en una plataforma de detección de anomalías que decide en base al historial, usando la memoria persistente que entrega Particle Ledger. Al final vas a saber crear ledgers en la nube, escribir una función Logic en JavaScript que calcula percentiles sobre una ventana de lecturas, y conectar el resultado a Slack y a un dashboard.

Monitor de calidad de aire armado con una placa Particle Boron y sensores ambientales

El problema de fondo: una lectura no es una tendencia

El punto de partida es un monitor de calidad de aire que mide CO2, partículas de polvo y un índice general de aire, y que envía esos datos a la nube de Particle cada cinco minutos. Funciona, pero tiene un defecto de diseño: dispara la alerta apenas una lectura cruza el umbral. Un auto que pasa fumando, una cocina que se prende, o simplemente ruido del sensor bastan para gatillar un aviso que no representa nada real.

La raíz del asunto es estadística. Una sola muestra no distingue entre un evento puntual y una condición sostenida que sí es peligrosa. Lo que necesitamos es que el dispositivo "recuerde" lo que vio en la última hora y recién entonces decida. Y ahí es donde el firmware del dispositivo se queda corto: la placa no tiene memoria persistente confiable para acumular series de tiempo entre reinicios. La solución no va en el equipo, va en la nube.

La estrategia: percentil 90 sobre una ventana de tiempo

En vez de preguntarnos "¿esta lectura superó el umbral?", vamos a preguntarnos algo mucho más robusto: "¿el percentil 90 de las últimas N lecturas superó el umbral?". El percentil 90 ignora los picos aislados (que quedan en el 10% superior descartado) y solo se dispara cuando la mayoría de las mediciones recientes son altas. Es la diferencia entre reaccionar a un estornudo y reaccionar a una fiebre sostenida.

Para este proyecto la regla es: alertar cuando el CO2 supere 1000 durante las últimas 12 lecturas. Como el dispositivo publica cada cinco minutos, 12 lecturas equivalen a una hora completa de aire malo antes de molestar a nadie. Ese es el corazón de la detección de anomalías: contexto temporal en lugar de gatillos instantáneos.

Hardware: el monitor base

Este tutorial parte de un proyecto existente, así que el armado físico ya está hecho. El dispositivo es una placa Particle Boron (un microcontrolador con conectividad celular integrada, ideal para puntos sin WiFi) acompañada de sensores ambientales montados sobre una base. El Boron publica los datos crudos a la nube de Particle bajo el topic aqas-env-raw, con campos como eco2 (CO2 equivalente), aq (índice de aire) y datos de polvo.

Lo importante para esta guía es entender que toda la inteligencia nueva vive en la nube, no en el firmware. No vas a recompilar ni reflashear nada en la placa: el dispositivo sigue enviando exactamente los mismos datos que antes. Todo lo que construyas acá es lógica del lado de Particle Cloud, lo que significa que puedes iterar y corregir sin tocar el hardware desplegado en terreno.

Qué es Particle Ledger y por qué lo necesitamos

Particle Ledger es el componente que le da memoria a nuestros dispositivos. Permite guardar y analizar datos en la nube por dispositivo, por producto o por cuenta de usuario. En la práctica, es un almacén clave valor (tipo gemelo digital) que persiste entre publicaciones, justo lo que nos faltaba para acumular la serie de tiempo.

Particle ofrece tres tipos de Ledger, y conviene tener clara la diferencia antes de elegir:

  • Cloud Ledger: guarda información solo en la nube, sin sincronizar al dispositivo.
  • Device to Cloud Ledger: almacenamiento en el dispositivo que sube automáticamente a la nube.
  • Cloud to Device Ledger: datos que se setean en la nube y bajan al dispositivo.

Selector con los tres tipos de Ledger en la consola de Particle

Para nuestro caso usamos Cloud Ledger: el análisis ocurre íntegramente en la nube y el dispositivo ni se entera. Vamos a crear dos ledgers separados, uno para la configuración de umbrales y otro para los datos de la serie de tiempo. Separarlos no es capricho: el de umbrales casi nunca cambia, mientras que el de la serie se reescribe en cada ciclo.

Paso 1: crear los Ledgers

Entra a la sección Ledger desde el menú lateral de la consola de Particle y haz clic en "Create new Ledger". El primero se llama aqas-thresholds y guarda la configuración: el umbral de CO2 y cuántas lecturas componen la ventana.

Pantalla inicial de Ledgers en la consola de Particle con el botón para crear el primero

Después ve a la pestaña de instancias y crea una nueva instancia con los valores de configuración (por ejemplo co2_threshold: 1000 y no_of_readings: 12). Luego repite el proceso para crear un segundo Ledger llamado aqas-time-series, y en su instancia inicializa el arreglo vacío {"co2": []}. Ese arreglo es el que se irá llenando lectura a lectura hasta completar la ventana.

Diálogo de nueva instancia de Ledger con el arreglo co2 vacío

Paso 2: la función Logic que hace el trabajo pesado

Ahora viene la parte interesante. Navega a la función "Logic" desde el menú lateral y crea una función basada en eventos. Una función Logic de Particle es, básicamente, una función JavaScript que se ejecuta automáticamente cada vez que llega un evento; corre en la nube, así que no consume recursos del dispositivo. Si nunca trabajaste con Logic, vale la pena revisar la documentación de Particle antes de seguir.

Copia y pega el siguiente código y despliega la función. Lo que hace, en orden: lee el umbral y el tamaño de ventana desde el ledger de configuración, agrega la lectura nueva de eco2 al arreglo de la serie de tiempo, y solo cuando junta las 12 lecturas calcula el percentil 90. Si ese percentil supera el umbral, publica el evento aqas-alert; en cualquier caso, vacía el buffer para empezar una ventana nueva. También reemite cada lectura como aqas-time-series para poder graficarla después.

JavaScript
import Particle from 'particle:core';

const  findPercentile = (arr, pct)=> {
  // Sort the array in ascending order
  const sortedArr = arr.slice().sort((a, b) => a - b);

  // Calculate the index for the 90th percentile
  const index = Math.ceil(sortedArr.length * pct* 0.01) - 1;

  // Return the value at the calculated index
  return sortedArr[index];
}

export default function process({ functionInfo, trigger, event }) {
  const deviceThresholdsLedger = Particle.ledger("aqas-thresholds");
  const timeSeriesLedger = Particle.ledger("aqas-time-series");
  const data = JSON.parse(event.eventData);
  const co2_threshold = deviceThresholdsLedger.get().data.co2_threshold;
  const no_of_readings = deviceThresholdsLedger.get().data.no_of_readings;
  const co2_ts = timeSeriesLedger.get().data.co2||[];
  co2_ts.push(data.eco2);

  if(co2_ts.length >= no_of_readings){
    //calculate 90th percentile 
    const pctCO2 = findPercentile(co2_ts,90);
    console.log("percentile value ", pctCO2);
    timeSeriesLedger.set({co2: []});
    if( pctCO2 >=co2_threshold ){
      Particle.publish("aqas-alert", {co2: pctCO2}, { productId: event.productId });
    }
  }else{
    timeSeriesLedger.set({co2: co2_ts});
  }

  Particle.publish("aqas-time-series", {co2: data.eco2, aq: data.aq, seq: co2_ts.length,  deviceId: event.deviceId}, { productId: event.productId, deviceId: event.deviceId });
}

Fíjate en un detalle clave de la implementación: el cálculo del percentil ordena el arreglo de menor a mayor y toma el valor en la posición Math.ceil(longitud * 90 * 0.01) - 1. Es un percentil "nearest rank" simple, sin interpolación, perfectamente suficiente para 12 muestras y mucho más liviano que traer una librería estadística completa. Y como la ventana se reinicia (timeSeriesLedger.set con el arreglo vacío) apenas se completa, evitamos que el arreglo crezca sin control y consuma cuota del ledger.

Paso 3: ver los eventos en acción

Una vez desplegada la Logic, anda a la página de Events de la consola. Vas a ver aparecer los eventos aqas-env-raw que manda el dispositivo, los aqas-time-series que reemite la función con su número de secuencia (seq), y lo más importante: el evento aqas-alert, que solo aparece cuando el percentil 90 del CO2 cruzó el umbral. Ese evento es la señal limpia que queremos propagar, ya filtrada de falsos positivos.

Página de eventos de Particle mostrando aqas alert y aqas time-series con sus datos

Paso 4: integraciones (Slack y dashboard)

Con la alerta confiable lista, conviene llevarla a donde la veas. El tutorial original arma dos integraciones.

Notificación a Slack: se usa una integración tipo webhook. Necesitas una URL de webhook de Slack, y al configurar la integración el nombre del evento debe coincidir exactamente con el que publica tu Logic (aqas-alert). Guarda, habilita, y vuelve a la página de Events para confirmar que el webhook se dispara; deberías recibir la notificación en tu app de Slack.

Dashboard en Ubidots: para visualizar las lecturas se envían los datos del evento aqas-time-series a Ubidots mediante su integración nativa. De nuevo, el nombre del evento importa. Tras guardar y habilitar, en pocos minutos (según la frecuencia con que tu dispositivo publica) las variables empiezan a poblarse en tu cuenta de Ubidots.

Variables de calidad de aire poblándose en el dashboard de Ubidots

Y listo: pasamos de un monitor que gritaba por cualquier cosa a un dispositivo que aprende de su propio historial y solo avisa cuando de verdad importa. Sin recompilar firmware, solo con lógica en la nube.

Variantes y mejoras

Una vez que tengas la base funcionando, hay varias formas de subirle el nivel que el tutorial original no cubre:

  • Umbral adaptativo por hora del día: en vez de un co2_threshold fijo, guarda en el ledger de configuración un arreglo de 24 umbrales (uno por hora). Una pieza cerrada de noche tolera más CO2 que una oficina llena a mediodía; ajustar el umbral por contexto reduce aún más los falsos positivos.
  • Detección sobre varias variables a la vez: el código actual solo evalúa CO2, pero el dispositivo también manda aq y polvo. Replica la lógica de percentil para esas series y dispara la alerta si cualquiera de las tres cruza su umbral, o exige que dos coincidan para confirmar un evento real.
  • Persistir la serie completa para análisis posterior: hoy el buffer se vacía al completar la ventana. Si quieres conservar el histórico largo para detectar tendencias semanales, suma una integración a una base de datos de series de tiempo (InfluxDB, o el mismo Ubidots con retención) en paralelo al cálculo en vivo.
  • Suavizado con media móvil: además del percentil 90, puedes calcular una media móvil exponencial para distinguir un alza gradual (ventilación deficiente) de un salto abrupto (fuga puntual), y etiquetar la alerta según el tipo.

Personalización para Chile

Este proyecto usa la plataforma celular de Particle, que en Chile es de nicho, pero la parte que importa (los sensores de calidad de aire) la consigues local en MechatronicStore y funcionan igual con una placa que ya tengas, sea un ESP32, un ESP8266 o un Arduino. Las lecturas de eco2 y polvo que procesa la lógica salen exactamente de estos sensores:

  • Sensor de Gas CJMCU-811 (SKU GP1-6): $16.920. Es un CCS811 que entrega CO2 equivalente (eCO2) y TVOC por I2C, justo el dato eco2 que la función Logic acumula para el percentil. El equivalente directo del sensor de CO2 del tutorial.
  • Sensor de Polvo DSM501A PM2.5 (SKU G-231): $9.990. Cubre la lectura de polvo y material particulado fino que el monitor original también vigila.

Si quieres una alternativa más moderna al CJMCU-811, el Sensor de Calidad de Aire SGP40 VOC (SKU GL3-5, $22.810) mide compuestos orgánicos volátiles con mejor estabilidad a largo plazo. Y si buscas algo más económico para empezar, el Sensor de Calidad de Aire MQ-135 (alrededor de $4.090) sirve para prototipar la lógica antes de invertir en sensores calibrados. La placa Particle Boron no se vende en Chile, pero como toda la detección de anomalías corre en la nube, puedes adaptar el mismo concepto publicando los datos de tu ESP32 a cualquier backend con lógica equivalente.

Recursos

Versión chilena con sensores en stock local en MechatronicStore. Guía basada en el trabajo original de Mithun Das.