Sistema de Monitorización de Calidad del Aire para Ciudades Inteligentes con LoRa
Componentes y Materiales
| Componente | Cantidad | Notas |
|---|---|---|
| Arduino UNO | 1 | Controlador principal del nodo sensor |
| Seeed Base Shield V2 | 1 | Expansión Grove para conexión sin soldadura |
| NodeMCU ESP8266 Breakout Board | 1 | Pasarela LoRa-WiFi hacia la nube |
| Sensor de Sonoridad Seeed Grove | 1 | Medición de nivel de ruido (dB) |
| Sensor de Calidad del Aire Seeed Grove v1.3 | 1 | Detección de gases contaminantes |
| Sensor Temperatura/Humedad DHT11 (Grove) | 1 | Parámetros meteorológicos básicos |
| Módulo LoRa Reyax RYLR998 | 1 | Transceptor larga distancia, baja potencia |
| Panel Solar Seeed 80×100 mm 1 W | 1 | Alimentación autónoma sostenible |
| Seeed LiPo Rider Pro | 1 | Cargador Li-ion con boost 5 V |
| Batería Recargable 3,7 V (Li-ion) | 1 | Respaldo nocturno / baja insolación |
Herramientas y Equipos
- Impresora 3D (genérica) – Para fabricar la carcasa estanca.
Software y Servicios en Línea
- Arduino IDE – Programación de ambos microcontroladores.
- Grandeur – Plataforma IoT backend (SDK, autenticación, base de datos en tiempo real).
- Grandeur Canvas – Constructor de paneles de control drag-and-drop para visualización.
Descripción del Proyecto
Ciudad Inteligente y Calidad del Aire
Según el Banco Mundial, una ciudad inteligente aprovecha la tecnología para ofrecer servicios públicos altamente eficientes y un entorno urbano sostenible. La monitorización ambiental en tiempo real es central en esta visión: identifica fuentes de contaminación y fundamenta decisiones basadas en datos, ya sea optimizando flujos de tráfico o recomendando rutas peatonales más seguras.
Problema y Solución
La contaminación atmosférica urbana representa riesgos significativos para la salud; los datos oportunos son esenciales para su mitigación. Redes IoT de nodos sensor de bajo coste pueden capturar continuamente parámetros de calidad del aire y meteorológicos. Combinadas con una capa de comunicación fiable y de bajo consumo, estas redes permiten a las autoridades detectar puntos calientes de contaminación y responder proactivamente.
Plan del Proyecto
Nuestra solución monitoriza métricas clave —ruido, temperatura, humedad y calidad del aire— y transmite los datos de forma inalámbrica a una plataforma en la nube. El nodo completo se alimenta con un panel solar de 1 W y respaldo de batería Li-ion, garantizando operación autónoma. Un panel de control en la nube visualiza los datos en tiempo real, facilitando insights inmediatos y análisis de tendencias a largo plazo.
En lugar de conectar cada sensor directamente a la nube —enfoque que incrementa consumo y coste—, agregamos datos de múltiples nodos cercanos y los transmitimos a través de una única pasarela LoRa. Las características de largo alcance y bajo consumo de LoRa la hacen ideal para despliegues urbanos densos.
Elegimos Grandeur como plataforma backend por sus SDKs amigables para Arduino, NodeMCU y Raspberry Pi, y su constructor de dashboards visual.
La energía solar proporciona una fuente sostenible y libre de mantenimiento para nodos remotos, mientras que la batería Li-ion garantiza operación durante periodos de baja insolación.
Construcción del Nodo Sensor
El nodo integra un sensor de calidad del aire Grove, un sensor de sonoridad Grove y un sensor DHT11 de temperatura/humedad —todos se enchufan al Seeed Base Shield sin soldadura, simplificando ensamblaje y mantenimiento.
La alimentación proviene del panel solar de 1 W y el cargador Seeed LiPo Rider Pro, que incluye un convertidor boost a 5 V para mantener al Arduino en su rango de tensión operativo.
El transceptor LoRa Reyax RYLR998 maneja la transmisión inalámbrica. Como el módulo acepta lógica de 3,6 V, un simple divisor de tensión protege la línea TX de 5 V del Arduino.
El siguiente diagrama muestra el cableado que conecta el módulo LoRa al Base Shield mediante un adaptador personalizado.
Importante: Al conectar el módulo LoRa, asegúrese de que el interruptor de alimentación del Base Shield esté en 3,3 V.
Tras el ensamblaje, verifique la comunicación enviando un comando AT desde el Arduino al módulo LoRa.
Carga y Prueba
Suba el sketch de Arduino proporcionado y abra el monitor serie para confirmar que las lecturas de los sensores se transmiten correctamente.
Montaje en Carcasa
Imprima los archivos STL suministrados en la sección de adjuntos. Tras verificar el ajuste, ensamble los componentes dentro de la carcasa usando cinta de doble cara o pegamento termofusible. Monte el panel solar en la parte superior y pase los cables de alimentación por el orificio previsto.
Una vez asegurados todos los componentes, el nodo está listo para despliegue en campo.
Configuración de Grandeur
Grandeur es un backend IoT que empareja dispositivos con usuarios y proporciona un dashboard en tiempo real. Siga estos pasos para configurar su proyecto:
- Cree un nuevo proyecto en grandeur.dev.
- Registre una cuenta de usuario; esta se usará para emparejar dispositivos después.
- Defina un modelo de dispositivo con el esquema JSON:
{"temp":0, "humid":0, "air":0, "noise":0} - Instancie un dispositivo, capture su ID único y token de acceso, y guárdelos para el firmware del Arduino.
- Empareje el dispositivo con el usuario mediante el dashboard o la API (ej.
pairDevice()). - Construya un dashboard en Canvas: añada cuatro widgets de gráficos —uno para temperatura, humedad, nivel de ruido y calidad del aire— y víngulos a las variables correspondientes.
Una vez autorizado Canvas, los usuarios pueden iniciar sesión para ver datos en vivo. La clave API y el token de dispositivo son necesarios en el firmware de la pasarela.
Configuración de la Pasarela IoT LoRa
El NodeMCU actúa como pasarela LoRa, recibiendo datos UART del nodo sensor y reenviándolos a Grandeur vía Wi-Fi. Aunque una Raspberry Pi podría cumplir este rol, el NodeMCU ofrece una solución compacta y de bajo coste que satisface los requisitos de ancho de banda y latencia del proyecto.
Firmware de la Pasarela
El firmware analiza la carga útil recibida del módulo LoRa (formato CSV delimitado por : y %), convierte cada valor a float y lo envía a Grandeur usando el SDK proporcionado. A continuación se muestra la lógica principal de extracción y transmisión.
void process_received_data(){
start_pos = inputString.indexOf(start_val);
end_pos = inputString.indexOf(end_val);
String data_string = inputString.substring(start_pos+1, end_pos-1);
int firstCommaIndex = data_string.indexOf(',');
int secondCommaIndex = data_string.indexOf(',', firstCommaIndex+1);
int thirdCommaIndex = data_string.indexOf(',', secondCommaIndex+1);
String temperature = data_string.substring(0, firstCommaIndex);
String humidity = data_string.substring(firstCommaIndex+1, secondCommaIndex);
String noise_level = data_string.substring(secondCommaIndex+1, thirdCommaIndex);
String air_quality = data_string.substring(thirdCommaIndex+1);
temp = temperature.toFloat();
humid = humidity.toFloat();
noise = noise_level.toFloat();
air = air_quality.toFloat();
inputString = "";
stringComplete = false;
}
Visualización en Dashboard
Código de Firmware
Nodo Sensor (Arduino)
#include "AirQuality.h" // Librería Grove air quality
#include "Arduino.h"
#include "DHT.h"
#define DHTPIN 4 // Pin digital para DHT11
#define DHTTYPE DHT11 // Modelo de sensor
DHT dht(DHTPIN, DHTTYPE);
AirQuality airqualitysensor;
int current_quality = -1;
const int sampleWindow = 50; // Ventana de muestreo 50 ms (20 Hz)
unsigned int sample;
float noise;
float temp, humid;
int airQuality;
void setup() {
Serial.begin(115200); // UART hacia módulo LoRa
airqualitysensor.init(14); // Pin analógico A0 para sensor aire
dht.begin();
}
void loop() {
noise = calculate_sound_in_db();
airQuality = calculate_air_quality();
calculate_temp_humid();
String values = String(temp) + "," + String(humid) + "," + String(noise) + "," + String(airQuality);
String cmd = "AT+SEND=0," + String(values.length()) + "," + values;
Serial.println(cmd); // Envía trama al módulo LoRa
delay(15000); // Intervalo 15 s entre envíos
}
float calculate_sound_in_db() {
unsigned long startMillis = millis();
float peakToPeak = 0;
unsigned int signalMax = 0;
unsigned int signalMin = 1024;
while (millis() - startMillis < sampleWindow) {
sample = analogRead(A1); // Pin sensor sonoridad
if (sample < 1024) {
if (sample > signalMax) signalMax = sample;
else if (sample < signalMin) signalMin = sample;
}
}
peakToPeak = signalMax - signalMin;
float db = map(peakToPeak, 0, 1000, 48, 120); // Mapeo a rango dB aproximado
return db;
}
int calculate_air_quality() {
current_quality = airqualitysensor.slope(); // Algoritmo de pendiente de la librería
return current_quality;
}
void calculate_temp_humid() {
float h = dht.readHumidity();
float t = dht.readTemperature();
if (isnan(h) || isnan(t)) return;
temp = t;
humid = h;
}
// ISR para muestreo periódico del sensor de calidad del aire (timer1 overflow)
ISR(TIMER1_OVF_vect) {
if (airqualitysensor.counter == 61) {
airqualitysensor.last_vol = airqualitysensor.first_vol;
airqualitysensor.first_vol = analogRead(A0);
airqualitysensor.counter = 0;
airqualitysensor.timer_index = 1;
PORTB = PORTB ^ 0x20; // Toggle pin 13 (LED built-in) como heartbeat
} else {
airqualitysensor.counter++;
}
}
Pasarela (NodeMCU / ESP8266)
/* Inclusión de SDK Grandeur y librerías WiFi/SoftwareSerial */
#include <Grandeur.h>
#include <ESP8266WiFi.h>
#include <SoftwareSerial.h>
SoftwareSerial LORA(D7, D8); // RX, TX hacia módulo LoRa
/* Configuración - REEMPLAZAR CON SUS CREDENCIALES */
String deviceID = "your_device_id";
String apiKey = "your_api_key";
String token = "your_device_token";
/* Credenciales WiFi */
String ssid = "your_wifi_ssid";
String password = "your_wifi_password";
/* Objetos Grandeur */
Grandeur::Project project;
Grandeur::Project::Device device;
/* Variables de estado y buffer */
int connected = 0;
uint32_t lastUpdate = 0;
String inputString = "";
boolean stringComplete = false;
char start_val = ':';
char end_val = '%';
int start_pos = 0;
int end_pos = 0;
float temp = 0, humid = 0, air = 0, noise = 0;
/* Callback de estado de conexión */
void onConnection(bool status) {
switch (status) {
case CONNECTED:
Serial.println("Dispositivo conectado a la nube.");
connected = 1;
lastUpdate = millis();
return;
case DISCONNECTED:
Serial.println("Dispositivo desconectado de la nube.");
connected = 0;
return;
}
}
/* Conexión WiFi */
void connectWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi conectado");
Serial.println(WiFi.localIP());
}
/* Callback de confirmación de actualización de datos */
void dataCallback(const char* code, Var res) {
if (code == "DEVICE-DATA-UPDATED") {
double temp_data = (double)res["update"]["temp"];
double humid_data = (double)res["update"]["humid"];
double noise_data = (double)res["update"]["noise"];
double air_data = (double)res["update"]["air"];
Serial.printf("Temp: %f Hum: %f Ruido: %f Aire: %f\n", temp_data, humid_data, noise_data, air_data);
return;
}
Serial.println("Falló actualización de datos");
}
/* Envío de lecturas a Grandeur */
void sendUpdate() {
Var data;
data["temp"] = temp;
data["humid"] = humid;
data["noise"] = noise;
data["air"] = air;
device.data().set("temp", temp, dataCallback);
device.data().set("humid", humid, dataCallback);
device.data().set("noise", noise, dataCallback);
device.data().set("air", air, dataCallback);
}
void setup() {
Serial.begin(9600); // Debug serial
LORA.begin(115200); // Comunicación con módulo LoRa
inputString.reserve(200);
connectWiFi();
project = grandeur.init(apiKey, token);
device = project.device(deviceID);
project.onConnection(onConnection);
}
void loop() {
serialEvent(); // Acumula datos desde LoRa
project.loop(WiFi.status() == WL_CONNECTED); // Mantiene conexión Grandeur
if (stringComplete) {
process_received_data(); // Parsea trama CSV
if (connected) sendUpdate(); // Envía a nube si hay conexión
}
}
/* Evento serie por software (LoRa) */
void serialEvent() {
while (LORA.available()) {
char inChar = (char)LORA.read();
if (inChar == '\n') {
stringComplete = true;
} else {
inputString += inChar;
}
}
}
Consideraciones Adicionales
- Escalabilidad: Puede añadir múltiples nodos sensor (cada uno con ID único) transmitiendo a la misma pasarela.
- Gestión de energía: El nodo sensor puede entrar en deep sleep entre lecturas para extender autonomía; el LiPo Rider Pro gestiona la carga solar automáticamente.
- Seguridad: Grandeur usa autenticación basada en tokens y TLS; asegúrese de no exponer claves en repositorios públicos.
- Calibración: El sensor de calidad del aire Grove requiere un periodo de "burn-in" (24-48 h) y calibración en aire limpio para lecturas fiables.
- Alternativas de backend: Puede sustituir Grandeur por MQTT + InfluxDB/Grafana, ThingsBoard, Blynk, etc., adaptando el firmware de la pasarela.
Proceso de manufactura
- Medición de la Calidad del Aire con OpenSensors: Guía para Arduino y Python
- Monitor DIY de calidad del aire: detector EPA-compliant económico
- Sensor de Calidad del Aire Solar con Helium: Guía de Diseño y Despliegue
- Planta Inteligente IoT: Construye un Invernadero Conectado con Arduino y Azure
- Bosch Sensortec presenta sensor de calidad de aire BME688 con IA para reducir riesgo de transmisión viral
- Renesas eleva su plataforma sensor calidad aire interior con IA embebida para detección de olores mejorada
- Pequeños sensores PM2.5 y VOC ofrecen monitoreo personal de calidad del aire en tiempo real
- Sensor avanzado de NO₂ basado en carbono mejora monitorización de calidad del aire interior
- Los sensores IoT revolucionan la monitorización de la contaminación atmosférica y la salud pública
- Monitorización de Calidad del Aire Ambiental con IoT: Seguridad, Productividad y Sostenibilidad
- Monitoreo de Calidad del Aire Industrial IoT: Aumentando Productividad y Reduciendo Costos