Monitorear el riego 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. Esta interconexión puede tener diversas funciones. 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 del sistema de riego. 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 del “dispositivo capaz de conectarse a internet”: es el microcontrolador que ya está midiendo la humedad y manejando la válvula y, a la vez, 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 dos, con estos nombres exactos (sin tildes, porque forman parte de la dirección con la que se publica):
humedad: recibirá la lectura del higrómetro. Recordá la convención del sensor: a mayor valor, tierra más seca.valvula: recibirá el estado de la válvula, con la convención 1 = abierta (regando) y 0 = cerrada.
Luego, desde la sección Dashboards, creamos un nuevo panel con el nombre y la descripción que quieras. Dentro del panel agregamos dos bloques, uno por feed:
- Un bloque Gauge (indicador de aguja) asociado a
humedad, para ver la lectura del sensor. Como es una lectura inversa, tené presente al leerlo que la aguja alta significa tierra seca y la aguja baja, tierra húmeda. - Un bloque Indicator (luz virtual) asociado a
valvula, con la condición “igual a 1”: encendido cuando la válvula está abierta (regando), apagado cuando está cerrada.
Cada bloque permite elegir un título y sus colores, y luego se puede ajustar su tamaño y ubicación dentro del panel. Al finalizar, el panel replicará a distancia el estado del sistema: qué tan seca está la tierra y si el riego está activo.
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 un segundo 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 el higrómetro y decide sobre
la válvula, 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.
Así queda el programa del nivel intermedio quitando el código bloqueante. Sigue midiendo cada segundo, igual que el original, pero ahora sin colgarse:
Ver el código completo · riego-no-bloqueante.ino
// Riego por humedad SIN delay(): la misma lógica del nivel intermedio
// (higrómetro que abre o cierra la electroválvula), pero no bloqueante.
// En vez de detener el programa un segundo entre mediciones, cada vuelta
// de loop() pregunta con millis() si ya pasó el intervalo; el resto del
// tiempo el procesador queda libre. Esta es la base que en ESP32 permite
// sumar IoT en riego-iot.ino sin que el riego se "cuelgue".
//
// CONVENCIÓN DEL HIGRÓMETRO: MAYOR lectura = tierra MÁS SECA (módulo
// resistivo). Por eso "seco" es el umbral alto (750) y "húmedo" el bajo
// (380), igual que en el nivel intermedio.
//
// HISTÉRESIS NATURAL DEL ORIGINAL (se preserva tal cual): dos ifs con dos
// umbrales; en la franja 380 a 750 la válvula conserva su estado, lo que
// evita el traqueteo cuando la lectura baila cerca de un borde.
//
// SEGURIDAD: agua y electricidad no se llevan bien. Conexiones eléctricas
// fuera y por encima del agua. Electroválvula de baja tensión (12 V) o
// simulación en el aula; 220 V solo docente/instalación supervisada, nunca
// sobre la protoboard (desvío pedagógico acordado).
//
// Plataforma: Arduino UNO.
// Conexión (la del esquema original): higrómetro → A0; relé de la
// electroválvula → pin 2.
//
// Basado en: Fragmentos de código 6 (patrón millis) y 7 (medición y
// control cada segundo, sin la parte IoT) — Sistema de Riego Automatizado,
// Saberes Digitales (INET/EDUCAR 2019)
// Modernización: constantes nombradas; millisAnterior como unsigned long
// (el long del original desborda la cuenta); una sola lectura del
// higrómetro por ciclo, reutilizada para imprimir y comparar.
const int PIN_HIGROMETRO = A0;
const int PIN_RELE_VALVULA = 2;
// Módulo relé activo-alto, como en el original (HIGH = válvula abierta).
// Si tu módulo es activo-bajo, invertí solo estas dos constantes.
const int VALVULA_ABIERTA = HIGH;
const int VALVULA_CERRADA = LOW;
// Umbrales del original (escala del UNO: 0 a 1023). Ajustalos a TU suelo.
const int UMBRAL_SECO = 750; // por encima: tierra seca → regar
const int UMBRAL_HUMEDO = 380; // por debajo: tierra húmeda → cortar
const long VELOCIDAD_SERIAL = 115200; // la del original
// El Fragmento de código 7 del original mide una vez por segundo.
const unsigned long INTERVALO_MEDICION_MS = 1000;
// Momento (ms desde el arranque) de la última medición.
unsigned long millisAnterior = 0;
void setup() {
pinMode(PIN_HIGROMETRO, INPUT);
pinMode(PIN_RELE_VALVULA, OUTPUT);
// Estado inicial SEGURO: arrancamos con la válvula cerrada. Sin esta línea
// el pin queda en LOW por defecto; si invertís las constantes para un módulo
// activo-bajo, ese LOW pasaría a ser "abierta" y, si al encender la lectura
// cae en la banda muerta (entre 380 y 750), la válvula regaría sin control.
// Regla de oro: dejá SIEMPRE el actuador en estado seguro antes del loop.
digitalWrite(PIN_RELE_VALVULA, VALVULA_CERRADA);
Serial.begin(VELOCIDAD_SERIAL);
}
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;
int humedad = analogRead(PIN_HIGROMETRO);
Serial.println(humedad);
// Histéresis del original: dos ifs independientes, dos umbrales.
if (humedad > UMBRAL_SECO) {
// Tierra demasiado seca: abrir la válvula para regar.
digitalWrite(PIN_RELE_VALVULA, VALVULA_ABIERTA);
}
if (humedad < UMBRAL_HUMEDO) {
// Tierra suficientemente húmeda: cerrar la válvula.
digitalWrite(PIN_RELE_VALVULA, VALVULA_CERRADA);
}
// Entre 380 y 750 la válvula queda como estaba.
}
// En este punto el procesador queda libre para realizar otras tareas.
} // Riego por humedad SIN delay(): la misma lógica del nivel intermedio
// (higrómetro que abre o cierra la electroválvula), pero no bloqueante.
// En vez de detener el programa un segundo entre mediciones, cada vuelta
// de loop() pregunta con millis() si ya pasó el intervalo; el resto del
// tiempo el procesador queda libre. Este programa es la base directa de
// riego-iot.ino: sin código bloqueante, el ESP32 puede mantener el WiFi y
// publicar por MQTT sin desatender el riego.
//
// CONVENCIÓN DEL HIGRÓMETRO: MAYOR lectura = tierra MÁS SECA (módulo
// resistivo). Por eso "seco" es el umbral alto y "húmedo" el bajo.
//
// HISTÉRESIS NATURAL DEL ORIGINAL (se preserva tal cual): dos ifs con dos
// umbrales; en la franja intermedia la válvula conserva su estado, lo que
// evita el traqueteo cuando la lectura baila cerca de un borde.
//
// DIFERENCIA DE ADC (didáctica): el ESP32 mide 0 a 4095 (12 bits, 3,3 V),
// no 0 a 1023 (10 bits, 5 V) del UNO; los umbrales del original se
// reescalan en la misma proporción (750→3000, 380→1520).
//
// SEGURIDAD: agua y electricidad no se llevan bien. Conexiones eléctricas
// fuera y por encima del agua. Electroválvula de baja tensión (12 V) o
// simulación en el aula; 220 V solo docente/instalación supervisada, nunca
// sobre la protoboard (desvío pedagógico acordado).
//
// Plataforma: ESP32 (DevKit v1).
// Conexión: higrómetro → GPIO 34 (ADC1, solo entrada); relé de la
// electroválvula → GPIO 16. Nota eléctrica: el ESP32 trabaja a 3,3 V;
// alimentá el higrómetro con 3,3 V y nunca inyectes 5 V en un GPIO.
//
// Basado en: Fragmentos de código 6 (patrón millis) y 7 (medición y
// control cada segundo, sin la parte IoT) — Sistema de Riego Automatizado,
// Saberes Digitales (INET/EDUCAR 2019)
// Modernización: constantes nombradas; millisAnterior como unsigned long
// (el long del original desborda la cuenta); portado a ESP32 con umbrales
// reescalados; una sola lectura del higrómetro por ciclo, reutilizada para
// imprimir y comparar.
const int PIN_HIGROMETRO = 34;
const int PIN_RELE_VALVULA = 16;
// Módulo relé activo-alto, como en el original (HIGH = válvula abierta).
// Si tu módulo es activo-bajo, invertí solo estas dos constantes.
const int VALVULA_ABIERTA = HIGH;
const int VALVULA_CERRADA = LOW;
// Umbrales del original reescalados al ADC de 12 bits del ESP32.
const int UMBRAL_SECO = 3000; // por encima: tierra seca → regar (750 en UNO)
const int UMBRAL_HUMEDO = 1520; // por debajo: tierra húmeda → cortar (380 en UNO)
const long VELOCIDAD_SERIAL = 115200; // la del original
// El Fragmento de código 7 del original mide una vez por segundo.
const unsigned long INTERVALO_MEDICION_MS = 1000;
// Momento (ms desde el arranque) de la última medición.
unsigned long millisAnterior = 0;
void setup() {
pinMode(PIN_HIGROMETRO, INPUT);
pinMode(PIN_RELE_VALVULA, OUTPUT);
// Estado inicial SEGURO: arrancamos con la válvula cerrada. Sin esta línea
// el pin queda en LOW por defecto; si invertís las constantes para un módulo
// activo-bajo, ese LOW pasaría a ser "abierta" y, si al encender la lectura
// cae en la banda muerta (entre 1520 y 3000), la válvula regaría sin control.
// Regla de oro: dejá SIEMPRE el actuador en estado seguro antes del loop.
digitalWrite(PIN_RELE_VALVULA, VALVULA_CERRADA);
Serial.begin(VELOCIDAD_SERIAL);
}
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;
int humedad = analogRead(PIN_HIGROMETRO);
Serial.println(humedad);
// Histéresis del original: dos ifs independientes, dos umbrales.
if (humedad > UMBRAL_SECO) {
// Tierra demasiado seca: abrir la válvula para regar.
digitalWrite(PIN_RELE_VALVULA, VALVULA_ABIERTA);
}
if (humedad < UMBRAL_HUMEDO) {
// Tierra suficientemente húmeda: cerrar la válvula.
digitalWrite(PIN_RELE_VALVULA, VALVULA_CERRADA);
}
// Entre el umbral húmedo y el seco la válvula queda como estaba.
}
// En este punto el procesador queda libre para realizar otras tareas
// (en riego-iot.ino, acá se atiende la conexión WiFi/MQTT).
} Teniendo un programa de riego 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 —la
válvula abre cuando la lectura supera UMBRAL_SECO y cierra cuando cae por debajo de
UMBRAL_HUMEDO—, y el Monitor Serie tiene que seguir mostrando la lectura cada segundo. Que por
fuera se vea idéntico y por dentro sea tan distinto 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 la humedad y el estado de la válvula 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, la convención de valores, 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 decidir cuando toca, el programa mantiene viva la conexión
con el servidor y publica los cambios: el estado (1 o 0) en valvula cada vez que la
válvula conmuta, y la lectura en humedad cuando difiere de la última publicada. El original
publicaba la humedad y el estado en cada medición, cada segundo; publicar solo los cambios
muestra lo mismo en el panel sin agotar el cupo de la cuenta gratuita de Adafruit IO (30
publicaciones por minuto; el desvío está documentado en la ficha). Por esto es importante que
nuestro 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 riego sigue funcionando, y al volver la conexión el panel se pone al día solo.
Ver el código completo · riego-iot.ino
Nota: Este código está disponible para ESP32. Cambiá la plataforma con el selector del encabezado para verlo y practicarlo.
// Riego por humedad con monitoreo IoT vía Adafruit IO (MQTT).
// La lógica no bloqueante de riego-no-bloqueante.ino sigue controlando la
// electroválvula según el higrómetro; además se publican dos feeds para
// verlos desde el Panel de Control de Adafruit (como en la guía):
//
// "humedad": lectura cruda del higrómetro (número más alto = más seco).
// "valvula": 0 = cerrada, 1 = abierta.
//
// CONVENCIÓN DEL HIGRÓMETRO: MAYOR lectura = tierra MÁS SECA (módulo
// resistivo). Por eso "seco" es el umbral alto y "húmedo" el bajo.
//
// HISTÉRESIS NATURAL DEL ORIGINAL (se preserva tal cual): dos ifs con dos
// umbrales; en la franja intermedia la válvula conserva su estado.
//
// DIFERENCIA DE ADC (didáctica): el ESP32 mide 0 a 4095 (12 bits, 3,3 V),
// no 0 a 1023 del UNO; los umbrales se reescalan (750→3000, 380→1520).
//
// SEGURIDAD: agua y electricidad no se llevan bien. Conexiones eléctricas
// fuera y por encima del agua. Electroválvula de baja tensión (12 V) o
// simulación en el aula; 220 V solo docente/instalación supervisada, nunca
// sobre la protoboard (desvío pedagógico acordado).
//
// Plataforma: ESP32 (DevKit v1) — usa su WiFi integrado.
// Conexión: higrómetro → GPIO 34 (ADC1, solo entrada); relé de la
// electroválvula → GPIO 16. Nota eléctrica: el ESP32 trabaja a 3,3 V;
// alimentá el higrómetro con 3,3 V y nunca inyectes 5 V en un GPIO.
//
// Librería requerida (Library Manager del IDE de Arduino):
// "Adafruit MQTT Library" (de Adafruit) — la misma elección que en los
// demás proyectos: cada feed es un objeto y publish() acepta números
// directamente, igual que el olq.publish() del original.
//
// Basado en: Fragmento de código 7 — Sistema de Riego Automatizado,
// 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 "humedad" en CADA
// medición (cada 1 s = 60 publicaciones por minuto, el doble del límite
// gratuito de Adafruit IO de 30/min); acá se publica solo cuando algo
// cambia: la válvula al conmutar y la humedad cuando difiere 50 o más del
// último valor publicado. (3) Umbrales reescalados al ADC de 12 bits.
#include <WiFi.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;
const int PIN_HIGROMETRO = 34;
const int PIN_RELE_VALVULA = 16;
// Módulo relé activo-alto, como en el original (HIGH = válvula abierta).
// Si tu módulo es activo-bajo, invertí solo estas dos constantes.
const int VALVULA_ABIERTA = HIGH;
const int VALVULA_CERRADA = LOW;
// Umbrales del original reescalados al ADC de 12 bits del ESP32.
const int UMBRAL_SECO = 3000; // por encima: tierra seca → regar (750 en UNO)
const int UMBRAL_HUMEDO = 1520; // por debajo: tierra húmeda → cortar (380 en UNO)
// La humedad se re-publica solo si difiere en 50 o más (sobre 0-4095) del
// último valor publicado: los saltitos de ruido entre medición y medición
// no le aportan nada a la central y consumen el cupo gratuito de Adafruit.
const int UMBRAL_PUBLICACION = 50;
const long VELOCIDAD_SERIAL = 115200;
// El original mide una vez por segundo.
const unsigned long INTERVALO_MEDICION_MS = 1000;
// Si se cae la conexión MQTT, reintentamos cada tanto SIN bloquear:
// el riego debe seguir funcionando aunque no haya internet.
const unsigned long INTERVALO_REINTENTO_MQTT_MS = 5000;
// Cliente MQTT sobre el WiFi del ESP32, y un objeto por feed.
WiFiClient clienteWifi;
Adafruit_MQTT_Client mqtt(&clienteWifi, SERVIDOR_MQTT, PUERTO_MQTT,
IO_USERNAME, IO_KEY);
Adafruit_MQTT_Publish feedHumedad(&mqtt, IO_USERNAME "/feeds/humedad");
Adafruit_MQTT_Publish feedValvula(&mqtt, IO_USERNAME "/feeds/valvula");
// Momento (ms desde el arranque) de la última medición y del último
// intento de conexión MQTT.
unsigned long millisAnterior = 0;
unsigned long millisUltimoIntentoMqtt = 0;
// Todavía no se midió: no hay que publicar humedad inventada si la
// conexión MQTT llega antes que la primera medición.
bool hayMedicion = false;
// Estado actual (lo que muestran la válvula y el higrómetro)...
int estadoValvula = 0; // 0 = cerrada, 1 = abierta
int humedadActual = 0; // última lectura cruda del higrómetro
// ...y lo último que llegó a publicarse. Separarlos permite publicar solo
// los CAMBIOS y, si la conexión estaba caída, ponerse al día al volver.
int estadoValvulaPublicado = -1; // -1 = todavía no se publicó nada
int humedadPublicada = -1000; // imposible: fuerza la 1ª publicación
// Diferencia sin signo entre dos enteros (para comparar contra el umbral).
int diferenciaAbsoluta(int a, int b) {
if (a > b) {
return a - b;
}
return b - a;
}
// Mantiene viva la conexión MQTT sin bloquear el riego: 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ó desde la última publicación exitosa
// (la humedad, solo si cambió 50 o más). Si publish() falla o no hay
// conexión, no se actualiza el registro: se reintenta en la próxima vuelta.
void publicarCambios() {
if (!mqtt.connected() || !hayMedicion) {
return; // sin internet o sin datos reales todavía: no se publica
}
if (estadoValvula != estadoValvulaPublicado
&& feedValvula.publish((int32_t)estadoValvula)) {
estadoValvulaPublicado = estadoValvula;
}
if (diferenciaAbsoluta(humedadActual, humedadPublicada) >= UMBRAL_PUBLICACION
&& feedHumedad.publish((int32_t)humedadActual)) {
humedadPublicada = humedadActual;
}
}
void setup() {
Serial.begin(VELOCIDAD_SERIAL);
pinMode(PIN_HIGROMETRO, INPUT);
pinMode(PIN_RELE_VALVULA, OUTPUT);
// Estado inicial seguro: válvula cerrada hasta que la primera medición
// diga otra cosa.
digitalWrite(PIN_RELE_VALVULA, VALVULA_CERRADA);
// Conexión WiFi. Esperar acá con delay() es aceptable porque el riego
// 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 control, una vez por segundo (como el original) ---
if (millisActual - millisAnterior >= INTERVALO_MEDICION_MS) {
millisAnterior = millisActual;
humedadActual = analogRead(PIN_HIGROMETRO);
hayMedicion = true; // desde ahora, publicarCambios() tiene datos reales
Serial.println(humedadActual);
// Histéresis del original: dos ifs independientes, dos umbrales.
if (humedadActual > UMBRAL_SECO) {
// Tierra demasiado seca: abrir la válvula para regar.
digitalWrite(PIN_RELE_VALVULA, VALVULA_ABIERTA);
estadoValvula = 1;
}
if (humedadActual < UMBRAL_HUMEDO) {
// Tierra suficientemente húmeda: cerrar la válvula.
digitalWrite(PIN_RELE_VALVULA, VALVULA_CERRADA);
estadoValvula = 0;
}
// Entre el umbral húmedo y el seco la válvula queda como estaba.
}
// --- Parte IoT ---
// Gracias a que el control no bloquea, estas dos tareas se atienden en
// CADA vuelta de loop(): esta es la razón de fondo por la que la guía
// enseña a abandonar delay() antes de llegar a IoT.
mantenerConexionMqtt();
publicarCambios();
} Así queda funcionando el tablero que permite monitorear el riego a distancia. Podés ver el sistema completo —el higrómetro midiendo, la válvula regando 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.
De lectura cruda a porcentaje
El Gauge muestra la lectura cruda del higrómetro, y “612” no le dice mucho a alguien que solo quiere saber si hay que regar. Tomando los umbrales que calibraste, modifiquen el programa para publicar un porcentaje de humedad (o directamente un estado “seco / húmedo”) en lugar del número crudo. ¿Qué hay que cambiar en el panel para que el bloque siga siendo coherente con el nuevo valor? ¿Conviene un Gauge o un Indicator?
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: el Indicator de valvula tiene que encenderse y apagarse junto con la
válvula en cada conmutación, y el Gauge de humedad, actualizarse cuando la lectura cambia
(recordá que se publica por cambios: con la tierra quieta, que no lleguen datos nuevos es lo
esperado; cambiá la humedad y van a aparecer). Todo con una pequeña demora de red. Si no llega
nada, revisá las credenciales y el nombre de la red; si llegan datos pero un bloque no reacciona,
revisá a qué feed está asociado y su condición.
Cierre del nivel
Al terminar este nivel, tu sistema de riego 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 qué tan seca está la tierra y si el sistema
está regando. Con esto queda completo el recorrido del proyecto. Si el grupo quiere seguir, en
la ficha del proyecto está la extensión sugerida: complementar con el proyecto “Invernadero
inteligente”, sumando sensores de humedad de ambiente y de temperatura.
Evidencia de cierre
Al terminar este nivel, esto tiene que estar funcionando:
- El sistema de riego funciona con código no bloqueante basado en millis(): no queda ningún delay() que detenga el programa entre mediciones.
- 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 lectura de humedad del suelo y el estado de la válvula publicados por el dispositivo (variante ESP32).