Saltar al contenido
Volver a la ficha del proyecto

Manual del proyecto · Proyecto 03

Sistema de Riego Automatizado

Diseño, construcción y programación de un sistema de riego automatizado que regula el paso de agua con una electroválvula: primero por intervalos de tiempo, luego según la humedad del suelo medida con un higrómetro, y por último con monitoreo remoto por Internet de las Cosas (IoT).

Arduino UNO R3 ESP32 DevKit
Dificultad:
Nivel educativo:
Secundario. Ciclo Básico.
Electrónica Programación Internet de las Cosas

Saberes Digitales

INET / EDUCAR — Ministerio de Educación, Cultura, Ciencia y Tecnología de la Nación Argentina

2019

Contenido del manual

  1. 01 Materiales y equipamiento
  2. 02 Regar por intervalos de tiempo Nivel inicial
  3. 03 Regar según la humedad del suelo Nivel intermedio
  4. 04 Monitorear el riego con Internet de las Cosas Nivel avanzado

Materiales y equipamiento

Insumos

Imagen Insumo Cantidad Plataforma
Foto de Módulo relé Módulo relé de 5 V Comanda la electroválvula. Con ESP32, verificá que el módulo se dispare con señal de 3,3 V (los módulos con optoacoplador suelen hacerlo) o alimentalo con 5 V desde el pin VIN. 1 Todas
Foto de Módulo reloj RTC Módulo reloj (RTC) Figura en la ficha de insumos del original, pero el código del proyecto NO lo usa: el riego temporizado se resuelve con delay() (nivel inicial) y millis() (nivel avanzado), no con un reloj de tiempo real. Se conserva en la lista por fidelidad a la ficha de 2019; sería la evolución natural para regar a horas fijas del día (inconsistencia documentada en los desvíos). 1 Todas
FC-28 393 AO DO GND VCC
Higrómetro de suelo (FC-28) Se usa desde el nivel intermedio. Salida analógica: a mayor lectura, suelo más seco. Con ESP32, alimentalo con 3,3 V y leelo por un GPIO del ADC1 (por ejemplo el 34, que es de solo entrada y sigue midiendo con el WiFi encendido). 1 Todas
Electroválvula de lavarropas (180°) El original la propone de 220 V (tipo lavarropas). Por seguridad, la práctica del aula usa una de baja tensión (12 V) o trabaja la lógica con el simulador; la conexión a 220 V queda como extensión exclusiva del docente, con instalación supervisada (desvío documentado). 1 Todas
Mangueras La de entrada va conectada a una canilla; la de salida lleva el agua a las plantas. según instalación Todas
Foto de Resistencia Resistencias Aparecen de forma genérica en la ficha del original. En el circuito básico no son imprescindibles (el módulo higrómetro y el módulo relé ya traen las suyas): quedan como material de reserva para adaptaciones. varias Todas
Foto de Cables dupont macho-macho Cable dupont macho-hembra 20 Todas
Foto de Arduino UNO R3 Arduino UNO R3 El original de 2019 sumaba un módulo IoT OBLOQ para el nivel avanzado; ese módulo está discontinuado. Con UNO, el nivel IoT se sigue de manera conceptual y la práctica se realiza con la variante ESP32. 1 Arduino UNO R3
Foto de Cable USB tipo B Cable USB tipo B 1 Arduino UNO R3
Foto de Fuente de alimentación Fuente de 9 V 1 A (plug centro positivo, 5,5 x 2,1 mm) (opcional) Para alimentar el sistema sin la computadora. El ESP32 se alimenta directamente por USB con una fuente de 5 V. 1 Arduino UNO R3
Foto de Placa ESP32 DevKit Placa ESP32 DevKit v1 Reemplaza al conjunto Arduino UNO + módulo OBLOQ del original: el ESP32 trae WiFi integrado, necesario para el nivel avanzado (IoT). 1 ESP32 DevKit
Foto de Cable micro USB Cable micro USB (con línea de datos) 1 ESP32 DevKit

Equipamiento del taller

  • Computadora
  • Soldador
  • Estaño
  • Alicate
  • Pinza de punta
  • Pinza brusela

Plano de conexionado en protoboard

Arduino UNO Conexionado en protoboard · sistema de riego

Deslizá horizontalmente para ver el plano completo

Arduino UNO
Higrómetro FC-28
VCC GND AO DO
Módulo relé · 1 canal
RELAY
IN VCC GND COM NO NC
Carga de práctica (LED, no 220 V)
220 Ω
Conexiones del plano · Arduino UNO. Convención de colores: alimentación VCC = rojo, GND = negro, señal analógica del higrómetro = amarillo, señal al relé = azul.
Desde Hacia Color de cable
Arduino UNO · 5V Bus + de la protoboard rojo
Arduino UNO · GND Bus − de la protoboard negro
Higrómetro FC-28 · VCC Bus + de la protoboard rojo
Higrómetro FC-28 · GND Bus − de la protoboard negro
Higrómetro FC-28 · AO Arduino UNO · A0 amarillo
Módulo relé · VCC Bus + de la protoboard rojo
Módulo relé · GND Bus − de la protoboard negro
Módulo relé · IN Arduino UNO · 2 azul
Relé · COM Bus + de la protoboard rojo
Relé · NO LED de práctica · ánodo (+) rojo
LED de práctica · cátodo (−) Resistencia 220 Ω negro
Resistencia 220 Ω Bus − de la protoboard negro
ESP32 DevKit v1 Conexionado en protoboard · sistema de riego

Deslizá horizontalmente para ver el plano completo

ESP32 DevKit v1
Higrómetro FC-28
VCC GND AO DO
Módulo relé · 1 canal
RELAY
IN VCC GND COM NO NC
Carga de práctica (LED, no 220 V)
220 Ω
Conexiones del plano · ESP32 DevKit v1. Convención de colores: alimentación VCC = rojo, GND = negro, señal analógica del higrómetro = amarillo, señal al relé = azul.
Desde Hacia Color de cable
ESP32 DevKit v1 · 3V3 Bus + de la protoboard rojo
ESP32 DevKit v1 · GND Bus − de la protoboard negro
Higrómetro FC-28 · VCC Bus + de la protoboard rojo
Higrómetro FC-28 · GND Bus − de la protoboard negro
Higrómetro FC-28 · AO ESP32 DevKit v1 · GPIO34 amarillo
Módulo relé · VCC Bus + de la protoboard rojo
Módulo relé · GND Bus − de la protoboard negro
Módulo relé · IN ESP32 DevKit v1 · GPIO16 azul
Relé · COM Bus + de la protoboard rojo
Relé · NO LED de práctica · ánodo (+) rojo
LED de práctica · cátodo (−) Resistencia 220 Ω negro
Resistencia 220 Ω Bus − de la protoboard negro
Nivel inicial

Regar por intervalos de tiempo

Conectar la electroválvula

El paso del agua a la manguera del sistema de riego lo regula una electroválvula: una válvula que se controla eléctricamente, como las que suelen encontrarse en los lavarropas. Cuando no recibe corriente, la válvula está normalmente cerrada; al energizarse, abre y deja pasar el agua.

Electroválvula de plástico blanco tipo lavarropas, con dos conectores para manguera (entrada y salida de agua) y dos terminales eléctricos tipo pala en un costado.
La electroválvula del proyecto: dos bocas para manguera (entrada y salida) y dos terminales eléctricos tipo pala, que se conectan al relé (figura original del proyecto).

La electroválvula tiene dos conectores para manguera (uno de entrada y otro de salida de agua) y dos terminales eléctricos tipo pala. Lo primero que haremos es instalar las mangueras correspondientes: la de entrada irá conectada a una canilla y la de salida será la que lleve el agua directamente a las plantas.

Como los terminales de la válvula manejan más tensión de la que entrega la placa, no la conectamos directamente al Arduino: usamos un relé como intermediario. El relé es un interruptor comandado por la placa con una señal chica (5 V en el UNO, 3,3 V en el ESP32), que abre o cierra el circuito de potencia de la válvula. El código que subamos controlará el relé y, en consecuencia, la apertura y el cierre de la electroválvula.

Esquema de conexión: placa Arduino UNO con el pin 5V y GND al módulo relé, y el pin digital 2 a la entrada de señal del relé; en los bornes del relé, la electroválvula rotulada ELECTROVALVULA 1 y un enchufe rotulado ENCHUFE 220V.
Esquema de conexión del nivel inicial (figura original del proyecto). Atención: el original alimentaba la válvula desde un enchufe de 220 V; en esta reedición ese cableado se reemplaza por una válvula y una fuente de baja tensión.

¡Atención! Agua y electricidad no se llevan bien. Todas las conexiones eléctricas van fuera del recorrido del agua y por encima de su nivel, y siempre que manipules el circuito hacelo con las manos secas y con el sistema desenchufado. El original trabajaba directamente con 220 V; en esta reedición la válvula es de 12 V (o se trabaja la lógica con el simulador), pero los hábitos de seguridad se practican igual, porque son los que te protegen cuando la instalación es real.

La conexión tiene dos partes:

  • El circuito de potencia: la fuente de baja tensión, el relé y la electroválvula. Conectamos la fuente a la bornera COM del relé, la bornera NO a un terminal de la válvula, y el otro terminal de la válvula de vuelta al otro polo de la fuente. Así, la válvula solo recibe energía cuando el relé cierra el contacto. En el esquema original, esta rama es la que llega al enchufe de 220 V; acá va a la fuente de baja tensión.
  • El circuito de control: la señal del relé hacia la placa, con un cable dupont.

En el Arduino UNO seguimos el esquema original: del módulo relé, el pin + va a 5V, el pin a GND y el pin de señal S al pin digital 2 (la constante PIN_RELE_VALVULA del código del paso siguiente).

En el ESP32 el armado es el mismo, pero el pin de señal del relé va a un GPIO de propósito general (evitamos los GPIO 0, 2, 12 y 15, que intervienen en el arranque de la placa). El número exacto está en la constante PIN_RELE_VALVULA al comienzo del código del paso siguiente: conectá el cable al GPIO que indica esa constante. Como el ESP32 trabaja a 3,3 V, usá un módulo relé cuya entrada acepte esa señal (la mayoría lo hace, aunque se alimenten con 5 V) y nunca inyectes 5 V en un GPIO.

Programar el funcionamiento del dispositivo

La programación la realizaremos directamente en el lenguaje de Arduino (C++), usando el Arduino IDE en su versión 2.x, que se descarga gratis desde arduino.cc/en/software. Un programa de Arduino se llama sketch y tiene dos bloques: setup(), que se ejecuta una sola vez al inicializarse el dispositivo, y loop(), que se ejecuta repetidamente después. Los comentarios (todo lo que sigue a //) no tienen efecto en el programa: están para que el código sea más fácil de interpretar.

Para asegurarnos de que el sistema funcione, empezamos con una prueba simple: mantener la válvula abierta un rato y luego cerrada otro rato, en un ciclo que se repite. En el código, al poner la salida del relé en VALVULA_ABIERTA (estado ALTO) enviamos corriente al relé, que permite el paso de energía a la electroválvula para que se abra y riegue; al ponerla en VALVULA_CERRADA (estado BAJO) cortamos esa energía y la válvula se cierra.

Los tiempos de cada tramo están en dos constantes al comienzo del programa, DURACION_RIEGO_MS (cuánto riega) y DURACION_ESPERA_MS (cuánto espera entre riegos), expresadas en milisegundos. El programa las usa con delay(), que detiene todo hasta que se cumple el tiempo: en este primer nivel es la herramienta más simple y hace que el código se lea igual que la secuencia real.

Ver el código completo · riego-temporizado.ino
riego-temporizado.ino Arduino UNO
// Riego por tiempo: una electroválvula, controlada por un relé, se abre
// durante un rato para regar y luego se cierra durante un rato más largo,
// en un ciclo que se repite para siempre. No hay sensores todavía: el
// riego depende solo del reloj interno (los delay).
//
// SEGURIDAD: agua y electricidad no se llevan bien. Todas las conexiones
// eléctricas van fuera y POR ENCIMA del nivel del agua. La electroválvula
// del original es de lavarropas (220 V): en el aula se usa una válvula de
// baja tensión (12 V) o se simula la carga, y la conexión a 220 V queda
// reservada al docente o a una instalación supervisada, nunca sobre la
// protoboard (desvío pedagógico acordado).
//
// Plataforma: Arduino UNO.
// Conexión (la del esquema original): entrada "S" del módulo relé de la
// electroválvula → pin 2.
//
// Basado en: Fragmentos de código 1 y 2 — Sistema de Riego Automatizado,
// Saberes Digitales (INET/EDUCAR 2019)
// Modernización: se reemplazó el código generado por mBlock3 (_delay,
// _loop) por C++ idiomático de Arduino con constantes nombradas.
//
// Nota pedagógica: la ficha de insumos del PDF menciona un "módulo reloj"
// (RTC), pero el código del original NO lo usa: temporiza con delay. Un
// RTC sería la evolución natural (regar a horas fijas del día, no cada X
// tiempo desde el encendido), pero queda fuera del alcance de este nivel.

// Relé que controla la electroválvula de entrada de agua.
const int PIN_RELE_VALVULA = 2;

// Módulo relé activo-alto, como en el original (HIGH = válvula abierta,
// pasa el agua). Si tu módulo es activo-bajo, invertí solo estas dos.
const int VALVULA_ABIERTA = HIGH; // riega
const int VALVULA_CERRADA = LOW;  // corta el agua

// Tiempos del ejemplo del original (Fragmento 2): riega 2 minutos y luego
// espera 1 hora. OJO en el aula: una hora es una eternidad para probar;
// bajá estos valores a segundos (por ejemplo 5000 y 10000) mientras
// verificás el circuito, y recién después poné los tiempos reales de tu
// huerta. En el nivel intermedio, el higrómetro reemplaza este reloj fijo.
const unsigned long DURACION_RIEGO_MS  = 120000UL;  // 2 minutos regando
const unsigned long DURACION_ESPERA_MS = 3600000UL; // 1 hora sin regar

void setup() {
  pinMode(PIN_RELE_VALVULA, OUTPUT);
}

void loop() {
  // delay() detiene todo el programa: en este nivel es la herramienta que
  // ya conocemos y hace que el código se lea igual que la secuencia real.
  digitalWrite(PIN_RELE_VALVULA, VALVULA_ABIERTA);
  delay(DURACION_RIEGO_MS);
  digitalWrite(PIN_RELE_VALVULA, VALVULA_CERRADA);
  delay(DURACION_ESPERA_MS);
}
riego-temporizado.ino ESP32
// Riego por tiempo: una electroválvula, controlada por un relé, se abre
// durante un rato para regar y luego se cierra durante un rato más largo,
// en un ciclo que se repite para siempre. No hay sensores todavía: el
// riego depende solo del reloj interno (los delay).
//
// SEGURIDAD: agua y electricidad no se llevan bien. Todas las conexiones
// eléctricas van fuera y POR ENCIMA del nivel del agua. La electroválvula
// del original es de lavarropas (220 V): en el aula se usa una válvula de
// baja tensión (12 V) o se simula la carga, y la conexión a 220 V queda
// reservada al docente o a una instalación supervisada, nunca sobre la
// protoboard (desvío pedagógico acordado).
//
// Plataforma: ESP32 (DevKit v1).
// Conexión: entrada "S" del módulo relé de la electroválvula → GPIO 16.
// Nota eléctrica: el ESP32 trabaja a 3,3 V (no 5 V como el UNO). Elegí un
// módulo relé cuya entrada acepte señal de 3,3 V (la mayoría la aceptan
// aunque se alimenten con 5 V) y nunca inyectes 5 V en un GPIO.
//
// Basado en: Fragmentos de código 1 y 2 — Sistema de Riego Automatizado,
// Saberes Digitales (INET/EDUCAR 2019)
// Modernización: se reemplazó el código generado por mBlock3 (_delay,
// _loop) por C++ idiomático con constantes nombradas, y se portó a ESP32
// con un GPIO seguro (el original usaba el pin 2 del UNO).
//
// Nota pedagógica: la ficha de insumos del PDF menciona un "módulo reloj"
// (RTC), pero el código del original NO lo usa: temporiza con delay. Un
// RTC sería la evolución natural (regar a horas fijas del día), pero queda
// fuera del alcance de este nivel.

// Relé que controla la electroválvula de entrada de agua.
const int PIN_RELE_VALVULA = 16;

// Módulo relé activo-alto, como en el original (HIGH = válvula abierta,
// pasa el agua). Si tu módulo es activo-bajo, invertí solo estas dos.
const int VALVULA_ABIERTA = HIGH; // riega
const int VALVULA_CERRADA = LOW;  // corta el agua

// Tiempos del ejemplo del original (Fragmento 2): riega 2 minutos y luego
// espera 1 hora. OJO en el aula: una hora es una eternidad para probar;
// bajá estos valores a segundos (por ejemplo 5000 y 10000) mientras
// verificás el circuito, y recién después poné los tiempos reales de tu
// huerta. En el nivel intermedio, el higrómetro reemplaza este reloj fijo.
const unsigned long DURACION_RIEGO_MS  = 120000UL;  // 2 minutos regando
const unsigned long DURACION_ESPERA_MS = 3600000UL; // 1 hora sin regar

void setup() {
  pinMode(PIN_RELE_VALVULA, OUTPUT);
}

void loop() {
  // delay() detiene todo el programa: en este nivel es la herramienta que
  // ya conocemos y hace que el código se lea igual que la secuencia real.
  digitalWrite(PIN_RELE_VALVULA, VALVULA_ABIERTA);
  delay(DURACION_RIEGO_MS);
  digitalWrite(PIN_RELE_VALVULA, VALVULA_CERRADA);
  delay(DURACION_ESPERA_MS);
}
Riego por tiempo: la válvula se abre durante DURACION_RIEGO_MS y luego se cierra durante DURACION_ESPERA_MS, en un ciclo que se repite para siempre.

El programa viene con los tiempos del ejemplo del original (riega 2 minutos y luego espera 1 hora). Para probar en el aula, una hora es una eternidad: bajá provisoriamente esas constantes a segundos (por ejemplo unos pocos miles de milisegundos) mientras verificás el circuito, y recién después cargá los tiempos reales de tu huerta, que vas a definir en el paso 3.

Podés ver el comportamiento —la válvula abriendo y cerrando por intervalos— en el simulador, antes de tocar el agua real:

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.

Para subir el código a la placa, conectala a la computadora con el cable USB, elegí en el Arduino IDE 2.x la placa y el puerto (en el IDE 2.x se eligen juntos en el menú desplegable de la barra superior) y apretá el botón Subir (la flecha →). Al terminar, aparece el mensaje de carga completada en la barra inferior.

Para el Arduino UNO: usá el cable USB tipo B, elegí la placa Arduino UNO y el puerto que se agregó al conectarla (en Windows es un puerto COM numerado; en Linux suele llamarse /dev/ttyACM0 o similar).

Para el ESP32 hay dos pasos previos que se hacen una sola vez: instalar el soporte de placas ESP32 desde el gestor de placas del IDE (buscá “esp32” e instalá el paquete de Espressif) y, si el puerto no aparece, instalar el driver del conversor USB-serie de tu placa (generalmente CP210x o CH340). Después, elegí la placa ESP32 Dev Module (o “DOIT ESP32 DEVKIT V1”) y el puerto correspondiente. Si al subir el IDE queda esperando en “Connecting…”, mantené apretado el botón BOOT de la placa hasta que empiece la carga.

Determinar tiempos de riego adecuados

En el programa anterior, la válvula regaba y esperaba tiempos de ejemplo. Ahora hay que ajustarlos a la realidad de tus plantas.

La frecuencia de riego necesaria varía según la necesidad de las plantas seleccionadas, las condiciones climáticas, el tipo de suelo y su cobertura (por ejemplo, si tiene hojarasca o no), y la cantidad de agua aportada en cada evento de riego. En este sentido, es interesante que los estudiantes investiguen previamente acerca de las necesidades de las plantas elegidas, mediante la búsqueda de información en distintas fuentes o la realización de entrevistas a personas expertas en el tema.

Una vez determinado el tiempo de riego más adecuado, hay que expresarlo en milisegundos y cargarlo en las constantes DURACION_RIEGO_MS y DURACION_ESPERA_MS. Por ejemplo, si querés que riegue 2 minutos y luego espere 1 hora, esos tiempos son 120000 y 3600000 milisegundos, respectivamente (1 segundo son 1000 milisegundos). Volvé a subir el programa con tus valores.

Cierre del nivel

Al terminar este nivel, tu sistema riega solo: la placa abre la electroválvula durante el tiempo de riego que definiste y la cierra durante la espera, en un ciclo automático. Además, ya sabés escribir y subir programas desde el Arduino IDE 2.x, una herramienta que te va a acompañar el resto del proyecto. Pero regar “por reloj” tiene un límite: riega llueva o truene, esté la tierra seca o empapada. En el nivel intermedio el sistema va a dejar de mirar el reloj y va a mirar la tierra, con un higrómetro que mide la humedad del suelo y decide cuándo hace falta regar.

Evidencia de cierre

Al terminar este nivel, esto tiene que estar funcionando:

  • La electroválvula está instalada con sus mangueras (entrada desde la canilla, salida al riego) y conectada al relé, con el circuito de potencia separado del de control y lejos del agua.
  • Sabés subir un programa desde el Arduino IDE 2.x: elegir la placa, elegir el puerto y verificar que el código quedó cargado.
  • El sistema abre y cierra la válvula por intervalos de tiempo, con los valores DURACION_RIEGO_MS y DURACION_ESPERA_MS ajustados (o reducidos para probar en el aula).
  • Investigaste y determinaste una frecuencia de riego adecuada para las plantas elegidas.
Nivel intermedio

Regar según la humedad del suelo

Conectar el higrómetro

En el nivel inicial desarrollamos un programa que controla la frecuencia de riego abriendo y cerrando una electroválvula por tiempo. Ahora vamos a ajustar ese riego en base a la información que obtengamos midiendo la humedad del suelo: regar cuando la humedad sea baja y dejar de regar cuando alcance un valor que consideremos suficiente.

Para eso usamos un higrómetro de suelo (FC-28), un sensor que mide la humedad de la tierra a partir de su conductividad eléctrica.

Módulo higrómetro de suelo FC-28: a la izquierda, la plaqueta con el circuito y los pines de conexión; a la derecha, la horquilla de dos puntas que se clava en la tierra, unida a la plaqueta por dos cables.
El higrómetro de suelo FC-28: la horquilla (derecha) se clava en la tierra y la plaqueta (izquierda) entrega la lectura a la placa (figura original del proyecto).

El módulo tiene tres pines de conexión: dos para la alimentación (VCC y GND) y un tercero de salida analógica, que entrega un nivel de tensión según la humedad registrada. Por eso se conecta a una entrada analógica de la placa: esas entradas digitalizan la señal y devuelven un número proporcional a la tensión de entrada.

Esquema de conexión: placa Arduino UNO con 5V y GND al módulo higrómetro FC-28, y la salida analógica del módulo al pin A0; la horquilla de dos puntas conectada a la plaqueta.
Esquema de conexión del higrómetro en el nivel intermedio (figura original del proyecto): alimentación a 5V/GND y salida analógica al pin A0.

Para obtener una buena medición hay que enterrar la horquilla del sensor en un lugar que represente lo más fielmente posible las condiciones generales de la huerta, cantero o maceta. Una mala ubicación (por ejemplo, fuera de la zona de riego) puede hacer que algunas zonas queden muy secas y otras muy húmedas.

En el Arduino UNO seguimos el esquema original: VCC al pin 5V, GND a GND y la salida analógica del módulo a la entrada A0 (la constante PIN_HIGROMETRO del código del paso 5). Las entradas analógicas del UNO (A0 a A5) devuelven un valor entre 0 y 1023.

En el ESP32 la conexión es equivalente, con dos cuidados propios de la placa: alimentá el módulo con 3,3 V (no 5 V) para que su salida no supere ese nivel, y leelo por un GPIO del ADC1, que sigue funcionando con el WiFi encendido. El número exacto está en la constante PIN_HIGROMETRO del código del paso 5 (el GPIO 34, que además es de solo entrada). Su conversor es de 12 bits, así que devuelve un valor entre 0 y 4095, en lugar del 0 a 1023 del UNO.

Leer el puerto serie

Cuando trabajamos con entradas digitales, que solo tienen dos estados, es fácil reflejar su estado con un LED. Pero el higrómetro es analógico: no da dos valores sino un rango (todos los niveles posibles de humedad de la tierra). Para ver esos datos y tomar decisiones en base a ellos usamos el puerto serie.

El puerto serie es un canal de comunicación entre la placa y la computadora. Lo vamos a usar para ver en pantalla los valores que registra el higrómetro. En la placa, el puerto serie está físicamente vinculado al mismo cable USB con el que subimos los programas, así que sirve tanto para cargar el código como para intercambiar mensajes.

En el Arduino UNO ese puerto está conectado a los pines 0 (RX) y 1 (TX): en proyectos que usen el puerto serie, no conectes nada a esos dos pines, porque quedan reservados para la comunicación (por eso el higrómetro va a A0 y no ahí).

En el código, para habilitar el puerto serie se usa Serial.begin() con una velocidad —en este proyecto, la constante VELOCIDAD_SERIAL, de 115200 baudios— y para enviar un mensaje se usa Serial.println(). Del lado de la computadora, en el Arduino IDE 2.x abrimos el Monitor Serie (el ícono de la lupa arriba a la derecha, o desde el menú Herramientas) y seleccionamos la misma velocidad, 115200 baudios, en su menú desplegable. Si los caracteres se ven ilegibles, casi siempre es porque la velocidad del Monitor no coincide con la del programa.

Leer el higrómetro por el puerto serie

Ahora que sabemos comunicarnos con la computadora por el puerto serie, vamos a transmitir los valores que leemos en la entrada analógica donde está el higrómetro. El programa que armaremos en el paso 5 hace exactamente eso: en cada vuelta lee el sensor con analogRead(PIN_HIGROMETRO), guarda el número en una variable y lo envía a la computadora con Serial.println().

Cada vez que subís un programa nuevo, el IDE usa el puerto para la carga, así que puede que el Monitor Serie se corte un instante; una vez terminada la subida, volvés a verlo actualizarse. Deberías empezar a recibir valores dentro del rango de tu placa (0 a 1023 en el UNO, 0 a 4095 en el ESP32) que cambian con la humedad: probá clavar la horquilla en tierra seca y en tierra mojada y observá cómo se mueve el número. Ese “movimiento” es lo que vamos a usar para decidir.

Determinar los valores de humedad para activar la válvula

El higrómetro nos va a permitir decidir cuándo abrir la válvula (cuando el suelo esté demasiado seco) y cuándo cerrarla (cuando esté suficientemente húmedo). Para eso primero tenemos que fijar dos valores de referencia.

Acá hay un detalle que conviene tener muy claro, porque es fácil equivocarse: en estos módulos resistivos, la lectura es inversa. A mayor valor leído, el suelo está más seco (hay menos agua conduciendo, así que el sensor “ve” más resistencia). No al revés. Por eso:

  • El umbral de “seco” es el valor alto: la constante UMBRAL_SECO. Cuando la lectura lo supera, la tierra está demasiado seca y hay que regar.
  • El umbral de “húmedo” es el valor bajo: la constante UMBRAL_HUMEDO. Cuando la lectura cae por debajo de él, la tierra está suficientemente húmeda y hay que cortar.

Ahora que ya podemos ver qué valores arroja el higrómetro por el puerto serie, tomamos muestras de tierra con distintos grados de humedad en unos recipientes, les colocamos el sensor y vamos anotando los valores (sin olvidar limpiar y secar bien la horquilla entre una muestra y la siguiente). Con esos números elegimos tus UMBRAL_SECO y UMBRAL_HUMEDO. El programa del paso 5 ya viene con los valores del ejemplo del original cargados en esas constantes, pero son un punto de partida: se miden en tu suelo, no se copian.

Programar la apertura y el cierre de la válvula según la humedad

Con los umbrales definidos, editamos el programa para que el higrómetro comande la válvula. La lógica, en cada vuelta de loop(), es la que definimos en el paso anterior:

  1. Leer el higrómetro una vez y guardar el valor (y publicarlo por el puerto serie, para seguir viéndolo).
  2. Si la lectura supera UMBRAL_SECO, la tierra está demasiado seca: abrir la válvula (VALVULA_ABIERTA) para regar.
  3. Si la lectura cae por debajo de UMBRAL_HUMEDO, la tierra está húmeda: cerrar la válvula (VALVULA_CERRADA).
Ver el código completo · riego-por-humedad.ino
riego-por-humedad.ino Arduino UNO
// Riego por humedad: un higrómetro de suelo (FC-28) mide qué tan seca está
// la tierra y el programa abre o cierra la electroválvula en función de eso.
// El higrómetro también se imprime por la consola serial para poder
// calibrar los umbrales observando valores reales.
//
// CONVENCIÓN DEL HIGRÓMETRO (¡importante!): en estos módulos resistivos,
// MAYOR lectura = tierra MÁS SECA (más resistencia, menos agua conduciendo).
// Por eso "seco" es el umbral ALTO y "húmedo" el umbral BAJO. El original
// lo confirma: "mayor a 750 => demasiado seco; menor a 380 => húmedo".
//
// HISTÉRESIS NATURAL DEL ORIGINAL (se preserva tal cual): son dos ifs
// independientes con DOS umbrales distintos. Por encima de 750 abre; por
// debajo de 380 cierra; en la franja intermedia (380 a 750) NINGUNO de los
// dos se cumple, así que la válvula conserva su estado anterior. Esa banda
// muerta evita que la válvula traquetee cuando la lectura baila cerca de un
// único umbral: es una solución elegante que ya venía en el original.
//
// 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): señal analógica del higrómetro → A0;
// entrada "S" del módulo relé de la electroválvula → pin 2.
//
// Basado en: Fragmento de código 5 — Sistema de Riego Automatizado,
// Saberes Digitales (INET/EDUCAR 2019)
// Modernización: constantes nombradas en lugar del C de mBlock3; el
// original llamaba a analogRead(A0) tres veces por vuelta (una para
// imprimir y una por cada if), lo que podía dar tres valores distintos;
// acá se lee UNA vez a una variable y esa misma lectura se imprime y se
// compara. Es más correcto y hace evidente que se decide sobre un único dato.

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). NO son mágicos: salen
// de medir muestras de tierra con distintos grados de humedad y anotar los
// valores por la consola (Paso 4 del original). Ajustalos a TU suelo.
const int UMBRAL_SECO   = 750; // por encima: tierra demasiado seca → regar
const int UMBRAL_HUMEDO = 380; // por debajo: tierra húmeda → cortar

const long VELOCIDAD_SERIAL = 115200; // la del original

// El original medía una vez por segundo (_delay(1)).
const unsigned long INTERVALO_MEDICION_MS = 1000;

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() {
  int humedad = analogRead(PIN_HIGROMETRO);

  // Publicamos la lectura por consola: sirve para calibrar los umbrales
  // y para ver en vivo qué "piensa" el sistema del suelo.
  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 no se cumple ninguno: la válvula queda como estaba.

  delay(INTERVALO_MEDICION_MS);
}
riego-por-humedad.ino ESP32
// Riego por humedad: un higrómetro de suelo (FC-28) mide qué tan seca está
// la tierra y el programa abre o cierra la electroválvula en función de eso.
// El higrómetro también se imprime por la consola serial para poder
// calibrar los umbrales observando valores reales.
//
// CONVENCIÓN DEL HIGRÓMETRO (¡importante!): en estos módulos resistivos,
// MAYOR lectura = tierra MÁS SECA (más resistencia, menos agua conduciendo).
// Por eso "seco" es el umbral ALTO y "húmedo" el umbral BAJO. El original
// lo confirma: "mayor a 750 => demasiado seco; menor a 380 => húmedo".
//
// HISTÉRESIS NATURAL DEL ORIGINAL (se preserva tal cual): son dos ifs
// independientes con DOS umbrales distintos. Por encima del umbral seco
// abre; por debajo del húmedo cierra; en la franja intermedia NINGUNO de
// los dos se cumple, así que la válvula conserva su estado anterior. Esa
// banda muerta evita que la válvula traquetee cuando la lectura baila
// cerca de un único umbral: es una solución elegante que ya venía en el
// original.
//
// DIFERENCIA DE ADC (didáctica): el conversor del ESP32 es de 12 bits, así
// que analogRead() devuelve 0 a 4095 (3,3 V), NO el 0 a 1023 (10 bits, 5 V)
// del UNO. Los umbrales del original (750 y 380) se reescalan en la misma
// proporción: 750/1023 ≈ 3000/4095 y 380/1023 ≈ 1520/4095. Mismo fenómeno,
// distinta "regla de medir".
//
// 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: señal analógica del higrómetro → GPIO 34; relé de la
// electroválvula → GPIO 16.
// Nota eléctrica: el ESP32 trabaja a 3,3 V. El GPIO 34 es de SOLO ENTRADA
// y pertenece al ADC1 (sigue midiendo con WiFi activo, a diferencia del
// ADC2). El módulo higrómetro de 3 pines ya entrega una señal analógica
// lista para el pin; alimentalo con 3,3 V para que su salida no supere ese
// nivel.
//
// Basado en: Fragmento de código 5 — Sistema de Riego Automatizado,
// Saberes Digitales (INET/EDUCAR 2019)
// Modernización: constantes nombradas y portado a ESP32 (el original usaba
// A0 y el pin 2 del UNO); umbrales reescalados al ADC de 12 bits; el
// original llamaba a analogRead tres veces por vuelta (una para imprimir y
// una por cada if), lo que podía dar tres valores distintos; acá se lee UNA
// vez a una variable y esa misma lectura se imprime y se compara.

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.
// Ajustalos a TU suelo midiendo con la consola (Paso 4 del original).
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 original medía una vez por segundo (_delay(1)).
const unsigned long INTERVALO_MEDICION_MS = 1000;

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() {
  int humedad = analogRead(PIN_HIGROMETRO);

  // Publicamos la lectura por consola: sirve para calibrar los umbrales
  // y para ver en vivo qué "piensa" el sistema del suelo.
  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 no se cumple ninguno: la válvula
  // queda como estaba.

  delay(INTERVALO_MEDICION_MS);
}
Riego por humedad: se lee el higrómetro, se imprime por el puerto serie y se abre o cierra la válvula según los umbrales. Entre UMBRAL_HUMEDO y UMBRAL_SECO, la válvula conserva su estado.

Hay un detalle elegante que ya venía en el original: son dos if independientes con dos umbrales distintos. Cuando la lectura queda en la franja intermedia (entre UMBRAL_HUMEDO y UMBRAL_SECO), ninguno de los dos se cumple, así que la válvula conserva el estado que tenía. Esa banda “muerta” es una histéresis natural: evita que la válvula traquetee abriendo y cerrando sin parar cuando la lectura baila cerca de un único punto. Es la respuesta al desafío del paso 4.

Podés ver el sistema completo —el higrómetro midiendo, la lectura subiendo cuando la tierra se seca y la válvula regando hasta humedecerla— 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.

Cierre del nivel

Al terminar este nivel, el sistema dejó de regar “por reloj” y pasó a regar “por necesidad”: el higrómetro mide la humedad del suelo y la placa abre la válvula solo cuando la tierra está seca, cortando cuando ya está húmeda. Aprendiste a leer un sensor analógico por el puerto serie, a calibrar umbrales con mediciones reales y a aprovechar una histéresis que evita el traqueteo de la válvula. En el nivel avanzado, los estudiantes y docentes van a poder mirar todo esto desde el celular: vamos a publicar la humedad del suelo y el estado de la válvula por Internet de las Cosas.

Evidencia de cierre

Al terminar este nivel, esto tiene que estar funcionando:

  • El higrómetro está conectado a la entrada analógica de la placa (A0 en el UNO, GPIO 34 en el ESP32) y su horquilla clavada en un lugar representativo de la tierra a regar.
  • Sabés abrir el Monitor Serie del Arduino IDE 2.x a 115200 baudios y leer los valores que reporta el higrómetro.
  • Determinaste tus umbrales UMBRAL_SECO y UMBRAL_HUMEDO tomando muestras, entendiendo que a mayor lectura, tierra más seca.
  • El sistema riega cuando la tierra está seca y corta cuando está húmeda, con la banda entre umbrales evitando que la válvula traquetee.
Nivel avanzado

Monitorear el riego con Internet de las Cosas

Introducción a Internet de las Cosas (IoT)

Internet de las Cosas (en inglés Internet of Things, abreviado IoT) 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:

  1. Un dispositivo capaz de conectarse a internet.
  2. 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 ESP32 y con los servidores de Adafruit IO.

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 IO, 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 feed 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 bloqueante: 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-no-bloqueante.ino Arduino UNO
// 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-no-bloqueante.ino ESP32
// 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).
}
El mismo control de la válvula por humedad del nivel intermedio, ahora sin delay(): el programa consulta millis() y solo mide cuando pasó el intervalo.

Teniendo un programa de riego con código no bloqueante (es decir, sin delay()) estamos en condiciones de incorporar IoT.

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-iot.ino ESP32
// 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();
}
El sistema de riego IoT completo: control no bloqueante + WiFi del ESP32 + publicación de la humedad y el estado de la válvula en Adafruit IO.

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.

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).