Manual del proyecto · Proyecto 04
Invernadero Inteligente
Diseño, construcción y programación de un invernadero inteligente: un sistema que mide la temperatura y la humedad del aire con un sensor DHT11 y avisa con un LED cuándo hay que ventilar; luego muestra esos valores en un display LCD y, por último, los monitorea de forma remota por Internet de las Cosas (IoT).
- Dificultad:
- Nivel educativo:
- Secundario. Ciclo Básico.
Contenido del manual
- 01 Materiales y equipamiento
- 02 Avisar cuándo ventilar por temperatura Nivel inicial
- 03 Ver temperatura y humedad en un display LCD Nivel intermedio
- 04 Monitorear el invernadero con Internet de las Cosas Nivel avanzado
Materiales y equipamiento
Insumos
| Imagen | Insumo | Cantidad | Plataforma |
|---|---|---|---|
| | Sensor DHT11 (humedad y temperatura) Sensor digital: mide temperatura y humedad del aire y entrega los datos por un pin digital (no analógico, no va a A0). En el esquema original se conecta al pin 2 del UNO; en el ESP32, al GPIO 4. No admite más de una lectura por segundo. | 1 | Todas |
| | LED verde de 5 mm Indicador de «hay que ventilar»: se enciende cuando la temperatura supera el umbral. La pata larga (positiva) va al pin de la placa. | 1 | Todas |
| | Resistencia de 220 Ω Limita la corriente del LED para que no se dañe. | 1 | Todas |
| | Cable dupont macho-hembra | 20 | Todas |
| | Cable dupont hembra-hembra El sensor DHT11 y el módulo LCD I2C traen pines macho; para llegar a la protoboard o a la placa suelen usarse cables con punta hembra. | 20 | Todas |
| | Protoboard | 1 | Todas |
| | 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 |
| | Cable USB tipo B | 1 | Arduino UNO R3 |
| | Display LCD Keypad Shield Pantalla de 16x2 con formato de shield: se apila sobre el UNO y se maneja en modo paralelo (6 pines). Por su formato Arduino UNO, NO se enchufa en un ESP32. | 1 | Arduino UNO R3 |
| | 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 |
| | 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). Trabaja a 3,3 V (no 5 V como el UNO). | 1 | ESP32 DevKit |
| | Display LCD 1602 con módulo I2C La alternativa del shield para el ESP32: la misma pantalla de 16x2, pero con un módulo I2C que se conecta con solo dos cables de señal (SDA y SCL) y la librería LiquidCrystal_I2C, en lugar de los 6 pines del shield paralelo del UNO. | 1 | ESP32 DevKit |
| | 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
Deslizá horizontalmente para ver el plano completo
Deslizá horizontalmente para ver el plano completo
| Desde | Hacia | Color de cable |
|---|---|---|
| Arduino UNO · 5V | Bus + de la protoboard | rojo |
| Arduino UNO · GND | Bus − de la protoboard | negro |
| Sensor DHT11 · VCC | Bus + de la protoboard | rojo |
| Sensor DHT11 · GND | Bus − de la protoboard | negro |
| Sensor DHT11 · DATA | Arduino UNO · 2 | amarillo |
| LED de ventilación · ánodo (+) | Arduino UNO · 13 | azul |
| LED de ventilación · cátodo (−) | Resistencia 220 Ω | negro |
| Resistencia 220 Ω | Bus − de la protoboard | negro |
| LCD Keypad Shield | Arduino UNO · apilado sobre los pines digitales (RS 8, E 9, D4-D7 4/5/6/7) | blanco |
| Desde | Hacia | Color de cable |
|---|---|---|
| ESP32 DevKit v1 · 3V3 | Bus + de la protoboard | rojo |
| ESP32 DevKit v1 · GND | Bus − de la protoboard | negro |
| Sensor DHT11 · VCC | Bus + de la protoboard | rojo |
| Sensor DHT11 · GND | Bus − de la protoboard | negro |
| Sensor DHT11 · DATA | ESP32 DevKit v1 · GPIO4 | amarillo |
| LED de ventilación · ánodo (+) | ESP32 DevKit v1 · GPIO16 | azul |
| LED de ventilación · cátodo (−) | Resistencia 220 Ω | negro |
| Resistencia 220 Ω | Bus − de la protoboard | negro |
| LCD 1602 I2C · VCC | Bus + de la protoboard | rojo |
| LCD 1602 I2C · GND | Bus − de la protoboard | negro |
| LCD 1602 I2C · SDA | ESP32 DevKit v1 · GPIO21 | verde |
| LCD 1602 I2C · SCL | ESP32 DevKit v1 · GPIO22 | naranja |
Avisar cuándo ventilar por temperatura
Fabricar la estructura del invernadero
Para empezar, armamos la maqueta del invernadero. Consiste en una estructura base recubierta por un material translúcido que permita el paso de los rayos solares (por ejemplo, vidrio o polietileno), para que deje entrar la luz y retenga el calor. Además, el invernadero debe contar con alguna abertura que permita ventilarlo cuando la temperatura interna sea demasiado alta: es la que el aviso del LED nos va a decir cuándo abrir.
Esta parte es de fabricación. Los archivos de la maqueta (corte láser en MDF y piezas impresas en 3D para sostener el sensor y el LED) forman parte de los paquetes de fabricación del proyecto, que se descargan desde su ficha. Mientras tanto, la estructura puede construirse con materiales simples: una caja o marco cubierto con film o acetato transparente, con una tapa o ventana que se pueda abrir.
Montar el circuito del LED en la protoboard
Vamos a construir el sistema que avisa cuándo ventilar. Empezamos por el indicador: un LEDLEDComponente electrónico tipo diodo que emite luz. Tiene polaridad: la pata larga es la positiva y la corta (o el lado plano del encapsulado) la negativa. Conectado al revés no se daña, pero no enciende. verde montado en la protoboardProtoboardPlaca experimental para el prototipado rápido de circuitos: sus orificios permiten insertar y conectar componentes sin soldadura. Las líneas superior e inferior recorren la placa de punta a punta y suelen usarse para la alimentación.. Más adelante, ese mismo LED va a parpadear cuando el sensor detecte demasiado calor.
El LED tiene polaridad: la pata larga es la positiva y va, por un cable, al pin de la placa; la pata corta (negativa) va a masa a través de una resistenciaResistenciaComponente que se opone al paso de la corriente eléctrica. Su valor se mide en ohm (Ω). Se usa para determinar la cantidad de corriente de una rama del circuito, por ejemplo para evitar que un LED se queme por exceso de corriente. de 220 Ω, que limita la corriente para que el LED no se dañe. El otro extremo de esa resistencia se conecta a la línea negativa de la protoboard, y esa línea al terminal GND de la placa.
En el Arduino UNO seguimos el esquema original: la pata larga del LED va al pin 13 (la
constante PIN_LED_VENTILACION del código del paso 4). El pin 13 tiene, además, un LED
integrado en la propia placa, útil para probar el aviso aunque todavía no hayas puesto el LED
externo.
En el ESP32 usamos un LED externo conectado al GPIO 16 (la constante
PIN_LED_VENTILACION del código del paso 4). No usamos el LED integrado del DevKit, que está en
el GPIO 2: ese es un strapping pin que interviene en el arranque de la placa y conviene dejarlo
libre. El ESP32 trabaja a 3,3 V, así que la resistencia de 220 Ω es más que suficiente.
La programación la haremos directamente en el lenguaje de Arduino (C++), con 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 encender, y loop(),
que se ejecuta repetidamente después. Todo lo que sigue a // es un comentario: no afecta al
programa, sirve para que el código sea más fácil de leer.
El aviso más simple posible es hacer parpadear el LED: en el código, poner su pin en estado
ALTO (HIGH) lo enciende y ponerlo en BAJO (LOW) lo apaga; intercalando esperas entre uno y
otro, el LED titila. Sobre esta idea básica vamos a construir el programa del paso 4, donde el
parpadeo dejará de ser constante y pasará a depender de la temperatura.
Conectar el sensor de temperatura y humedad (DHT11)
Ahora sumamos el sensor que mide el ambiente: el sensor DHT11Sensor DHT11Sensor digital que mide temperatura y humedad del aire en un mismo integrado. Digitaliza la lectura internamente y la entrega por un pin digital (no es analógico: no va a A0). Necesita una librería para leerlo y no admite más de una lectura por segundo., que registra la temperatura y la humedad del aire.
Un dato importante: el DHT11 es un sensor digital. A diferencia de un sensor analógico (como un higrómetro, que se conecta a una entrada A0–A5), el DHT11 tiene electrónica interna que digitaliza la medición y la transmite a la placa como un paquete de datos por un pin digital. Por eso no va a A0: va a un pin digital, y para leerlo hace falta una librería que gestione esa comunicación (la instalamos en un momento).
El DHT11 tiene tres conexiones útiles: alimentación (VCC y GND) y datos.
En el Arduino UNO seguimos el esquema original: el pin de datos del DHT11 va al pin digital 2
(la constante PIN_DHT del código), VCC al pin 5V y GND a GND.
En el ESP32 la conexión es equivalente, con un cuidado propio de la placa: el pin de datos del
DHT11 va al GPIO 4 (la constante PIN_DHT del código) y la alimentación se toma de 3,3 V
(no de 5 V) y GND. El DHT11 funciona sin problemas a 3,3 V.
Para poder leer el sensor necesitamos instalar su librería. En el Arduino IDE 2.x abrimos el
Gestor de bibliotecas (el ícono de los libritos en la barra lateral), buscamos “DHT sensor
library” de Adafruit y la instalamos; el IDE ofrecerá instalar también su dependencia
“Adafruit Unified Sensor”: aceptá, porque el sensor no funciona sin ella. Esta librería
reemplaza a la vieja DHT.h genérica del original, que ya no se consigue igual.
Programar el aviso de ventilación por temperatura
Con el circuito armado, editamos el programa para que el LED parpadee solo cuando la
temperatura supere un umbral. Ese umbral está en la constante UMBRAL_TEMP_C al comienzo del
código, cargada con el valor del original: 25 °C. En cada vuelta de loop(), el programa lee
la temperatura del DHT11 y, si supera UMBRAL_TEMP_C, hace titilar el LED (medio segundo
encendido, medio segundo apagado); si no la supera, deja el LED apagado.
Hay un detalle del sensor que marca el ritmo del programa: el DHT11 no admite más de una
lectura por segundo. Por eso, entre medición y medición el programa espera con delay() el
tiempo de la constante INTERVALO_MEDICION_MS, que está en 2 segundos (un margen seguro; la
librería de Adafruit, además, guarda la última lectura durante 2 segundos). En este primer nivel
usar delay() es lo más simple y hace que el código se lea igual que su comportamiento; en el
nivel avanzado lo cambiaremos.
Ver el código completo · ventilacion-por-temperatura.ino
// Aviso de ventilación por temperatura: un sensor DHT11 mide la temperatura
// del aire del invernadero y, cuando supera el umbral, un LED PARPADEA para
// avisar que hay que ventilar. Mientras la temperatura esté por debajo del
// umbral, el LED queda apagado.
//
// ¿Por qué parpadea y no queda fijo? Así lo plantea el original: un LED que
// titila llama más la atención que uno encendido de forma continua. En el
// nivel avanzado, ya con pantalla y monitoreo, el aviso evoluciona a
// encendido fijo (encendido = ventilar / apagado = todo en orden).
//
// Plataforma: Arduino UNO.
// Conexión (la del esquema original): dato del DHT11 → pin 2; pata larga
// (positiva) del LED → pin 13, con su resistencia a GND. El pin 13 tiene
// además el LED integrado de la placa, útil para probar sin LED externo.
//
// Basado en: Fragmento de código del Nivel Inicial (Paso 6) — Invernadero
// Inteligente, Saberes Digitales (INET/EDUCAR 2019)
// Modernización: (1) se reemplazó el C generado por mBlock3 (_delay, _loop)
// por C++ idiomático con constantes nombradas. (2) Se usa la librería "DHT
// sensor library" de Adafruit (instalar desde el Library Manager; arrastra
// como dependencia "Adafruit Unified Sensor"), que reemplaza a la vieja
// "DHT.h" genérica del original — ya no se consigue igual. Requiere
// dht.begin() en setup(), que el código viejo no llamaba.
#include "DHT.h"
// Pin de datos del sensor y tipo de sensor (DHT11).
const int PIN_DHT = 2;
#define DHTTYPE DHT11
// LED indicador de "hay que ventilar".
const int PIN_LED_VENTILACION = 13;
// Umbral del original: por encima de 25 °C hay que ventilar.
const float UMBRAL_TEMP_C = 25.0;
// Tiempos del parpadeo del aviso (medio segundo encendido, medio apagado),
// como en el original.
const unsigned long PARPADEO_ENCENDIDO_MS = 500;
const unsigned long PARPADEO_APAGADO_MS = 500;
// El DHT11 no admite más de una lectura por segundo (es su límite físico).
// Medimos cada 2 s: margen seguro, y la librería de Adafruit además cachea
// la lectura durante 2 s. El original pedía 1 s; 2 s es el valor honesto
// para este sensor.
const unsigned long INTERVALO_MEDICION_MS = 2000;
// Creamos el objeto del sensor: pin de datos + tipo.
DHT dht(PIN_DHT, DHTTYPE);
void setup() {
pinMode(PIN_LED_VENTILACION, OUTPUT);
// La librería de Adafruit necesita esta inicialización (la vieja no).
dht.begin();
}
void loop() {
// readTemperature() devuelve la temperatura en grados Celsius.
float temperatura = dht.readTemperature();
if (temperatura > UMBRAL_TEMP_C) {
// Demasiado calor: un parpadeo del LED para avisar que hay que ventilar.
digitalWrite(PIN_LED_VENTILACION, HIGH);
delay(PARPADEO_ENCENDIDO_MS);
digitalWrite(PIN_LED_VENTILACION, LOW);
delay(PARPADEO_APAGADO_MS);
}
// Si la temperatura no supera el umbral, el LED simplemente queda apagado
// (no se toca, y quedó en LOW del parpadeo anterior o del arranque).
// Espera entre mediciones (respeta el límite del DHT11). En este nivel
// usamos delay(): es la herramienta que ya conocemos y hace que el
// programa se lea de arriba hacia abajo igual que su comportamiento.
delay(INTERVALO_MEDICION_MS);
} // Aviso de ventilación por temperatura: un sensor DHT11 mide la temperatura
// del aire del invernadero y, cuando supera el umbral, un LED PARPADEA para
// avisar que hay que ventilar. Mientras la temperatura esté por debajo del
// umbral, el LED queda apagado.
//
// ¿Por qué parpadea y no queda fijo? Así lo plantea el original: un LED que
// titila llama más la atención. En el nivel avanzado el aviso evoluciona a
// encendido fijo.
//
// Plataforma: ESP32 (DevKit v1).
// Conexión: dato del DHT11 → GPIO 4; pata larga (positiva) del LED → GPIO 16,
// con su resistencia a GND.
// Decisión de plataforma: se usa un LED EXTERNO en GPIO 16, no el LED
// integrado del DevKit (GPIO 2). Motivo: el GPIO 2 es un "strapping pin"
// (interviene en el arranque de la placa) y conviene dejarlo libre; además,
// el indicador de "ventilar" se ve mejor como un LED discreto montado en el
// invernadero. Documentado también en el README.
// Nota eléctrica: el ESP32 trabaja a 3,3 V (no 5 V como el UNO); el DHT11
// funciona bien a 3,3 V.
//
// Basado en: Fragmento de código del Nivel Inicial (Paso 6) — Invernadero
// Inteligente, Saberes Digitales (INET/EDUCAR 2019)
// Modernización: (1) C++ idiomático con constantes nombradas en vez del C
// de mBlock3; portado a ESP32. (2) Librería "DHT sensor library" de Adafruit
// (instalar desde el Library Manager con su dependencia "Adafruit Unified
// Sensor"), que reemplaza la vieja "DHT.h" del original y requiere
// dht.begin() en setup().
#include "DHT.h"
// Pin de datos del sensor y tipo de sensor (DHT11).
const int PIN_DHT = 4;
#define DHTTYPE DHT11
// LED externo indicador de "hay que ventilar".
const int PIN_LED_VENTILACION = 16;
// Umbral del original: por encima de 25 °C hay que ventilar.
const float UMBRAL_TEMP_C = 25.0;
// Tiempos del parpadeo del aviso, como en el original.
const unsigned long PARPADEO_ENCENDIDO_MS = 500;
const unsigned long PARPADEO_APAGADO_MS = 500;
// El DHT11 no admite más de una lectura por segundo; medimos cada 2 s
// (margen seguro; la librería de Adafruit además cachea 2 s).
const unsigned long INTERVALO_MEDICION_MS = 2000;
// Objeto del sensor: pin de datos + tipo.
DHT dht(PIN_DHT, DHTTYPE);
void setup() {
pinMode(PIN_LED_VENTILACION, OUTPUT);
dht.begin();
}
void loop() {
// readTemperature() devuelve la temperatura en grados Celsius.
float temperatura = dht.readTemperature();
if (temperatura > UMBRAL_TEMP_C) {
// Demasiado calor: un parpadeo del LED para avisar que hay que ventilar.
digitalWrite(PIN_LED_VENTILACION, HIGH);
delay(PARPADEO_ENCENDIDO_MS);
digitalWrite(PIN_LED_VENTILACION, LOW);
delay(PARPADEO_APAGADO_MS);
}
// Si la temperatura no supera el umbral, el LED queda apagado.
// Espera entre mediciones (respeta el límite del DHT11).
delay(INTERVALO_MEDICION_MS);
} Podés ver el comportamiento —la temperatura subiendo y el LED que empieza a parpadear al pasar el umbral— en el simulador, antes de armar el circuito 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 puertoPuerto COMPuerto de comunicaciones por el cual el sistema operativo se comunica con un dispositivo externo, como la placa. Se asigna automáticamente al conectarla por USB y puede cambiar de número; en el IDE hay que indicar cuál usar. (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.
Para el aula
El material original de 2019 enseñaba este paso con bloques en mBlock3, hoy discontinuado. La función didáctica de los bloques —bajar la barrera de entrada— la cumple acá la combinación de un código corto con constantes nombradas, comentado, y el simulador en el navegador. Buen disparador para el aula: el original hace parpadear el LED (no lo deja fijo) a propósito, porque un titileo llama más la atención que una luz encendida. Que los grupos predigan qué se vería si, en lugar de parpadear, el LED quedara encendido de forma continua… y guarden la hipótesis, porque en el nivel avanzado el aviso justamente evoluciona a encendido fijo.
Verificá
Con el programa cargado, acercá al sensor una fuente de calor suave (la mano ahuecada, el aliento) para que la temperatura supere los 25 °C: el LED tiene que empezar a parpadear. Al retirar el calor y dejar que la lectura baje, el LED tiene que apagarse. Si el LED no responde, revisá la polaridad (pata larga al pin) y que el pin de datos del DHT11 esté en el pin correcto. Como el sensor mide cada 2 segundos, puede haber una pequeña demora entre el cambio de temperatura y la reacción del LED: es lo esperado.
Cierre del nivel
Al terminar este nivel, tu invernadero ya avisa solo: el sensor DHT11 mide la temperatura del aire y el LED parpadea cuando hace demasiado calor, indicando que hay que ventilar. Además, ya sabés conectar un sensor digital, instalar su librería y subir programas desde el Arduino IDE 2.x. Pero el aviso solo dice “hace calor / no hace calor”: no cuánto. En el nivel intermedio vamos a agregar un display LCD para ver en pantalla los valores exactos de temperatura y de humedad que mide el sensor.
Evidencia de cierre
Al terminar este nivel, esto tiene que estar funcionando:
- La maqueta del invernadero está armada (estructura cubierta con material translúcido y con una abertura para ventilar) y el circuito montado en la protoboard: el LED verde con su resistencia y el sensor DHT11 conectado a su pin de datos.
- 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 sensor DHT11 mide la temperatura del aire y el LED parpadea cuando esa temperatura supera UMBRAL_TEMP_C (25 °C), avisando que hay que ventilar.
- Entendés que el DHT11 es un sensor digital (se conecta a un pin digital, no a A0) y que no admite más de una lectura por segundo.
Ver temperatura y humedad en un display LCD
Situación problema
Ahora, además de contar con un LED que indica cuándo es necesario ventilar el invernadero, se desea poder visualizar en una pantalla cuál es la temperatura de su interior en cada momento. En esa pantalla se puede incluir también el nivel de humedad del ambiente, que el sensor DHT11 mide en simultáneo.
En esta segunda instancia del proyecto se propone agregar un display LCD que muestre la temperatura y la humedad ambiente del invernadero que mide el sensor DHT11.
Conectar el display LCD
Vamos a sumar un display LCDDisplay LCDPantalla de cristal líquido que muestra texto en filas y columnas; el clásico es de 16 columnas por 2 filas. Se puede conectar en modo paralelo (muchos pines) o por I2C (solo dos cables, SDA y SCL). No representa bien las tildes ni la letra Ñ. de 16 columnas por 2 filas para ver en pantalla lo que mide el sensor. Acá aparece la primera diferencia real entre las dos plataformas de este proyecto, y vale la pena entenderla, porque es muy didáctica: el display se conecta de una manera distinta en cada una.
En el Arduino UNO usamos el LCD Keypad Shield del original, un shieldShieldPlaca modular que se monta encimada sobre una placa Arduino, encastrando en sus pines, para agregarle funciones (pantalla, sensores, comunicación). Suele diseñarse para un modelo concreto: el LCD Keypad Shield tiene el formato del Arduino UNO y no encaja en un ESP32. que tiene el formato de la placa y se apila directamente encima del UNO, encastrando en sus pines: no hay que cablear nada en la protoboard. Al apilarlo, el display queda conectado en modo paralelo, usando seis pines de datos de la placa (los pines 8, 9, 4, 5, 6 y 7). Es importante no forzar las piezas: todos los pines deben coincidir perfectamente. Como el shield ocupa varios pines, el LED y el sensor DHT11 se cablean a los pines que quedan libres, tal como veníamos.
El LCD Keypad Shield tiene el formato del Arduino UNO y no se enchufa en un ESP32. Por eso, en el ESP32 usamos un LCD 1602 con módulo I2C: la misma pantalla de 16x2, pero con una plaquita (el módulo I2C) pegada atrás que reduce el cableado a solo dos cables de señal. En lugar de los seis pines del modo paralelo, conectamos: VCC a 3,3 V, GND a GND, SDA al GPIO 21 y SCL al GPIO 22 (los pines I2C por defecto del ESP32). Esta es una adaptación de plataforma pensada a propósito: hace lo mismo que el shield con muchísimo menos cableado.
Instalar la librería y probar el display con un saludo
Para manejar el display necesitamos su librería, y cada plataforma usa una distinta, en correspondencia con la forma en que conectamos la pantalla en el paso anterior.
En el Arduino UNO, el shield en modo paralelo se maneja con la librería LiquidCrystal, que
ya viene incluida con el Arduino IDE (no hay que instalar nada). En el código, el display se
declara indicándole los seis pines que usa: LiquidCrystal lcd(8, 9, 4, 5, 6, 7).
En el ESP32, el LCD por I2C se maneja con la librería LiquidCrystal_I2C, que se instala
desde el Gestor de bibliotecas del IDE (buscá “LiquidCrystal_I2C”). En el código, el display
se declara con su dirección I2C —típicamente 0x27— y su tamaño: LiquidCrystal_I2C lcd(0x27, 16, 2). Si al probar la pantalla no muestra nada, es posible que tu módulo use la dirección
0x3F: probá con ese valor.
Para familiarizarnos con el display, un primer programa de prueba puede escribir un saludo:
tras inicializar la pantalla, con lcd.print("Buen dia") aparece ese texto en la primera fila.
Un detalle a tener siempre presente: el LCD no representa bien las tildes ni la letra Ñ, así
que los textos se escriben sin ellas (por eso “Buen dia”, y más adelante “Temp.” y “Hum.”).
Si en el UNO el saludo no se lee (pantalla en blanco o todo encendido), ajustá el contraste con el potenciómetro azul (RP1) que está en la esquina superior izquierda del shield, usando un destornillador chico.
Mostrar la temperatura y la humedad en el display
Ahora volvemos al sistema completo: el sensor DHT11 y el LED de aviso siguen conectados como en el
nivel inicial, y le sumamos el display. Aprovechamos que el LCD tiene dos filas para escribir
la temperatura en la primera y la humedad en la segunda, cada una precedida por un rótulo
(Temp.: y Hum.:) para que se lean con claridad.
El programa toma lo del nivel inicial y le agrega el display: en cada vuelta lee una sola vez
la temperatura y la humedad del DHT11, con esos valores decide el aviso (el LED parpadea si la
temperatura supera UMBRAL_TEMP_C, igual que antes) y los escribe en el LCD. Sigue midiendo cada
INTERVALO_MEDICION_MS (2 segundos), respetando el límite del sensor.
Ver el código completo · temp-humedad-lcd.ino
// Invernadero con display: el sensor DHT11 mide temperatura y humedad del
// aire, un LCD 16x2 las muestra en pantalla, y el LED de ventilación
// PARPADEA cuando la temperatura supera el umbral (igual que en el nivel
// inicial). Ahora, además del aviso, vemos los números exactos.
//
// Plataforma: Arduino UNO.
// Conexión (la del esquema original): dato del DHT11 → pin 2; LED → pin 13;
// display LCD Keypad Shield en modo paralelo por los pines 8, 9, 4, 5, 6, 7.
// El shield se apila directamente sobre el UNO; su potenciómetro RP1 ajusta
// el contraste si no se lee el texto.
//
// Nota del original: en el LCD no usar tildes ni la letra Ñ (el display no
// las representa bien). Por eso los rótulos van como "Temp." y "Hum.".
//
// Basado en: Fragmento de código del Nivel Intermedio (Paso 5) — Invernadero
// Inteligente, Saberes Digitales (INET/EDUCAR 2019)
// Modernización: (1) constantes nombradas en vez del C de mBlock3. (2)
// Librería DHT de Adafruit (ver nivel inicial), con dht.begin(). (3) El
// original llamaba a readTemperature() y readHumidity() varias veces por
// vuelta; acá se leen UNA vez a variables y esos mismos valores se usan
// para el LED y para el display.
#include "DHT.h"
#include <LiquidCrystal.h>
// Pin de datos del sensor y tipo de sensor (DHT11).
const int PIN_DHT = 2;
#define DHTTYPE DHT11
// LED indicador de "hay que ventilar".
const int PIN_LED_VENTILACION = 13;
// Umbral del original: por encima de 25 °C hay que ventilar.
const float UMBRAL_TEMP_C = 25.0;
// Tiempos del parpadeo del aviso, como en el original.
const unsigned long PARPADEO_ENCENDIDO_MS = 500;
const unsigned long PARPADEO_APAGADO_MS = 500;
// El DHT11 no admite más de una lectura por segundo; medimos cada 2 s.
const unsigned long INTERVALO_MEDICION_MS = 2000;
// Objeto del sensor: pin de datos + tipo.
DHT dht(PIN_DHT, DHTTYPE);
// Display LCD en modo paralelo. El orden de pines es el del LCD Keypad
// Shield: (RS, E, D4, D5, D6, D7) = (8, 9, 4, 5, 6, 7).
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
void setup() {
pinMode(PIN_LED_VENTILACION, OUTPUT);
dht.begin();
// Inicializamos el display de 16 columnas por 2 filas y lo limpiamos.
lcd.begin(16, 2);
lcd.clear();
}
void loop() {
// Leemos una sola vez y reutilizamos los valores. readTemperature() da
// grados Celsius; readHumidity() da humedad relativa en %.
float temperatura = dht.readTemperature();
float humedad = dht.readHumidity();
if (temperatura > UMBRAL_TEMP_C) {
// Demasiado calor: un parpadeo del LED para avisar que hay que ventilar.
digitalWrite(PIN_LED_VENTILACION, HIGH);
delay(PARPADEO_ENCENDIDO_MS);
digitalWrite(PIN_LED_VENTILACION, LOW);
delay(PARPADEO_APAGADO_MS);
}
// Fila 0: temperatura. Fila 1: humedad. Sin tildes ni Ñ (limitación LCD).
lcd.setCursor(0, 0);
lcd.print("Temp.: ");
lcd.print(temperatura);
lcd.setCursor(0, 1);
lcd.print("Hum.: ");
lcd.print(humedad);
delay(INTERVALO_MEDICION_MS);
} // Invernadero con display: el sensor DHT11 mide temperatura y humedad del
// aire, un LCD 16x2 las muestra en pantalla, y el LED de ventilación
// PARPADEA cuando la temperatura supera el umbral (igual que en el inicial).
//
// Plataforma: ESP32 (DevKit v1).
// Conexión: dato del DHT11 → GPIO 4; LED externo → GPIO 16; display LCD 1602
// por I2C: SDA → GPIO 21, SCL → GPIO 22 (los pines I2C por defecto del ESP32).
//
// ADAPTACIÓN DE PLATAFORMA (¡importante!, ver README): el original usa un
// LCD Keypad Shield, una placa con formato Arduino UNO que se apila sobre la
// placa y se maneja con LiquidCrystal en modo PARALELO (6 pines de datos:
// 8, 9, 4, 5, 6, 7). Ese shield NO se enchufa en un ESP32. Por eso en ESP32
// usamos un LCD 1602 con módulo I2C: solo DOS cables de señal (SDA y SCL) en
// lugar de seis, y la librería LiquidCrystal_I2C en lugar de LiquidCrystal.
// Es el estándar moderno y evita el problema del shield. La dirección I2C
// típica de estos módulos es 0x27 (algunos son 0x3F: si la pantalla no
// muestra nada, probá 0x3F o escaneá el bus I2C).
// Nota eléctrica: el ESP32 trabaja a 3,3 V; DHT11 y el módulo I2C del LCD
// funcionan a 3,3 V.
//
// Nota del original: en el LCD no usar tildes ni la letra Ñ.
//
// Librerías requeridas (Library Manager): "DHT sensor library" (Adafruit) +
// su dependencia "Adafruit Unified Sensor", y "LiquidCrystal_I2C".
//
// Basado en: Fragmento de código del Nivel Intermedio (Paso 5) — Invernadero
// Inteligente, Saberes Digitales (INET/EDUCAR 2019)
// Modernización: constantes nombradas; DHT de Adafruit con dht.begin(); LCD
// paralelo del original adaptado a I2C para ESP32; una sola lectura por
// vuelta reutilizada para LED y display.
#include "DHT.h"
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// Pin de datos del sensor y tipo de sensor (DHT11).
const int PIN_DHT = 4;
#define DHTTYPE DHT11
// LED externo indicador de "hay que ventilar".
const int PIN_LED_VENTILACION = 16;
// Dirección I2C y tamaño del display (16 columnas x 2 filas).
const uint8_t LCD_DIRECCION_I2C = 0x27;
const uint8_t LCD_COLUMNAS = 16;
const uint8_t LCD_FILAS = 2;
// Umbral del original: por encima de 25 °C hay que ventilar.
const float UMBRAL_TEMP_C = 25.0;
// Tiempos del parpadeo del aviso, como en el original.
const unsigned long PARPADEO_ENCENDIDO_MS = 500;
const unsigned long PARPADEO_APAGADO_MS = 500;
// El DHT11 no admite más de una lectura por segundo; medimos cada 2 s.
const unsigned long INTERVALO_MEDICION_MS = 2000;
// Objeto del sensor y display por I2C.
DHT dht(PIN_DHT, DHTTYPE);
LiquidCrystal_I2C lcd(LCD_DIRECCION_I2C, LCD_COLUMNAS, LCD_FILAS);
void setup() {
pinMode(PIN_LED_VENTILACION, OUTPUT);
dht.begin();
// Con I2C, primero arranca el bus y el display; backlight enciende la luz.
lcd.init();
lcd.backlight();
lcd.clear();
}
void loop() {
// Leemos una sola vez y reutilizamos los valores.
float temperatura = dht.readTemperature(); // grados Celsius
float humedad = dht.readHumidity(); // humedad relativa en %
if (temperatura > UMBRAL_TEMP_C) {
// Demasiado calor: un parpadeo del LED para avisar que hay que ventilar.
digitalWrite(PIN_LED_VENTILACION, HIGH);
delay(PARPADEO_ENCENDIDO_MS);
digitalWrite(PIN_LED_VENTILACION, LOW);
delay(PARPADEO_APAGADO_MS);
}
// Fila 0: temperatura. Fila 1: humedad. Sin tildes ni Ñ (limitación LCD).
lcd.setCursor(0, 0);
lcd.print("Temp.: ");
lcd.print(temperatura);
lcd.setCursor(0, 1);
lcd.print("Hum.: ");
lcd.print(humedad);
delay(INTERVALO_MEDICION_MS);
} Fijate, al comparar las dos plataformas con el selector, que el corazón del programa es idéntico: cambia solo la parte del display (qué librería se incluye y cómo se declara e inicializa la pantalla). Todo lo demás —leer el sensor, decidir el aviso, los rótulos— es el mismo código.
Podés ver el sistema completo —el sensor midiendo, el display actualizándose y el LED avisando— 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.
Para el aula
Este nivel es una gran oportunidad para hacer visible una idea de fondo: el mismo problema se resuelve distinto según el hardware. En el UNO, el shield paralelo es cómodo (se apila, sin cables) pero ocupa muchos pines y solo entra en placas con ese formato; en el ESP32, el I2C usa dos cables y una dirección, y sirve para casi cualquier placa. Ninguno es “el correcto”: son respuestas distintas a la misma necesidad. Comparar los dos códigos lado a lado con el selector, señalando qué líneas cambian y cuáles no, deja clarísimo qué parte del programa depende del periférico y qué parte es la lógica del proyecto.
Verificá
Con el programa cargado, el display tiene que mostrar Temp.: con el valor en la primera fila y
Hum.: con el valor en la segunda, actualizándose cada 2 segundos. Al acercar calor al sensor y
superar los 25 °C, el número de temperatura tiene que subir y el LED empezar a parpadear. Si la
pantalla se ve en blanco, revisá el contraste (RP1 en el UNO) o la dirección I2C (0x27 o 0x3F
en el ESP32).
Cierre del nivel
Al terminar este nivel, el invernadero no solo avisa que hace calor: muestra en pantalla los valores exactos de temperatura y humedad del aire. En el camino apareció una diferencia real y muy formativa entre las plataformas —el shield paralelo del UNO frente al LCD por I2C del ESP32— y viste que la lógica del proyecto no cambia, solo la manera de conectar la pantalla. En el nivel avanzado vamos a poder mirar todo esto a distancia: publicaremos la temperatura y la humedad por Internet de las Cosas para monitorear el invernadero desde el celular.
Evidencia de cierre
Al terminar este nivel, esto tiene que estar funcionando:
- El display LCD está conectado: en el UNO, el LCD Keypad Shield apilado sobre la placa (modo paralelo, 6 pines); en el ESP32, un LCD 1602 por I2C, con solo dos cables de señal (SDA y SCL).
- Instalaste la librería correcta según tu plataforma (LiquidCrystal, que ya viene con el IDE, en el UNO; LiquidCrystal_I2C en el ESP32) y probaste el display con un saludo, sin tildes ni Ñ.
- El display muestra la temperatura en la primera fila y la humedad en la segunda, y el LED sigue avisando (parpadea) cuando la temperatura supera UMBRAL_TEMP_C (25 °C).
Monitorear el invernadero con Internet de las Cosas
Situación problema
Se desea monitorear, desde una central, las condiciones internas de humedad y temperatura del invernadero. Para ello se decide instalar un sistema de IoT, que permite enviar por internet la información que recoge el sensor y visualizarla a distancia, sin ir hasta el invernadero.
En esta tercera instancia se programará el envío de los datos obtenidos a un dispositivo móvil, a través de Internet de las Cosas (IoT).
Introducción a Internet de las Cosas (IoT)
Internet de las Cosas (en inglés Internet of Things, abreviado IoTIoT (Internet de las Cosas)Interconexión digital de objetos cotidianos con internet. Permite, por ejemplo, enviar los datos de un sensor a una central y visualizarlos desde un dispositivo móvil. Requiere un dispositivo capaz de conectarse a internet y un servidor que reciba y aloje los datos.) es un concepto que refiere a la interconexión digital de objetos cotidianos con internet. Por ejemplo, monitorear la temperatura de un ambiente enviando los datos de un sensor a una central donde se recopile la información, para visualizarla desde un dispositivo móvil: la temperatura de un laboratorio, de un invernadero o de una sala de un hospital.
Para poder incorporar IoT a nuestro proyecto es necesario:
- Un dispositivo capaz de conectarse a internet.
- Un servidor que reciba y aloje los datos.
Existen diversas formas de registrar y almacenar los datos del invernadero. En este caso, se detallará cómo hacerlo con el WiFi integrado de la placa ESP32ESP32Placa con un microcontrolador de Espressif que trae WiFi (y Bluetooth) integrados. Se programa desde el Arduino IDE igual que un Arduino, con pines llamados GPIO. En este proyecto reemplaza al conjunto Arduino UNO + módulo OBLOQ para el nivel IoT. y con los servidores de Adafruit IOAdafruit IOPlataforma online de uso gratuito, orientada al uso educativo, que centraliza y gestiona la información que reportan los dispositivos IoT. Permite crear feeds y paneles de control para monitorear los datos desde cualquier dispositivo..
El material original de 2019 resolvía este punto agregando al Arduino UNO un módulo externo llamado OBLOQ, un adaptador WiFi-serie de DFRobot hoy discontinuado (igual que su biblioteca ObloqAdafruit). Como el UNO clásico no tiene conectividad propia, en esta reedición la práctica de IoT se realiza con la variante ESP32 del proyecto: podés seguir este nivel de manera conceptual con tu UNO (el paso 3, sobre código no bloqueante, funciona perfectamente en él) y cambiar al ESP32 con el selector de plataforma para la parte práctica de conexión, que es idéntica en lo didáctico y más simple en el circuito.
El ESP32 cumple los dos roles a la vez: es el microcontrolador que ya está midiendo la temperatura y la humedad y manejando el aviso, y, además, trae radio WiFi integrada. No hace falta ningún módulo adicional ni cableado extra: la conexión a internet se resuelve por completo desde el código.
Crear un panel de control en Adafruit IO
Primero se explicará cómo crear un panel de control en Adafruit IOAdafruit IOPlataforma online de uso gratuito, orientada al uso educativo, que centraliza y gestiona la información que reportan los dispositivos IoT. Permite crear feeds y paneles de control para monitorear los datos desde cualquier dispositivo., una plataforma online con posibilidad de uso gratuito, orientada al uso educativo, que centraliza la información que reportan los dispositivos. Empezamos creando una cuenta de usuario en io.adafruit.com.
Una vez que ingresamos, el trabajo tiene dos partes: crear los feeds donde se publicarán los datos y armar el panel que los muestre.
Un feedFeedFuente de datos en la que se puede publicar y a la que uno puede suscribirse. Permite enviar datos para que se almacenen en el tiempo, y también leerlos recibiendo las actualizaciones de quienes publican allí. es una fuente de datos en la que uno puede publicar, así como también suscribirse para recibir sus datos. Desde la sección Feeds creamos dos, con estos nombres exactos (sin tildes, porque forman parte de la dirección con la que se publica):
temperatura: recibirá el valor de temperatura en grados Celsius.humedad: recibirá el valor de humedad relativa del aire, en porcentaje.
Luego, desde la sección Dashboards, creamos un nuevo panel con el nombre y la descripción que quieras. Dentro del panel agregamos, tal como propone el original, dos bloques del tipo Line Chart (gráfico de línea), uno por feed: nos permiten ver el historial de los cambios en la temperatura y en la humedad a lo largo del tiempo. Cada bloque permite elegir un título y sus colores, y luego se puede ajustar su tamaño y ubicación. Al finalizar, el panel replicará a distancia la evolución de las condiciones del invernadero.
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.
Verificá
Para probar el panel sin la placa, entrá al feed temperatura y agregale valores a mano (Add
Data): el gráfico de línea tiene que ir dibujando esos puntos. Hacé lo mismo con humedad. Si un
bloque no reacciona, revisá que esté asociado al feed correcto.
Programar sin código bloqueante
Antes de usar IoT debemos hacer una aclaración sobre la función delay() que venimos usando
(nuestro programa espera unos segundos entre medición y medición). Esta función brinda un tiempo de
espera al sistema y suele usarse bastante en las primeras aproximaciones a la programación, porque
su comportamiento es fácil de comprender y no requiere más que una línea de código.
Sin embargo, delay() genera un código bloqueanteCódigo bloqueanteCódigo que detiene todo el procesamiento hasta que se cumple una condición, como delay(), que deja al programa 'colgado' durante el tiempo indicado. En IoT es conflictivo: impide atender tareas simultáneas como la publicación de datos y la conexión a internet.: cuando el
programa entra en esa función, se detiene todo el procesamiento hasta que se cumple el tiempo
indicado. En otras palabras, el programa queda “colgado” durante ese período.
Al utilizar IoT, el código bloqueante es un problema: mientras el procesamiento está detenido, el sistema no puede realizar otras operaciones que funcionan en simultáneo, como las tareas de publicación y el mantenimiento constante de la conexión a internet.
Para evitarlo, usamos una alternativa no bloqueante, la función millis(). Esta función
devuelve un conteo de tiempo (en milisegundos) desde que se inició el sistema: funciona como un
cronómetro que, cada vez que se lo consulta, informa en cuánto va. Con eso podemos preguntarle al
sistema cuánto tiempo pasó desde la última medición, sin detener todo lo demás.
La receta tiene tres ingredientes, que vas a reconocer en el código: una variable millisAnterior,
que guarda cuándo fue la última medición (arranca en cero); la consulta del valor actual con
millis() en cada vuelta de loop(); y la comparación entre ambos contra el
INTERVALO_MEDICION_MS. Si ya pasó el intervalo, el programa mide y actualiza, 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.
Este programa introduce, además, un cambio en el aviso: el LED deja de parpadear y pasa a
quedar encendido fijo mientras la temperatura supera el umbral (y apagado cuando está por
debajo), resuelto con un if/else. Es exactamente como evoluciona el original al llegar a este
nivel: con pantalla y monitoreo, un estado estable se lee mejor que un titileo.
Ver el código completo · invernadero-no-bloqueante.ino
// Invernadero SIN delay(): el DHT11 mide temperatura y humedad, el LCD 16x2
// las muestra y el LED de ventilación queda ENCENDIDO FIJO mientras la
// temperatura supera el umbral (apagado si está por debajo). Todo con
// millis() en lugar de delay(): cada vuelta de loop() pregunta si ya pasó
// el intervalo y, si no, deja el procesador libre.
//
// Cambio de comportamiento del LED respecto a los niveles anteriores: acá
// el aviso pasa de parpadeo a encendido fijo, tal como evoluciona el
// original al llegar al nivel avanzado (con pantalla y monitoreo, un estado
// estable se lee mejor que un titileo). Esta es además la base directa de
// invernadero-iot.ino: sin código bloqueante, el ESP32 puede sumar WiFi y
// MQTT sin desatender el sensor.
//
// Plataforma: Arduino UNO.
// Conexión (la del esquema original): dato del DHT11 → pin 2; LED → pin 13;
// display LCD Keypad Shield en modo paralelo por los pines 8, 9, 4, 5, 6, 7.
//
// Nota del original: en el LCD no usar tildes ni la letra Ñ.
//
// Basado en: Fragmentos de código del Nivel Superior (Paso 5, patrón millis,
// con LED encendido/apagado por if/else y sin la parte IoT) — Invernadero
// Inteligente, Saberes Digitales (INET/EDUCAR 2019)
// Modernización: constantes nombradas; millisAnterior como unsigned long
// (el long del original desborda la cuenta); librería DHT de Adafruit con
// dht.begin(); una sola lectura por ciclo reutilizada para LED y display.
#include "DHT.h"
#include <LiquidCrystal.h>
// Pin de datos del sensor y tipo de sensor (DHT11).
const int PIN_DHT = 2;
#define DHTTYPE DHT11
// LED indicador de "hay que ventilar".
const int PIN_LED_VENTILACION = 13;
// Umbral del original: por encima de 25 °C hay que ventilar.
const float UMBRAL_TEMP_C = 25.0;
// El DHT11 no admite más de una lectura por segundo; medimos cada 2 s.
const unsigned long INTERVALO_MEDICION_MS = 2000;
// Objeto del sensor y display paralelo (RS, E, D4, D5, D6, D7).
DHT dht(PIN_DHT, DHTTYPE);
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
// Momento (ms desde el arranque) de la última medición.
unsigned long millisAnterior = 0;
void setup() {
pinMode(PIN_LED_VENTILACION, OUTPUT);
dht.begin();
lcd.begin(16, 2);
lcd.clear();
}
void loop() {
unsigned long millisActual = millis();
// ¿Ya pasó el intervalo desde la última medición? Si no, no hacemos nada
// y loop() vuelve a empezar: eso es NO bloquear.
if (millisActual - millisAnterior >= INTERVALO_MEDICION_MS) {
millisAnterior = millisActual;
float temperatura = dht.readTemperature(); // grados Celsius
float humedad = dht.readHumidity(); // humedad relativa en %
// Aviso de ventilación como encendido fijo (if/else): encendido = hay
// que ventilar, apagado = temperatura en orden.
if (temperatura > UMBRAL_TEMP_C) {
digitalWrite(PIN_LED_VENTILACION, HIGH);
} else {
digitalWrite(PIN_LED_VENTILACION, LOW);
}
// Fila 0: temperatura. Fila 1: humedad. Sin tildes ni Ñ (limitación LCD).
lcd.setCursor(0, 0);
lcd.print("Temp.: ");
lcd.print(temperatura);
lcd.setCursor(0, 1);
lcd.print("Hum.: ");
lcd.print(humedad);
}
// En este punto el procesador queda libre para realizar otras tareas.
} // Invernadero SIN delay(): el DHT11 mide temperatura y humedad, el LCD 16x2
// (I2C) las muestra y el LED de ventilación queda ENCENDIDO FIJO mientras la
// temperatura supera el umbral (apagado si está por debajo). Todo con
// millis(): cada vuelta de loop() pregunta si ya pasó el intervalo y, si no,
// deja el procesador libre.
//
// El aviso pasa de parpadeo (niveles anteriores) a encendido fijo, como
// evoluciona el original. Este programa es la base directa de
// invernadero-iot.ino: sin código bloqueante, el ESP32 puede sumar WiFi y
// MQTT sin desatender el sensor.
//
// Plataforma: ESP32 (DevKit v1).
// Conexión: dato del DHT11 → GPIO 4; LED externo → GPIO 16; display LCD 1602
// por I2C: SDA → GPIO 21, SCL → GPIO 22.
//
// ADAPTACIÓN DE PLATAFORMA (ver README): el original usa un LCD Keypad
// Shield paralelo (6 pines) que no entra en un ESP32; acá usamos un LCD 1602
// con módulo I2C (2 cables, librería LiquidCrystal_I2C, dirección típica
// 0x27; probá 0x3F si no muestra nada).
// Nota eléctrica: el ESP32 trabaja a 3,3 V.
//
// Nota del original: en el LCD no usar tildes ni la letra Ñ.
//
// Librerías requeridas (Library Manager): "DHT sensor library" (Adafruit) +
// "Adafruit Unified Sensor", y "LiquidCrystal_I2C".
//
// Basado en: Fragmentos de código del Nivel Superior (Paso 5, patrón millis,
// LED por if/else, sin la parte IoT) — Invernadero Inteligente, Saberes
// Digitales (INET/EDUCAR 2019)
// Modernización: constantes nombradas; millisAnterior como unsigned long;
// DHT de Adafruit con dht.begin(); LCD paralelo del original adaptado a I2C;
// una sola lectura por ciclo reutilizada para LED y display.
#include "DHT.h"
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// Pin de datos del sensor y tipo de sensor (DHT11).
const int PIN_DHT = 4;
#define DHTTYPE DHT11
// LED externo indicador de "hay que ventilar".
const int PIN_LED_VENTILACION = 16;
// Dirección I2C y tamaño del display (16 columnas x 2 filas).
const uint8_t LCD_DIRECCION_I2C = 0x27;
const uint8_t LCD_COLUMNAS = 16;
const uint8_t LCD_FILAS = 2;
// Umbral del original: por encima de 25 °C hay que ventilar.
const float UMBRAL_TEMP_C = 25.0;
// El DHT11 no admite más de una lectura por segundo; medimos cada 2 s.
const unsigned long INTERVALO_MEDICION_MS = 2000;
// Objeto del sensor y display por I2C.
DHT dht(PIN_DHT, DHTTYPE);
LiquidCrystal_I2C lcd(LCD_DIRECCION_I2C, LCD_COLUMNAS, LCD_FILAS);
// Momento (ms desde el arranque) de la última medición.
unsigned long millisAnterior = 0;
void setup() {
pinMode(PIN_LED_VENTILACION, OUTPUT);
dht.begin();
lcd.init();
lcd.backlight();
lcd.clear();
}
void loop() {
unsigned long millisActual = millis();
// ¿Ya pasó el intervalo desde la última medición? Si no, no hacemos nada
// y loop() vuelve a empezar: eso es NO bloquear.
if (millisActual - millisAnterior >= INTERVALO_MEDICION_MS) {
millisAnterior = millisActual;
float temperatura = dht.readTemperature(); // grados Celsius
float humedad = dht.readHumidity(); // humedad relativa en %
// Aviso de ventilación como encendido fijo (if/else).
if (temperatura > UMBRAL_TEMP_C) {
digitalWrite(PIN_LED_VENTILACION, HIGH);
} else {
digitalWrite(PIN_LED_VENTILACION, LOW);
}
// Fila 0: temperatura. Fila 1: humedad. Sin tildes ni Ñ (limitación LCD).
lcd.setCursor(0, 0);
lcd.print("Temp.: ");
lcd.print(temperatura);
lcd.setCursor(0, 1);
lcd.print("Hum.: ");
lcd.print(humedad);
}
// En este punto el procesador queda libre para realizar otras tareas
// (en invernadero-iot.ino, acá se atiende la conexión WiFi/MQTT).
} Teniendo un programa con código no bloqueante (es decir, sin delay()) estamos en condiciones de
incorporar IoT.
Para el aula
Este es el concepto más transferible del nivel: la diferencia entre esperar deteniendo el mundo
y consultar un reloj sin detenerlo. Funciona muy bien teatralizarlo: un estudiante
“bloqueante” que se congela contando sin poder responder nada, contra uno “no bloqueante” que sigue
atendiendo consultas y cada tanto mira el reloj de la pared. Cuando el grupo puede predecir cuál de
los dos puede, además, “atender el teléfono de internet”, el paso 4 se explica solo. Si el curso ya
hizo el proyecto de Sistema de Riego o el de Tanques, este millis() es un reencuentro.
Verificá
Cargá el programa: el sistema tiene que comportarse igual que en el nivel intermedio en cuanto a la
medición —el display muestra temperatura y humedad cada 2 segundos—, con un solo cambio visible: el
LED, en vez de parpadear, queda encendido fijo mientras la temperatura supera los 25 °C y se
apaga cuando baja. Que por fuera se vea casi idéntico y por dentro ya no haya delay() es,
justamente, el punto de este paso.
Programar IoT
Con el panel listo y el código ya libre de bloqueos, falta el último eslabón: que el sistema publique la temperatura y la humedad en Adafruit IO.
Esta parte práctica corresponde a la variante ESP32: el Arduino UNO clásico no tiene WiFi (el original lo resolvía con el módulo OBLOQ, hoy discontinuado). Cambiá la plataforma a ESP32 con el selector para ver el código y las instrucciones de conexión. Todo lo conceptual —qué se publica en cada feed, el porqué del código no bloqueante— ya lo tenés resuelto con tu UNO en los pasos anteriores.
El programa final integra tres cosas que ya conocés: el control no bloqueante del paso 3, la conexión WiFi nativa del ESP32 y la publicación en Adafruit IO (mediante el protocolo MQTT, usando la Adafruit MQTT Library, que se instala desde el gestor de bibliotecas del Arduino IDE; reemplaza a la biblioteca ObloqAdafruit del original, discontinuada junto con su módulo).
Antes de subirlo, tenés que completar cuatro datos al comienzo del código: el nombre (SSID) y la contraseña de tu red WiFi, y el usuario y la clave que copiaste de Adafruit IO en el paso 2.
La publicación se engancha en el tiempo que el paso 3 le dejó libre al procesador: en cada vuelta
de loop(), además de medir y actualizar el display cuando toca, el programa mantiene viva la
conexión con el servidor y publica los cambios. Para no gastar el cupo de la cuenta gratuita de
Adafruit IO (30 publicaciones por minuto), un valor se re-publica solo si cambió lo suficiente
respecto del último enviado: al menos 0,5 °C la temperatura y al menos 1 % la humedad. El original
publicaba en cada medición; como el DHT11 tiene resolución de 1 °C y 1 %, en la práctica se publica
ante cualquier cambio real de la medición (el desvío está documentado en la ficha). Por esto es
importante que el código no sea bloqueante: si el programa quedara colgado en un delay(), la
conexión no podría atenderse y las publicaciones se perderían. Bonus del mismo diseño: si internet
se cae, el aviso y el display siguen funcionando, y al volver la conexión el panel se pone al día
solo.
Ver el código completo · invernadero-iot.ino
Nota: Este código está disponible para ESP32. Cambiá la plataforma con el selector del encabezado para verlo y practicarlo.
// Invernadero con monitoreo IoT vía Adafruit IO (MQTT).
// La lógica no bloqueante de invernadero-no-bloqueante.ino sigue midiendo
// con el DHT11, mostrando en el LCD (I2C) y encendiendo el LED de
// ventilación; además publica dos feeds para ver las condiciones del
// invernadero desde el Panel de Control de Adafruit (como en la guía):
//
// "temperatura": grados Celsius.
// "humedad": humedad relativa en %.
//
// Plataforma: ESP32 (DevKit v1) — usa su WiFi integrado.
// Conexión: dato del DHT11 → GPIO 4; LED externo → GPIO 16; display LCD 1602
// por I2C: SDA → GPIO 21, SCL → GPIO 22.
//
// ADAPTACIÓN DE PLATAFORMA (ver README): el original usa un LCD Keypad
// Shield paralelo (6 pines) que no entra en un ESP32; acá usamos un LCD 1602
// con módulo I2C (2 cables, librería LiquidCrystal_I2C, dirección típica
// 0x27; probá 0x3F si no muestra nada).
// Nota eléctrica: el ESP32 trabaja a 3,3 V.
//
// Nota del original: en el LCD no usar tildes ni la letra Ñ.
//
// Librerías requeridas (Library Manager): "DHT sensor library" (Adafruit) +
// "Adafruit Unified Sensor", "LiquidCrystal_I2C" y "Adafruit MQTT Library".
//
// Basado en: Fragmento de código del Nivel Superior (Paso 6) — Invernadero
// Inteligente, 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 temperatura y humedad en
// CADA medición; acá se publican solo cuando cambian de forma apreciable
// (temperatura ≥ 0,5 °C; humedad ≥ 1 %) respecto de lo último publicado, para
// no gastar el cupo gratuito de Adafruit IO (30 publicaciones por minuto).
// Como el DHT11 tiene resolución de 1 °C y 1 %, en la práctica se publica
// ante cualquier cambio real de la medición. (3) Se ignora una lectura
// fallida del DHT11 (isnan): si el sensor devuelve "no numérico" (pasa cada
// tanto), se saltea el ciclo en vez de mostrar/publicar basura. (4) El LCD
// paralelo del original se adapta a I2C.
#include <WiFi.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "DHT.h"
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"
// ---------------------------------------------------------------------------
// Credenciales: reemplazar los cuatro valores por los propios.
// El usuario y la key de Adafruit se copian desde io.adafruit.com
// haciendo clic en el ícono de la "llave" (igual que en la guía original).
// ---------------------------------------------------------------------------
#define WIFI_SSID "TU_SSID"
#define WIFI_PASSWORD "TU_PASSWORD"
#define IO_USERNAME "TU_USUARIO_ADAFRUIT"
#define IO_KEY "TU_KEY_ADAFRUIT"
// Servidor MQTT de Adafruit IO.
const char SERVIDOR_MQTT[] = "io.adafruit.com";
const int PUERTO_MQTT = 1883;
// Pin de datos del sensor y tipo de sensor (DHT11).
const int PIN_DHT = 4;
#define DHTTYPE DHT11
// LED externo indicador de "hay que ventilar".
const int PIN_LED_VENTILACION = 16;
// Dirección I2C y tamaño del display (16 columnas x 2 filas).
const uint8_t LCD_DIRECCION_I2C = 0x27;
const uint8_t LCD_COLUMNAS = 16;
const uint8_t LCD_FILAS = 2;
// Umbral del original: por encima de 25 °C hay que ventilar.
const float UMBRAL_TEMP_C = 25.0;
// El DHT11 no admite más de una lectura por segundo; medimos cada 2 s.
const unsigned long INTERVALO_MEDICION_MS = 2000;
// Un valor se re-publica solo si cambió al menos esto respecto del último
// publicado: evita gastar el cupo gratuito de Adafruit IO con repeticiones.
const float UMBRAL_PUBLICACION_TEMP_C = 0.5;
const float UMBRAL_PUBLICACION_HUM = 1.0;
// Si se cae la conexión MQTT, reintentamos cada tanto SIN bloquear.
const unsigned long INTERVALO_REINTENTO_MQTT_MS = 5000;
// Objetos de sensor, display, WiFi y MQTT.
DHT dht(PIN_DHT, DHTTYPE);
LiquidCrystal_I2C lcd(LCD_DIRECCION_I2C, LCD_COLUMNAS, LCD_FILAS);
WiFiClient clienteWifi;
Adafruit_MQTT_Client mqtt(&clienteWifi, SERVIDOR_MQTT, PUERTO_MQTT,
IO_USERNAME, IO_KEY);
Adafruit_MQTT_Publish feedTemperatura(&mqtt, IO_USERNAME "/feeds/temperatura");
Adafruit_MQTT_Publish feedHumedad(&mqtt, IO_USERNAME "/feeds/humedad");
// Momento (ms) de la última medición y del último intento de conexión MQTT.
unsigned long millisAnterior = 0;
unsigned long millisUltimoIntentoMqtt = 0;
// Últimas mediciones válidas, y lo último que llegó a publicarse. Separarlos
// permite publicar solo los cambios y ponerse al día tras una desconexión.
// Se arranca con valores imposibles para forzar la primera publicación.
float temperaturaPublicada = -1000.0;
float humedadPublicada = -1000.0;
bool hayMedicion = false;
// Diferencia sin signo entre dos números (para comparar contra el umbral).
float diferenciaAbsoluta(float a, float b) {
if (a > b) {
return a - b;
}
return b - a;
}
// Mantiene viva la conexión MQTT sin bloquear el sensor: si está caída,
// reintenta como mucho una vez cada 5 segundos.
void mantenerConexionMqtt() {
if (mqtt.connected()) {
return;
}
unsigned long millisActual = millis();
if (millisActual - millisUltimoIntentoMqtt < INTERVALO_REINTENTO_MQTT_MS) {
return; // todavía no toca reintentar
}
millisUltimoIntentoMqtt = millisActual;
Serial.print("Conectando a Adafruit IO... ");
int8_t resultado = mqtt.connect(); // 0 = conexión exitosa
if (resultado == 0) {
Serial.println("conectado.");
} else {
Serial.println(mqtt.connectErrorString(resultado));
mqtt.disconnect(); // deja el socket limpio para el próximo intento
}
}
// Publica cada feed SOLO si cambió lo suficiente desde la última publicación
// exitosa. Si publish() falla o no hay conexión, no se actualiza el registro:
// se reintenta en la próxima vuelta.
void publicarCambios(float temperatura, float humedad) {
if (!mqtt.connected() || !hayMedicion) {
return; // sin internet o sin datos reales todavía: no se publica
}
if (diferenciaAbsoluta(temperatura, temperaturaPublicada)
>= UMBRAL_PUBLICACION_TEMP_C
&& feedTemperatura.publish(temperatura)) {
temperaturaPublicada = temperatura;
}
if (diferenciaAbsoluta(humedad, humedadPublicada) >= UMBRAL_PUBLICACION_HUM
&& feedHumedad.publish(humedad)) {
humedadPublicada = humedad;
}
}
void setup() {
Serial.begin(115200);
pinMode(PIN_LED_VENTILACION, OUTPUT);
dht.begin();
lcd.init();
lcd.backlight();
lcd.clear();
// Conexión WiFi. Esperar acá con delay() es aceptable porque el ciclo de
// medición todavía no arrancó; una vez dentro de loop(), nada bloquea.
Serial.print("Conectando a la red WiFi");
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("WiFi conectado, IP: ");
Serial.println(WiFi.localIP());
}
void loop() {
unsigned long millisActual = millis();
// --- Medición, aviso y display, cada 2 s (respeta el DHT11) ---
if (millisActual - millisAnterior >= INTERVALO_MEDICION_MS) {
millisAnterior = millisActual;
float temperatura = dht.readTemperature(); // grados Celsius
float humedad = dht.readHumidity(); // humedad relativa en %
// Una lectura fallida del DHT11 devuelve "no numérico": la salteamos
// para no mostrar ni publicar basura.
if (!isnan(temperatura) && !isnan(humedad)) {
hayMedicion = true;
// Aviso de ventilación como encendido fijo (if/else).
if (temperatura > UMBRAL_TEMP_C) {
digitalWrite(PIN_LED_VENTILACION, HIGH);
} else {
digitalWrite(PIN_LED_VENTILACION, LOW);
}
// Fila 0: temperatura. Fila 1: humedad. Sin tildes ni Ñ.
lcd.setCursor(0, 0);
lcd.print("Temp.: ");
lcd.print(temperatura);
lcd.setCursor(0, 1);
lcd.print("Hum.: ");
lcd.print(humedad);
// Publicamos (solo si cambió lo suficiente).
publicarCambios(temperatura, humedad);
}
}
// --- Mantenimiento de la conexión IoT, en cada vuelta ---
// Gracias a que la medición no bloquea, esto se atiende continuamente:
// esta es la razón de fondo por la que la guía abandona delay() antes de IoT.
mantenerConexionMqtt();
} Así queda funcionando el tablero que permite monitorear el invernadero a distancia. Podés ver el sistema completo —el sensor midiendo, el aviso, el display y el panel reflejando cada publicación— en el simulador:
Simulación del comportamiento
La misma máquina de estados del firmware, portada a TypeScript, maneja el
comportamiento del dispositivo. No se ejecuta el binario real: la ejecución
del .hex con avr8js está en el roadmap.
Simulación lista. Presioná «Iniciar» para arrancar la secuencia.
De aviso a acción
Hoy el sistema avisa que hay que ventilar, pero la ventilación sigue siendo manual. Piensen en grupo: ¿cómo automatizarían la apertura de una ventana o el encendido de un ventilador cuando la temperatura supera el umbral? ¿Qué componente harían falta (un servo, un ventilador con relé) y en qué se parecería su control al del LED que ya tienen? ¿Y si además publicaran en un tercer feed el estado “ventilando / en reposo”, como hace el proyecto de riego con su válvula?
Verificá
Con el programa cargado y la placa conectada a tu red WiFi, abrí el panel de Adafruit IO en el
celular o la computadora: los gráficos de temperatura y humedad tienen que ir sumando puntos
cuando las lecturas cambian (recordá que se publica por cambios: con el ambiente estable, que no
lleguen datos nuevos es lo esperado; acercá calor o humedad al sensor 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.
Cierre del nivel
Al terminar este nivel, tu invernadero dejó de ser solo un dispositivo local: es un dispositivo
IoT. El programa funciona con millis(), sin ningún delay() que lo cuelgue; sabés explicar por
qué eso es imprescindible cuando hay que atender internet en simultáneo; y el panel de Adafruit IO
muestra desde cualquier dispositivo la temperatura y la humedad del invernadero. 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 “Sistema de Riego Automatizado”, para que el
invernadero controle tanto el ambiente como el riego de las plantas.
Evidencia de cierre
Al terminar este nivel, esto tiene que estar funcionando:
- El sistema funciona con código no bloqueante basado en millis(): no queda ningún delay() que detenga el programa entre mediciones, y el LED de aviso pasó de parpadear a quedar encendido fijo mientras la temperatura supera el umbral.
- Podés explicar con tus palabras por qué el código bloqueante es un problema cuando el dispositivo tiene que atender internet en simultáneo.
- El panel de Adafruit IO muestra la temperatura y la humedad del invernadero publicadas por el dispositivo (variante ESP32).