Monitorear la estación con Internet de las Cosas
Recorrido del nivel
Esa barra de arriba te dice en qué paso estás. Tocala para ver el índice completo y saltar a cualquier paso.
Introducción a Internet de las Cosas (IoT)
Internet de las Cosas (en inglés Internet of Things, abreviado IoTIoT (Internet de las Cosas)Interconexión digital de objetos cotidianos con internet. Permite, por ejemplo, enviar los datos de un sensor a una central y visualizarlos desde un dispositivo móvil. Requiere un dispositivo capaz de conectarse a internet y un servidor que reciba y aloje los datos.) es un concepto que refiere a la interconexión digital de objetos cotidianos con internet. Por ejemplo, monitorear la temperatura de un ambiente enviando los datos de un sensor a una central donde se recopile la información, para visualizarla desde un dispositivo móvil: la temperatura de un laboratorio, de un invernadero o de una sala de un hospital.
Para poder incorporar IoT a nuestro proyecto es necesario:
- Un dispositivo capaz de conectarse a internet.
- Un servidor que reciba y aloje los datos.
Existen diversas formas de registrar y almacenar los datos de la estación. En este caso, se detallará cómo hacerlo con el WiFi integrado de la placa ESP32ESP32Placa con un microcontrolador de Espressif que trae WiFi (y Bluetooth) integrados. Se programa desde el Arduino IDE igual que un Arduino, con pines llamados GPIO. En este proyecto reemplaza al conjunto Arduino UNO + módulo OBLOQ para el nivel IoT. y con los servidores de Adafruit IOAdafruit IOPlataforma online de uso gratuito, orientada al uso educativo, que centraliza y gestiona la información que reportan los dispositivos IoT. Permite crear feeds y paneles de control para monitorear los datos desde cualquier dispositivo..
El material original de 2019 resolvía este punto agregando al Arduino UNO un módulo externo llamado OBLOQ, un adaptador WiFi-serie de DFRobot hoy discontinuado (igual que su biblioteca ObloqAdafruit). Como el UNO clásico no tiene conectividad propia, en esta reedición la práctica de IoT se realiza con la variante ESP32 del proyecto: podés seguir este nivel de manera conceptual con tu UNO (el paso 3, sobre código no bloqueante, funciona perfectamente en él) y cambiar al ESP32 con el selector de plataforma para la parte práctica de conexión, que es idéntica en lo didáctico y más simple en el circuito.
El ESP32 cumple los dos roles a la vez: es el microcontrolador que ya está midiendo la temperatura, la humedad, la lluvia y la presión, y, además, trae radio WiFi integrada. No hace falta ningún módulo adicional ni cableado extra: la conexión a internet se resuelve por completo desde el código.
Crear un panel de control en Adafruit IO
Primero se explicará cómo crear un panel de control en Adafruit IOAdafruit IOPlataforma online de uso gratuito, orientada al uso educativo, que centraliza y gestiona la información que reportan los dispositivos IoT. Permite crear feeds y paneles de control para monitorear los datos desde cualquier dispositivo., una plataforma online con posibilidad de uso gratuito, orientada al uso educativo, que centraliza la información que reportan los dispositivos. Empezamos creando una cuenta de usuario en io.adafruit.com.
Una vez que ingresamos, el trabajo tiene dos partes: crear los feeds donde se publicarán los datos y armar el panel que los muestre.
Un feedFeedFuente de datos en la que se puede publicar y a la que uno puede suscribirse. Permite enviar datos para que se almacenen en el tiempo, y también leerlos recibiendo las actualizaciones de quienes publican allí. es una fuente de datos en la que uno puede publicar, así como también suscribirse para recibir sus datos. Desde la sección Feeds creamos cuatro, con estos nombres exactos (sin tildes, porque forman parte de la dirección con la que se publica):
temperatura: recibirá el valor de temperatura en grados Celsius.humedad: recibirá el valor de humedad relativa del aire, en porcentaje.presion: recibirá el valor de presión atmosférica, en hectopascales (hPa).lluvia: recibirá el estado de la lluvia como un0(no llueve) o un1(llueve).
Los tres primeros son los del original; el feed lluvia es una extensión de esta reedición: la
estación ya mide la lluvia, así que tiene sentido publicarla también para completar el monitoreo
remoto (queda registrado en la ficha del proyecto).
Luego, desde la sección Dashboards, creamos un nuevo panel con el nombre y la descripción que
quieras. Dentro del panel agregamos, tal como propone el original, bloques del tipo Line Chart
(gráfico de línea) para temperatura, humedad y presion: nos permiten ver el historial de
esas variables a lo largo del tiempo. Para lluvia, que es un valor de 0 o 1, conviene un bloque más
simple, como un indicador o un gráfico que muestre el estado actual. Cada bloque permite elegir
un título y sus colores, y luego se puede ajustar su tamaño y ubicación. Al finalizar, el panel
replicará a distancia la evolución de las condiciones del clima que registra la estación.
Por último, necesitamos las credenciales que le permitirán a nuestro dispositivo publicar en la cuenta: en Adafruit IO, tocá el ícono de la llave (API Key). Vas a ver dos datos, el nombre de usuario (Username) y la clave (Active Key): anotalos, porque los vamos a copiar dentro del código en el paso 4. La clave es secreta: cualquiera que la tenga puede publicar en tus feeds.
Programar sin código bloqueante
Antes de usar IoT debemos hacer una aclaración sobre la función delay() que venimos usando
(nuestro programa espera unos segundos entre medición y medición). Esta función brinda un tiempo de
espera al sistema y suele usarse bastante en las primeras aproximaciones a la programación, porque su
comportamiento es fácil de comprender y no requiere más que una línea de código.
Sin embargo, delay() genera un código bloqueanteCódigo bloqueanteCódigo que detiene todo el procesamiento hasta que se cumple una condición, como delay(), que deja al programa 'colgado' durante el tiempo indicado. En IoT es conflictivo: impide atender tareas simultáneas como la publicación de datos y la conexión a internet.: cuando el
programa entra en esa función, se detiene todo el procesamiento hasta que se cumple el tiempo
indicado. En otras palabras, el programa queda “colgado” durante ese período.
Al utilizar IoT, el código bloqueante es un problema: mientras el procesamiento está detenido, el sistema no puede realizar otras operaciones que funcionan en simultáneo, como las tareas de publicación y el mantenimiento constante de la conexión a internet.
Para evitarlo, usamos una alternativa no bloqueante, la función millis(). Esta función devuelve
un conteo de tiempo (en milisegundos) desde que se inició el sistema: funciona como un cronómetro que,
cada vez que se lo consulta, informa en cuánto va. Con eso podemos preguntarle al sistema cuánto
tiempo pasó desde la última medición, sin detener todo lo demás.
La receta tiene tres ingredientes, que vas a reconocer en el código: una variable millisAnterior,
que guarda cuándo fue la última medición (arranca en cero); la consulta del valor actual con
millis() en cada vuelta de loop(); y la comparación entre ambos contra el INTERVALO_MEDICION_MS.
Si ya pasó el intervalo, el programa mide y actualiza el display, y anota el nuevo valor en
millisAnterior, para que la cuenta arranque de cero otra vez. Si todavía no pasó el tiempo, no
espera: sigue de largo, con el procesador libre para otras tareas.
Ver el código completo · estacion-no-bloqueante.ino
// Estación meteorológica completa SIN delay(): DHT11 (temperatura y humedad),
// sensor de lluvia y BMP180 (presión), mostrando todo en el LCD 16x2, pero
// con millis() en lugar de delay(). Cada vuelta de loop() pregunta si ya pasó
// el intervalo de medición y, si no, deja el procesador libre.
//
// Este programa es la base directa de estacion-iot.ino: sin código bloqueante,
// el ESP32 puede sumar WiFi y MQTT sin desatender los sensores.
//
// CONVENCIÓN DEL SENSOR DE LLUVIA (activo-bajo): la salida digital D0 vale
// BAJO cuando DETECTA agua y ALTO cuando está SECO (ver detalle en el sketch
// del nivel inicial). Por eso comparamos contra LOW.
//
// Plataforma: Arduino UNO.
// Conexión (la del esquema original): dato del DHT11 → pin 2; lluvia D0 → pin
// 3; display LCD Keypad Shield paralelo por 8, 9, 4, 5, 6, 7; BMP180 por I2C:
// SDA → A4, SCL → A5.
//
// Nota del original: en el LCD no usar tildes ni la letra Ñ.
//
// Librerías requeridas (Library Manager): "DHT sensor library" (Adafruit) +
// "Adafruit Unified Sensor", y "Adafruit BMP085 Library" (BMP085/BMP180).
//
// Basado en: Fragmentos de código del Nivel Avanzado (patrón millis, sin la
// parte IoT) — Estación Meteorológica Automatizada, Saberes Digitales
// (INET/EDUCAR 2019)
// Modernización: constantes nombradas; millisAnterior como unsigned long (el
// long del original desborda la cuenta); DHT de Adafruit con dht.begin().
#include "DHT.h"
#include <Wire.h>
#include <Adafruit_BMP085.h>
#include <LiquidCrystal.h>
// Pin de datos del sensor y tipo de sensor (DHT11).
const int PIN_DHT = 2;
#define DHTTYPE DHT11
// Salida digital D0 del sensor de lluvia.
const int PIN_LLUVIA = 3;
// Nivel de la salida del sensor cuando HAY lluvia (activo-bajo).
const int LLUVIA_DETECTADA_NIVEL = LOW;
// El DHT11 no admite más de una lectura por segundo; medimos cada 2 s.
const unsigned long INTERVALO_MEDICION_MS = 2000;
// Objetos de sensores y display.
DHT dht(PIN_DHT, DHTTYPE);
Adafruit_BMP085 bmp;
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
// Momento (ms desde el arranque) de la última medición.
unsigned long millisAnterior = 0;
void setup() {
pinMode(PIN_LLUVIA, INPUT);
dht.begin();
bmp.begin();
lcd.begin(16, 2);
lcd.clear();
}
void loop() {
unsigned long millisActual = millis();
// ¿Ya pasó el intervalo desde la última medición? Si no, no hacemos nada
// y loop() vuelve a empezar: eso es NO bloquear.
if (millisActual - millisAnterior >= INTERVALO_MEDICION_MS) {
millisAnterior = millisActual;
float temperatura = dht.readTemperature(); // grados Celsius
float humedad = dht.readHumidity(); // humedad relativa en %
float presion = bmp.readPressure() / 100.0; // Pa → hPa
// Fila 0: temperatura (col 0) y humedad (col 8). Sin tildes ni Ñ.
lcd.setCursor(0, 0);
lcd.print("T:");
lcd.print(temperatura);
lcd.setCursor(8, 0);
lcd.print("H:");
lcd.print(humedad);
// Fila 1: estado de la lluvia (col 0) y presión (col 10).
lcd.setCursor(0, 1);
if (digitalRead(PIN_LLUVIA) == LLUVIA_DETECTADA_NIVEL) {
lcd.print("Lluvia:SI");
} else {
lcd.print("Lluvia:NO");
}
lcd.setCursor(10, 1);
lcd.print("P:");
lcd.print(presion);
}
// En este punto el procesador queda libre para realizar otras tareas.
} // Estación meteorológica completa SIN delay(): DHT11 (temperatura y humedad),
// sensor de lluvia y BMP180 (presión), mostrando todo en el LCD 16x2 (I2C),
// pero con millis() en lugar de delay(). Cada vuelta de loop() pregunta si ya
// pasó el intervalo de medición y, si no, deja el procesador libre.
//
// Este programa es la base directa de estacion-iot.ino: sin código bloqueante,
// el ESP32 puede sumar WiFi y MQTT sin desatender los sensores.
//
// BUS I2C COMPARTIDO: el LCD (0x27) y el BMP180 (0x77) conviven en el mismo
// bus I2C (SDA GPIO 21, SCL GPIO 22): distintas direcciones, sin conflicto.
//
// CONVENCIÓN DEL SENSOR DE LLUVIA (activo-bajo): la salida D0 vale BAJO cuando
// DETECTA agua y ALTO cuando está SECO (ver detalle en el nivel inicial).
//
// Plataforma: ESP32 (DevKit v1).
// Conexión: DHT11 → GPIO 4; lluvia D0 → GPIO 14; LCD y BMP180 al bus I2C
// (SDA GPIO 21, SCL GPIO 22).
//
// ADAPTACIÓN DE PLATAFORMA (ver README): LCD 1602 I2C (LiquidCrystal_I2C,
// 0x27; probá 0x3F si no muestra nada) en lugar del Keypad Shield paralelo
// del original.
// Nota eléctrica: el ESP32 trabaja a 3,3 V.
//
// Nota del original: en el LCD no usar tildes ni la letra Ñ.
//
// Librerías requeridas (Library Manager): "DHT sensor library" (Adafruit) +
// "Adafruit Unified Sensor", "Adafruit BMP085 Library" y "LiquidCrystal_I2C".
//
// Basado en: Fragmentos de código del Nivel Avanzado (patrón millis, sin la
// parte IoT) — Estación Meteorológica Automatizada, Saberes Digitales
// (INET/EDUCAR 2019)
// Modernización: constantes nombradas; millisAnterior como unsigned long;
// portado a ESP32; DHT de Adafruit con dht.begin(); LCD adaptado a I2C.
#include "DHT.h"
#include <Wire.h>
#include <Adafruit_BMP085.h>
#include <LiquidCrystal_I2C.h>
// Pin de datos del sensor y tipo de sensor (DHT11).
const int PIN_DHT = 4;
#define DHTTYPE DHT11
// Salida digital D0 del sensor de lluvia.
const int PIN_LLUVIA = 14;
// Nivel de la salida del sensor cuando HAY lluvia (activo-bajo).
const int LLUVIA_DETECTADA_NIVEL = LOW;
// Dirección I2C y tamaño del display (16 columnas x 2 filas).
const uint8_t LCD_DIRECCION_I2C = 0x27;
const uint8_t LCD_COLUMNAS = 16;
const uint8_t LCD_FILAS = 2;
// El DHT11 no admite más de una lectura por segundo; medimos cada 2 s.
const unsigned long INTERVALO_MEDICION_MS = 2000;
// Objetos de sensores y display por I2C.
DHT dht(PIN_DHT, DHTTYPE);
Adafruit_BMP085 bmp;
LiquidCrystal_I2C lcd(LCD_DIRECCION_I2C, LCD_COLUMNAS, LCD_FILAS);
// Momento (ms desde el arranque) de la última medición.
unsigned long millisAnterior = 0;
void setup() {
pinMode(PIN_LLUVIA, INPUT);
dht.begin();
bmp.begin();
lcd.init();
lcd.backlight();
lcd.clear();
}
void loop() {
unsigned long millisActual = millis();
// ¿Ya pasó el intervalo desde la última medición? Si no, no hacemos nada
// y loop() vuelve a empezar: eso es NO bloquear.
if (millisActual - millisAnterior >= INTERVALO_MEDICION_MS) {
millisAnterior = millisActual;
float temperatura = dht.readTemperature(); // grados Celsius
float humedad = dht.readHumidity(); // humedad relativa en %
float presion = bmp.readPressure() / 100.0; // Pa → hPa
// Fila 0: temperatura (col 0) y humedad (col 8). Sin tildes ni Ñ.
lcd.setCursor(0, 0);
lcd.print("T:");
lcd.print(temperatura);
lcd.setCursor(8, 0);
lcd.print("H:");
lcd.print(humedad);
// Fila 1: estado de la lluvia (col 0) y presión (col 10).
lcd.setCursor(0, 1);
if (digitalRead(PIN_LLUVIA) == LLUVIA_DETECTADA_NIVEL) {
lcd.print("Lluvia:SI");
} else {
lcd.print("Lluvia:NO");
}
lcd.setCursor(10, 1);
lcd.print("P:");
lcd.print(presion);
}
// En este punto el procesador queda libre para realizar otras tareas
// (en estacion-iot.ino, acá se atiende la conexión WiFi/MQTT).
} Teniendo un programa con código no bloqueante (es decir, sin delay()) estamos en condiciones de
incorporar IoT.
Verificá
Cargá el programa: el sistema tiene que comportarse igual que en el nivel intermedio —el display
muestra temperatura, humedad, lluvia y presión, actualizándose cada 2 segundos—. Que por fuera se vea
idéntico y por dentro ya no haya delay() es, justamente, el punto de este paso.
Programar IoT
Con el panel listo y el código ya libre de bloqueos, falta el último eslabón: que el sistema publique sus mediciones en Adafruit IO.
Esta parte práctica corresponde a la variante ESP32: el Arduino UNO clásico no tiene WiFi (el original lo resolvía con el módulo OBLOQ, hoy discontinuado). Cambiá la plataforma a ESP32 con el selector para ver el código y las instrucciones de conexión. Todo lo conceptual —qué se publica en cada feed, el porqué del código no bloqueante— ya lo tenés resuelto con tu UNO en los pasos anteriores.
El programa final integra tres cosas que ya conocés: el control no bloqueante del paso 3, la conexión WiFi nativa del ESP32 y la publicación en Adafruit IO (mediante el protocolo MQTT, usando la Adafruit MQTT Library, que se instala desde el gestor de bibliotecas del Arduino IDE; reemplaza a la biblioteca ObloqAdafruit del original, discontinuada junto con su módulo).
Antes de subirlo, tenés que completar cuatro datos al comienzo del código: el nombre (SSID) y la contraseña de tu red WiFi, y el usuario y la clave que copiaste de Adafruit IO en el paso 2.
La publicación se engancha en el tiempo que el paso 3 le dejó libre al procesador: en cada vuelta de
loop(), además de medir y actualizar el display cuando toca, el programa mantiene viva la conexión
con el servidor y publica los cambios en los cuatro feeds (temperatura, humedad, presion y
lluvia). Para no gastar el cupo de la cuenta gratuita de Adafruit IO (30 publicaciones por minuto),
un valor se re-publica solo si cambió lo suficiente respecto del último enviado: al menos 0,5 °C la
temperatura, 1 % la humedad y 1 hPa la presión; la lluvia se publica cuando cambia de estado. Por
esto es importante que el código no sea bloqueante: si el programa quedara colgado en un delay(),
la conexión no podría atenderse y las publicaciones se perderían. Bonus del mismo diseño: si internet
se cae, el display sigue funcionando, y al volver la conexión el panel se pone al día solo.
Ver el código completo · estacion-iot.ino
Nota: Este código está disponible para ESP32. Cambiá la plataforma con el selector del encabezado para verlo y practicarlo.
// Estación meteorológica con monitoreo IoT vía Adafruit IO (MQTT).
// La lógica no bloqueante de estacion-no-bloqueante.ino sigue midiendo con el
// DHT11, el sensor de lluvia y el BMP180, y mostrando en el LCD (I2C); además
// publica cuatro feeds para verlos desde el Panel de Control de Adafruit:
//
// "temperatura": grados Celsius.
// "humedad": humedad relativa en %.
// "presion": presión atmosférica en hPa.
// "lluvia": 0 = seco, 1 = lloviendo.
//
// El original publicaba solo temperatura, humedad y presión. Acá se agrega el
// feed "lluvia" (extensión): el estado de lluvia ya se muestra en el LCD y es
// una variable meteorológica que también vale la pena monitorear a distancia.
//
// BUS I2C COMPARTIDO: el LCD (0x27) y el BMP180 (0x77) conviven en el mismo
// bus I2C (SDA GPIO 21, SCL GPIO 22): distintas direcciones, sin conflicto.
//
// CONVENCIÓN DEL SENSOR DE LLUVIA (activo-bajo): la salida D0 vale BAJO cuando
// DETECTA agua y ALTO cuando está SECO (ver detalle en el nivel inicial).
//
// Plataforma: ESP32 (DevKit v1) — usa su WiFi integrado.
// Conexión: DHT11 → GPIO 4; lluvia D0 → GPIO 14; LCD y BMP180 al bus I2C
// (SDA GPIO 21, SCL GPIO 22).
//
// ADAPTACIÓN DE PLATAFORMA (ver README): LCD 1602 I2C (0x27; probá 0x3F si no
// muestra nada) en lugar del Keypad Shield paralelo del original.
// Nota eléctrica: el ESP32 trabaja a 3,3 V.
//
// Nota del original: en el LCD no usar tildes ni la letra Ñ.
//
// Librerías requeridas (Library Manager): "DHT sensor library" (Adafruit) +
// "Adafruit Unified Sensor", "Adafruit BMP085 Library", "LiquidCrystal_I2C" y
// "Adafruit MQTT Library".
//
// Basado en: Fragmento de código del Nivel Avanzado (Paso 4) — Estación
// Meteorológica Automatizada, Saberes Digitales (INET/EDUCAR 2019)
// Modernización: (1) el módulo OBLOQ y la librería ObloqAdafruit quedaron
// discontinuados; se reemplazan por el WiFi nativo del ESP32 (WiFi.h) y MQTT
// contra io.adafruit.com. (2) El original publicaba en cada medición (cada
// 10 s); acá se publica solo cuando un valor cambia de forma apreciable
// (temperatura ≥ 0,5 °C; humedad ≥ 1 %; presión ≥ 1 hPa; lluvia ante cualquier
// cambio de estado), para no gastar el cupo gratuito de Adafruit IO (30
// publicaciones/minuto). (3) Se ignora una lectura fallida del DHT11 (isnan).
// (4) LCD adaptado a I2C.
#include <WiFi.h>
#include <Wire.h>
#include <Adafruit_BMP085.h>
#include <LiquidCrystal_I2C.h>
#include "DHT.h"
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"
// ---------------------------------------------------------------------------
// Credenciales: reemplazar los cuatro valores por los propios.
// El usuario y la key de Adafruit se copian desde io.adafruit.com
// haciendo clic en el ícono de la "llave" (igual que en la guía original).
// ---------------------------------------------------------------------------
#define WIFI_SSID "TU_SSID"
#define WIFI_PASSWORD "TU_PASSWORD"
#define IO_USERNAME "TU_USUARIO_ADAFRUIT"
#define IO_KEY "TU_KEY_ADAFRUIT"
// Servidor MQTT de Adafruit IO.
const char SERVIDOR_MQTT[] = "io.adafruit.com";
const int PUERTO_MQTT = 1883;
// Pin de datos del sensor y tipo de sensor (DHT11).
const int PIN_DHT = 4;
#define DHTTYPE DHT11
// Salida digital D0 del sensor de lluvia.
const int PIN_LLUVIA = 14;
// Nivel de la salida del sensor cuando HAY lluvia (activo-bajo).
const int LLUVIA_DETECTADA_NIVEL = LOW;
// Dirección I2C y tamaño del display (16 columnas x 2 filas).
const uint8_t LCD_DIRECCION_I2C = 0x27;
const uint8_t LCD_COLUMNAS = 16;
const uint8_t LCD_FILAS = 2;
// El DHT11 no admite más de una lectura por segundo; medimos cada 2 s.
const unsigned long INTERVALO_MEDICION_MS = 2000;
// Un valor se re-publica solo si cambió al menos esto respecto del último
// publicado: evita gastar el cupo gratuito de Adafruit IO con repeticiones.
const float UMBRAL_PUBLICACION_TEMP_C = 0.5;
const float UMBRAL_PUBLICACION_HUM = 1.0;
const float UMBRAL_PUBLICACION_PRESION = 1.0; // hPa
// Si se cae la conexión MQTT, reintentamos cada tanto SIN bloquear.
const unsigned long INTERVALO_REINTENTO_MQTT_MS = 5000;
// Objetos de sensores, display, WiFi y MQTT.
DHT dht(PIN_DHT, DHTTYPE);
Adafruit_BMP085 bmp;
LiquidCrystal_I2C lcd(LCD_DIRECCION_I2C, LCD_COLUMNAS, LCD_FILAS);
WiFiClient clienteWifi;
Adafruit_MQTT_Client mqtt(&clienteWifi, SERVIDOR_MQTT, PUERTO_MQTT,
IO_USERNAME, IO_KEY);
Adafruit_MQTT_Publish feedTemperatura(&mqtt, IO_USERNAME "/feeds/temperatura");
Adafruit_MQTT_Publish feedHumedad(&mqtt, IO_USERNAME "/feeds/humedad");
Adafruit_MQTT_Publish feedPresion(&mqtt, IO_USERNAME "/feeds/presion");
Adafruit_MQTT_Publish feedLluvia(&mqtt, IO_USERNAME "/feeds/lluvia");
// Momento (ms) de la última medición y del último intento de conexión MQTT.
unsigned long millisAnterior = 0;
unsigned long millisUltimoIntentoMqtt = 0;
// Últimos valores publicados (arrancan imposibles para forzar la 1ª publicación).
float temperaturaPublicada = -1000.0;
float humedadPublicada = -1000.0;
float presionPublicada = -1000.0;
int lluviaPublicada = -1;
bool hayMedicion = false;
// Diferencia sin signo entre dos números (para comparar contra el umbral).
float diferenciaAbsoluta(float a, float b) {
if (a > b) {
return a - b;
}
return b - a;
}
// Mantiene viva la conexión MQTT sin bloquear los sensores: si está caída,
// reintenta como mucho una vez cada 5 segundos.
void mantenerConexionMqtt() {
if (mqtt.connected()) {
return;
}
unsigned long millisActual = millis();
if (millisActual - millisUltimoIntentoMqtt < INTERVALO_REINTENTO_MQTT_MS) {
return; // todavía no toca reintentar
}
millisUltimoIntentoMqtt = millisActual;
Serial.print("Conectando a Adafruit IO... ");
int8_t resultado = mqtt.connect(); // 0 = conexión exitosa
if (resultado == 0) {
Serial.println("conectado.");
} else {
Serial.println(mqtt.connectErrorString(resultado));
mqtt.disconnect(); // deja el socket limpio para el próximo intento
}
}
// Publica cada feed SOLO si cambió lo suficiente desde la última publicación
// exitosa. Si publish() falla o no hay conexión, no se actualiza el registro:
// se reintenta en la próxima vuelta.
void publicarCambios(float temperatura, float humedad, float presion, int lluvia) {
if (!mqtt.connected() || !hayMedicion) {
return; // sin internet o sin datos reales todavía: no se publica
}
if (diferenciaAbsoluta(temperatura, temperaturaPublicada)
>= UMBRAL_PUBLICACION_TEMP_C
&& feedTemperatura.publish(temperatura)) {
temperaturaPublicada = temperatura;
}
if (diferenciaAbsoluta(humedad, humedadPublicada) >= UMBRAL_PUBLICACION_HUM
&& feedHumedad.publish(humedad)) {
humedadPublicada = humedad;
}
if (diferenciaAbsoluta(presion, presionPublicada) >= UMBRAL_PUBLICACION_PRESION
&& feedPresion.publish(presion)) {
presionPublicada = presion;
}
if (lluvia != lluviaPublicada && feedLluvia.publish((int32_t)lluvia)) {
lluviaPublicada = lluvia;
}
}
void setup() {
Serial.begin(115200);
pinMode(PIN_LLUVIA, INPUT);
dht.begin();
bmp.begin();
lcd.init();
lcd.backlight();
lcd.clear();
// Conexión WiFi. Esperar acá con delay() es aceptable porque el ciclo de
// medición todavía no arrancó; una vez dentro de loop(), nada bloquea.
Serial.print("Conectando a la red WiFi");
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("WiFi conectado, IP: ");
Serial.println(WiFi.localIP());
}
void loop() {
unsigned long millisActual = millis();
// --- Medición y display, cada 2 s (respeta el DHT11) ---
if (millisActual - millisAnterior >= INTERVALO_MEDICION_MS) {
millisAnterior = millisActual;
float temperatura = dht.readTemperature(); // grados Celsius
float humedad = dht.readHumidity(); // humedad relativa en %
float presion = bmp.readPressure() / 100.0; // Pa → hPa
// 1 si la salida del sensor está en el nivel de "agua detectada", si no 0.
int lluvia = (digitalRead(PIN_LLUVIA) == LLUVIA_DETECTADA_NIVEL) ? 1 : 0;
// Una lectura fallida del DHT11 devuelve "no numérico": la salteamos
// para no mostrar ni publicar basura (temperatura y humedad).
if (!isnan(temperatura) && !isnan(humedad)) {
hayMedicion = true;
// Fila 0: temperatura (col 0) y humedad (col 8). Sin tildes ni Ñ.
lcd.setCursor(0, 0);
lcd.print("T:");
lcd.print(temperatura);
lcd.setCursor(8, 0);
lcd.print("H:");
lcd.print(humedad);
// Fila 1: estado de la lluvia (col 0) y presión (col 10).
lcd.setCursor(0, 1);
if (lluvia == 1) {
lcd.print("Lluvia:SI");
} else {
lcd.print("Lluvia:NO");
}
lcd.setCursor(10, 1);
lcd.print("P:");
lcd.print(presion);
// Publicamos (solo lo que cambió lo suficiente).
publicarCambios(temperatura, humedad, presion, lluvia);
}
}
// --- Mantenimiento de la conexión IoT, en cada vuelta ---
mantenerConexionMqtt();
} Así queda funcionando el tablero que permite monitorear la estación a distancia. Podés ver el sistema completo —los sensores midiendo, el display y el panel reflejando cada publicación— en el simulador:
Simulación del comportamiento
La misma máquina de estados del firmware, portada a TypeScript, maneja el
comportamiento del dispositivo. No se ejecuta el binario real: la ejecución
del .hex con avr8js está en el roadmap.
Simulación lista. Presioná «Iniciar» para arrancar la secuencia.
Más variables del clima
Hoy la estación mide temperatura, humedad, lluvia y presión. Piensen en grupo: ¿qué otras variables del clima les gustaría monitorear y publicar? El cierre del proyecto sugiere una veleta (dirección del viento), un anemómetro (velocidad del viento) y un LDR (intensidad de la luz del Sol). Elijan una: ¿es un sensor digital, analógico o por bus? ¿A qué pin iría? ¿Cómo la agregarían al panel de Adafruit, como gráfico de línea o como indicador?
Verificá
Con el programa cargado y la placa conectada a tu red WiFi, abrí el panel de Adafruit IO en el celular
o la computadora: los bloques de temperatura, humedad y presion tienen que ir sumando puntos
cuando las lecturas cambian, y el de lluvia tiene que cambiar de estado al mojar la placa colectora
(recordá que se publica por cambios: con el ambiente estable, que no lleguen datos nuevos es lo
esperado). Todo con una pequeña demora de red. Si no llega nada, revisá las credenciales y el nombre
de la red.
Cierre del nivel
Al terminar este nivel, tu estación dejó de ser solo un dispositivo local: es un dispositivo IoT. El
programa funciona con millis(), sin ningún delay() que lo cuelgue; sabés explicar por qué eso es
imprescindible cuando hay que atender internet en simultáneo; y el panel de Adafruit IO muestra desde
cualquier dispositivo la temperatura, la humedad, la presión y la lluvia de tu estación. Con esto
queda completo el recorrido del proyecto. Si el grupo quiere seguir, en la ficha del proyecto están
las extensiones sugeridas: sumar una veleta, un anemómetro o un sensor de luz para medir aún más
variables del clima.
Evidencia de cierre
Al terminar este nivel, esto tiene que estar funcionando:
- El sistema funciona con código no bloqueante basado en millis(): no queda ningún delay() que detenga el programa entre mediciones, y la estación puede atender internet mientras sigue midiendo.
- Podés explicar con tus palabras por qué el código bloqueante es un problema cuando el dispositivo tiene que atender internet en simultáneo.
- El panel de Adafruit IO muestra la temperatura, la humedad, la presión y el estado de la lluvia publicados por el dispositivo (variante ESP32).