Este proyecto, publicado por Electronics For You el 15 de octubre, implementa una pasarela IoT inteligente que conecta redes inalámbricas de sensores de largo alcance con un panel web moderno y análisis con IA. Con un transceptor SX1278 manejado por la biblioteca RadioLib, el receptor capta los paquetes LoRa que llegan en la banda de 433 MHz, calcula métricas de calidad de señal (RSSI y SNR) y ordena los datos válidos en un JSON.

Fig. 1, prototipo final del autor

¿Cómo funciona la pasarela?

El sistema usa el framework Arduino RouterBridge para exponer un endpoint RPC (getLatestPayload), con el que un router acompañante o un sistema anfitrión obtiene al instante los datos de los sensores. Esos datos van directo a un panel web adaptable que muestra la telemetría en tiempo real, las tendencias ambientales y el estado de cada nodo. Un componente de IA integrado analiza los flujos de datos para detectar anomalías, anticipar fallas de sensores o entregar diagnósticos automáticos. El proyecto combina comunicación de radio de largo alcance, llamadas a procedimientos remotos, visualización web en tiempo real y análisis con IA en una solución de monitoreo completa.

Código del receptor LoRa

Primero hay que instalar la biblioteca necesaria en el Arduino IDE. En el menú Sketch se elige "Manage Library", se busca el nombre de la biblioteca y se pulsa "Install". Este proyecto requiere la biblioteca RadioLib de Jan Gromes.

Fig. 2, instalación de bibliotecas

El código del receptor y del acuse de recibo con RadioLib se organiza en ocho pasos.

Paso 1. Inclusión de la biblioteca y pines

  • Línea 1. #include <RadioLib.h> importa la biblioteca universal de comunicación inalámbrica que controla el módulo de radio LoRa.
  • Líneas 3 a 6. Definen la interfaz SPI y los pines de control conectados al SX1278. LORA_CS (10) es el pin de selección de chip para SPI. LORA_DIO0 (2) es el pin digital que señala las interrupciones de hardware, por ejemplo un paquete recibido. LORA_RESET (9) reinicia por hardware el transceptor. LORA_BUSY (RADIOLIB_NC) queda "no conectado", porque el SX1278, a diferencia de la serie SX1262, no tiene un pin BUSY dedicado.

Paso 2. Objetos globales

  • Línea 8. Crea el objeto de radio SX1278 con el mapeo de pines del paso 1.
  • Línea 11. Declara la cadena global latestJsonPayload, que parte en {}. Guarda el último paquete válido para que lo lean componentes de nivel superior, como el lado Linux de la Arduino UNO Q a través de RPC.

Paso 3. Configuración inicial con setup()

  • Líneas 14 y 15. Inician Monitor.begin(9600), la interfaz serial de depuración del ecosistema Arduino App Lab y UNO Q, con un tiempo de espera de 3 segundos para que la placa arranque aunque no haya un terminal serial conectado.
  • Línea 17. Llama a radio.begin(...) para fijar los parámetros de radio. Frecuencia portadora de 433,0 MHz, ancho de banda de 125,0 kHz, factor de dispersión SF7, tasa de codificación 4/5, palabra de sincronización 0x12, potencia de transmisión de 10 dBm y preámbulo de 8 símbolos.
  • Líneas 18 a 24. Si la inicialización devuelve RADIOLIB_ERR_NONE (éxito), activa encabezados explícitos y la verificación CRC por hardware para descartar tramas corruptas, y llama a radio.startReceive() para dejar el módulo escuchando de forma continua y asíncrona.
  • Líneas 25 a 28. Si la inicialización falla, imprime el código de error y detiene la ejecución en un bucle while(1).

Paso 4. Detección de paquetes en loop()

Línea 32. Revisa if (digitalRead(LORA_DIO0) == HIGH). El SX1278 pone DIO0 en alto apenas detecta en el aire un paquete válido y lo guarda en su búfer FIFO interno.

Paso 5. Extracción del paquete con dos estrategias

  • Líneas 33 y 34. Intenta una lectura estándar de alto nivel con radio.readData(payload).
  • Líneas 36 a 45, lógica de rescate. Si la extracción estándar falla, por ejemplo por un error al interpretar la cadena, crea un búfer de bytes uint8_t buffer[16], lee a mano los primeros 16 bytes del FIFO, recorre los bytes y se queda solo con los caracteres ASCII imprimibles (rango 32 a 126). Así reconstruye el paquete y no se pierden datos.

Paso 6. Métricas de calidad de señal

  • Líneas 47 y 48. Consultan los registros del SX1278 para diagnosticar el enlace. getRSSI() mide la intensidad de la señal recibida en dBm y getSNR() calcula la relación señal a ruido en dB.

Paso 7. Filtrado, JSON y acuse de recibo

  • Línea 50. Evalúa si el paquete es válido. Solo se procesa si no está vacío y supera un umbral de claridad de señal (snr > -12.0 dB).
  • Líneas 51 a 57. Da formato JSON al paquete, el RSSI y el SNR (por ejemplo {"payload":"hello","rssi":-45.00,"snr":8.50}) y actualiza la variable global latestJsonPayload.
  • Línea 60. Envía el JSON a Monitor para depuración.
  • Líneas 63 a 66, acuse de recibo. Espera 50 ms para que el nodo emisor pase a modo escucha, ejecuta radio.transmit("ok") para devolverle un ACK y restablece los encabezados explícitos y el CRC del transceptor.

Paso 8. Vuelta al modo escucha

  • Línea 69. Ejecuta otra vez radio.startReceive() para que el módulo vuelva a la recepción pasiva después del ACK.
  • Línea 71. delay(20) agrega una pausa corta entre lecturas para estabilizar la ejecución y bajar la carga de CPU cuando no hay actividad.
Fig. 3, página web en Flask

El panel web y el motor de anomalías

Estructura de directorios

Fig. 4, estructura del proyecto

Arquitectura del proyecto

El proyecto implementa un panel de telemetría LoRa con inteligencia en el borde, organizado en cuatro capas.

  • Capa de ingesta por socket TCP. Un hilo en segundo plano (tcp_bridge_reader_thread) escucha de forma continua en el puerto TCP 7500 (127.0.0.1) los JSON que envía el reenviador de paquetes del hardware LoRa.
  • Motor de tendencias y aprendizaje automático (DynamicTrendMLModel). Clasifica la telemetría con expresiones regulares en categorías (temperatura y humedad, sonda de nivel de agua, contador o latido). Sigue la evolución de la línea base con un promedio móvil exponencial (EWMA) con factor alfa de 0,15. Calcula picos de tasa de cambio por minuto frente a umbrales de desviación estándar móvil de 3 sigma. Y usa un modelo Isolation Forest de Scikit-Learn para detectar anomalías no lineales en el espacio de temperatura, humedad y RSSI.
  • Persistencia de datos. Crea y alimenta de forma automática el archivo data/sensor_history.csv con registros estructurados.
  • Presentación web en Flask y reportes PDF. Sirve interfaces dinámicas con Chart.js para la visualización en tiempo real y usa ReportLab para generar reportes de auditoría en PDF.

Backend (app.py)

  • Importaciones y configuración. Importa módulos estándar (socket, threading, re, csv) junto con herramientas numéricas (pandas, numpy, sklearn). Fija Matplotlib en el backend no interactivo "Agg" para que funcione en un servidor.
  • Función init_csv(). Valida el directorio de destino y crea sensor_history.csv con encabezados estándar (timestamp, raw_payload, data_type, parsed_json, rssi, snr, is_anomaly, anomaly_reason).
  • Clase DynamicTrendMLModel. fit_model() ajusta un modelo IsolationForest cada vez que hay 15 muestras o más. classify_and_parse() extrae los parámetros físicos con expresiones regulares. detect_anomaly() valida las lecturas con la desviación de velocidad, límites absolutos (sobre 55 °C o bajo -10 °C), condiciones de estanque vacío y el puntaje de Isolation Forest.
  • tcp_bridge_reader_thread(). Corre en un hilo daemon, interpreta de forma continua los objetos JSON separados por salto de línea que llegan al puerto 7500 y guarda los metadatos extraídos.
  • Ruta download_report(). Reúne la telemetría reciente, arma en memoria cuatro gráficos de líneas con BytesIO, estructura tablas de detalle con estilo y devuelve un documento PDF generado de forma automática.

Interfaz web (templates/)

  • login.html. Exige autenticación básica (admin / admin123) con sesiones de Flask y estilos de Bootstrap 5.
  • index.html. Consulta /api/data cada 2.000 milisegundos y muestra tarjetas con métricas en tiempo real y un gráfico de líneas dinámico de 20 muestras.
  • history.html. Pide /api/history_all para llenar una tabla de auditoría y marca en rojo las tramas anómalas con la clase CSS anomaly-row.
  • analytics.html. Integra Chart.js, hammerjs y chartjs-plugin-zoom para desplazar y ampliar los históricos de temperatura, humedad, RSSI y SNR.

¿Cómo se conecta el módulo Ra-02?

Estas son las conexiones entre el módulo LoRa Ai-Thinker Ra-02 y la Arduino UNO Q, según el diagrama del circuito.

Alimentación y tierra

  • Tierra común (GND). El pin GND del conector de alimentación de la Arduino se une con el cable negro a los pines de tierra del Ra-02 para fijar una referencia eléctrica común.
  • Voltaje de operación. El Ra-02 funciona estrictamente con lógica y alimentación de 3,3 V. Hay que asegurar que el montaje entregue 3,3 V al pin de 3,3 V del módulo.

Bus SPI

  • SCK (pin 13). Cable rosado o rojo. Lleva la señal de reloj que genera la Arduino para sincronizar la transmisión.
  • MISO (pin 12). Cable verde. Lleva los datos que salen del Ra-02 hacia la Arduino.
  • MOSI (pin 11). Cable azul. Lleva los datos que salen de la Arduino hacia el Ra-02.
  • NSS / CS (pin 10). Cable café. La Arduino lo usa para habilitar y seleccionar el Ra-02 en el bus SPI.

Control y reinicio

  • RST (pin 9). Cable morado. Permite que la Arduino reinicie por hardware el chip SX1278 durante la inicialización.

Prueba del sistema

Para probar el sistema, primero se revisan todas las conexiones entre la Arduino UNO Q y el módulo Ra-02 para evitar cortocircuitos. Luego se encienden la pasarela y el nodo remoto de campo, que transmite en vivo temperatura y humedad desde un sensor DHT11. Después se inicia el puente de socket TCP en el puerto 7500 para comprobar que los paquetes de radio se interpretan, se guardan en data/sensor_history.csv y pasan por los motores de anomalías EWMA e Isolation Forest. Al final se entra al panel web en http://localhost:5050 para confirmar que las métricas del DHT11 se actualizan cada 2 segundos, verificar el desplazamiento de los gráficos en la página de análisis y generar un reporte de auditoría en PDF descargable.

Fig. 5, prueba del dispositivo con el panel de IA, y a la derecha su arquitectura en diagrama de bloques

Esquemáticos

Fig. 7, diagrama del nodo con el kit de evaluación MAX32672 FTHR y un DHT11