Manual del proyecto · Proyecto 12
Sistema de Calefacción Automático
Diseño, construcción y programación de un sistema de calefacción automático: un radiador eléctrico que una placa enciende y apaga mediante un relé, primero por tiempo y luego según la temperatura que mide un sensor DHT11 —con un potenciómetro para fijar la temperatura deseada y un display LCD para verla—, y por último monitoreado de forma remota a través de Internet de las Cosas (IoT).
- Dificultad:
- Nivel educativo:
- Secundario. Ciclo Básico.
Contenido del manual
- 01 Materiales y equipamiento
- 02 Encender y apagar el radiador por tiempo Nivel inicial
- 03 Encender el radiador según la temperatura Nivel intermedio
- 04 Monitorear la calefacción 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 |
| | Módulo relé Actúa como interruptor del radiador: la placa lo comanda con 5 V (o 3,3 V en el ESP32) y él conmuta la carga. En el original se conecta al pin 3 del UNO; en el ESP32, al GPIO 25. Por seguridad, en el aula conmuta una carga de baja tensión (o el LED de prueba del pin 13); la conexión a 220 V es exclusiva del docente. | 1 | Todas |
| | Potenciómetro de 10 kΩ Con él se fija a mano la temperatura objetivo del sistema (nivel intermedio). Es analógico: va a una entrada analógica (pin A1 en el UNO; GPIO 34, de entrada, en el ESP32). | 1 | Todas |
| | Radiador eléctrico de 220 V (o, para el aula, una lámpara de baja tensión) Es la carga que el relé enciende y apaga. El original usaba un radiador o una bombilla de 100 W a 220 V; en el aula se practica con una carga de baja tensión y la instalación de 220 V queda reservada al docente. En el nivel avanzado, el relé se reemplaza por un LED en el pin 13 para probar sin tensión de red. | 1 | Todas |
| | Cable dupont macho-hembra | 20 | Todas |
| | Cable dupont macho-macho Para las conexiones en la protoboard entre componentes. | 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: 8, 9, 4, 5, 6, 7). 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 |
| Módulo relé · VCC | Bus + de la protoboard | rojo |
| Módulo relé · GND | Bus − de la protoboard | negro |
| Módulo relé · IN | Arduino UNO · 3 | azul |
| Enchufe 220 V (docente) | Relé · COM | naranja |
| Relé · NO | Radiador | naranja |
| Potenciómetro · extremo A | Bus + de la protoboard | rojo |
| Potenciómetro · extremo B | Bus − de la protoboard | negro |
| Potenciómetro · cursor | Arduino UNO · A1 | violeta |
| 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 |
| 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 · GPIO25 | azul |
| Enchufe 220 V (docente) | Relé · COM | naranja |
| Relé · NO | Radiador | naranja |
| Potenciómetro · extremo A | Bus + de la protoboard | rojo |
| Potenciómetro · extremo B | Bus − de la protoboard | negro |
| Potenciómetro · cursor | ESP32 DevKit v1 · GPIO34 | violeta |
| 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 |
Encender y apagar el radiador por tiempo
Conectar el módulo relé al radiador
Para optimizar el consumo del radiador necesitamos controlar su encendido y apagado desde la placa. Como la placa trabaja con señales de baja tensión y el radiador con tensión de red, entre los dos va un reléReléDispositivo electromagnético que funciona como un interruptor controlado por un circuito eléctrico: un electroimán abre o cierra contactos de otros circuitos independientes. Permite controlar con bajo voltaje dispositivos que utilizan mayor voltaje.: un interruptor accionado eléctricamente que deja pasar o corta la corriente del radiador según lo que le indique la placa.
En el Arduino UNO seguimos el esquema original: el pin de señal del módulo relé va al pin
digital 3 (la constante PIN_RELE del código), VCC al pin 5V y GND a GND. Al
poner el pin 3 en estado ALTO (HIGH), el relé cierra el contacto y el radiador se enciende; en
BAJO (LOW), lo abre y el radiador se apaga.
En el ESP32 la conexión es equivalente, con otros números: el pin de señal del módulo relé va
al GPIO 25 (la constante PIN_RELE del código), y la alimentación del módulo se toma de 5V
(pin VIN, tomado del USB) y GND, porque muchos módulos relé no conmutan de forma confiable
con los 3,3 V del ESP32. La lógica es la misma: GPIO 25 en ALTO enciende el radiador, en BAJO lo
apaga.
El lado de potencia se arma como en la figura: se intercala el relé en uno de los cables del enchufe del radiador. Un cable va del enchufe al borne COM del relé y otro desde el borne NO (normal abierto) de vuelta al radiador; así, el radiador solo recibe energía cuando el relé cierra el contacto.
¡Atención! El radiador funciona con 220 V, que es una tensión peligrosa. En el aula, el relé conmuta una carga de baja tensión (por ejemplo, una lámpara de bajo voltaje) o directamente el LED de prueba del pin 13, y así se practica toda la lógica sin tocar la red. La instalación real de 220 V (desarmar el enchufe, cablear el radiador) es una tarea exclusiva del docente, supervisada y sin participación de estudiantes. Y una regla que el propio original remarca: las conexiones de potencia del relé nunca van sobre la protoboard.
Programar el encendido y apagado del radiador
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.
Antes de manejar tiempos largos, conviene una prueba rápida: hacer que el radiador se encienda
y se apague de forma intermitente cada pocos segundos, para verificar que el relé responde. En el
código, poner PIN_RELE en ALTO (HIGH) enciende el radiador y ponerlo en BAJO (LOW) lo apaga;
intercalando una espera con delay() entre uno y otro, el radiador prende y apaga solo. Vas a
escuchar el clic del relé y, si estás probando con el LED del pin 13 o una lámpara, verlo
titilar. Sobre esta idea básica construimos, en el paso 4, el temporizador con los tiempos reales.
Subir el código a la placa
Para subir el código de nuestro programa a la placa necesitamos:
- Conectar la placa a la computadora con el cable USB.
- Chequear que en el selector de placa del Arduino IDE esté seleccionada la placa correcta.
- Seleccionar 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. al que está conectada la placa.
- Cliquear el botón Subir (la flecha →) en la parte superior de la interfaz.
En el Arduino IDE 2.x, la placa y el puerto se eligen juntos en el menú desplegable de la barra superior: al conectar la placa suele aparecer directamente como opción. Si no aparece, entrá a “Select other board and port” y buscala por nombre.
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.
Al terminar de subir, veremos el mensaje de carga completada en la barra inferior del IDE.
Programar el tiempo de encendido y apagado
Ahora sí, el temporizador de verdad. Para ahorrar energía hay que calcular cuánto tiempo conviene tener el radiador encendido; eso depende de muchos factores, como el tamaño de la habitación y la temperatura exterior. A modo de ejemplo, y como en el original, dejamos el radiador encendido durante 10 minutos (600 segundos) y apagado durante 15 minutos (900 segundos), repitiendo el ciclo indefinidamente.
Esos dos tiempos están al comienzo del código en las constantes TIEMPO_ENCENDIDO_MS (600000
milisegundos) y TIEMPO_APAGADO_MS (900000 milisegundos). Cambiando esos valores, adaptás el
ritmo del radiador a tu habitación sin tocar el resto del programa. En este nivel usamos delay(),
que es lo más simple y hace que el código se lea igual que su comportamiento: encender, esperar el
tiempo de encendido, apagar, esperar el tiempo de apagado, y volver a empezar.
Ver el código completo · calefaccion-temporizada.ino
// Calefaccion temporizada: un modulo rele enciende y apaga un radiador
// electrico siguiendo tiempos fijos, sin sensores. Es el prototipo mas
// simple del sistema: "prender 10 min, apagar 15 min", como un temporizador.
//
// !! SEGURIDAD 220V !! El radiador es una carga de 220V conmutada por el
// rele. En el aula, usar una carga de BAJA TENSION (o directamente el LED de
// prueba del pin 13) para ver el ciclo sin riesgo. La conexion real a 220V
// queda reservada al docente o a una instalacion supervisada, y el rele con
// su cableado de 220V van SIEMPRE fuera de la protoboard (ver README).
//
// Plataforma: Arduino UNO.
// Conexion (la del esquema original): senal del modulo rele -> pin 3 (con su
// alimentacion de 5V y GND). El rele se comporta como el interruptor del
// radiador.
//
// Basado en: Fragmento de codigo del Nivel Inicial (Paso 4) - Sistema de
// Calefaccion Automatico, Saberes Digitales (INET/EDUCAR 2019)
// Modernizacion: se reemplaza el C generado por mBlock3 (_delay, _loop) por
// C++ idiomatico con constantes nombradas; los tiempos quedan como constantes
// claras en milisegundos, y el estado del rele por constantes invertibles.
// Pin de la senal del modulo rele.
const int PIN_RELE = 3;
// El modulo rele de la guia es ACTIVO-ALTO: HIGH cierra el contacto y el
// radiador se enciende. Muchos modulos comerciales son activo-bajo: si el
// tuyo enciende al reves, invertí estas dos constantes (ENCENDIDO = LOW /
// APAGADO = HIGH) y el resto del programa no cambia.
const int RELE_ENCENDIDO = HIGH;
const int RELE_APAGADO = LOW;
// Tiempos del ejemplo original: 10 minutos encendido, 15 minutos apagado
// (600 s y 900 s en el PDF, aca expresados en milisegundos).
// NOTA PARA EL AULA: 10 y 15 minutos hacen la demostracion muy lenta. Para
// ver el ciclo en clase, bajá estos valores a algo corto, por ejemplo
// 5000UL / 5000UL (5 s encendido / 5 s apagado); una vez entendido el
// comportamiento, volvé a los tiempos reales.
const unsigned long TIEMPO_ENCENDIDO_MS = 600000UL; // 10 min
const unsigned long TIEMPO_APAGADO_MS = 900000UL; // 15 min
void setup() {
pinMode(PIN_RELE, OUTPUT);
// Arrancamos con el radiador apagado.
digitalWrite(PIN_RELE, RELE_APAGADO);
}
void loop() {
// Encender el radiador y esperar el tiempo de encendido.
digitalWrite(PIN_RELE, RELE_ENCENDIDO);
delay(TIEMPO_ENCENDIDO_MS);
// Apagar el radiador y esperar el tiempo de apagado. 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.
digitalWrite(PIN_RELE, RELE_APAGADO);
delay(TIEMPO_APAGADO_MS);
} // Calefaccion temporizada: un modulo rele enciende y apaga un radiador
// electrico siguiendo tiempos fijos, sin sensores. Es el prototipo mas
// simple del sistema: "prender 10 min, apagar 15 min", como un temporizador.
//
// !! SEGURIDAD 220V !! El radiador es una carga de 220V conmutada por el
// rele. En el aula, usar una carga de BAJA TENSION (o directamente un LED de
// prueba) para ver el ciclo sin riesgo. La conexion real a 220V queda
// reservada al docente o a una instalacion supervisada, y el rele con su
// cableado de 220V van SIEMPRE fuera de la protoboard (ver README).
//
// Plataforma: ESP32 (DevKit v1).
// Conexion: senal del modulo rele -> GPIO 25. Nota electrica: el ESP32
// trabaja a 3,3 V; usar un modulo rele con entrada compatible con 3,3 V (o
// con optoacoplador) para que conmute de forma confiable.
//
// Basado en: Fragmento de codigo del Nivel Inicial (Paso 4) - Sistema de
// Calefaccion Automatico, Saberes Digitales (INET/EDUCAR 2019)
// Modernizacion: se reemplaza el C generado por mBlock3 (_delay, _loop) por
// C++ idiomatico con constantes nombradas; los tiempos quedan como constantes
// claras en milisegundos, y el estado del rele por constantes invertibles.
// Pin de la senal del modulo rele (GPIO seguro del contrato ESP32).
const int PIN_RELE = 25;
// El modulo rele de la guia es ACTIVO-ALTO: HIGH cierra el contacto y el
// radiador se enciende. Muchos modulos comerciales son activo-bajo: si el
// tuyo enciende al reves, invertí estas dos constantes (ENCENDIDO = LOW /
// APAGADO = HIGH) y el resto del programa no cambia.
const int RELE_ENCENDIDO = HIGH;
const int RELE_APAGADO = LOW;
// Tiempos del ejemplo original: 10 minutos encendido, 15 minutos apagado
// (600 s y 900 s en el PDF, aca expresados en milisegundos).
// NOTA PARA EL AULA: 10 y 15 minutos hacen la demostracion muy lenta. Para
// ver el ciclo en clase, bajá estos valores a algo corto, por ejemplo
// 5000UL / 5000UL (5 s encendido / 5 s apagado); una vez entendido el
// comportamiento, volvé a los tiempos reales.
const unsigned long TIEMPO_ENCENDIDO_MS = 600000UL; // 10 min
const unsigned long TIEMPO_APAGADO_MS = 900000UL; // 15 min
void setup() {
pinMode(PIN_RELE, OUTPUT);
// Arrancamos con el radiador apagado.
digitalWrite(PIN_RELE, RELE_APAGADO);
}
void loop() {
// Encender el radiador y esperar el tiempo de encendido.
digitalWrite(PIN_RELE, RELE_ENCENDIDO);
delay(TIEMPO_ENCENDIDO_MS);
// Apagar el radiador y esperar el tiempo de apagado. 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.
digitalWrite(PIN_RELE, RELE_APAGADO);
delay(TIEMPO_APAGADO_MS);
} Podés ver el comportamiento —el radiador encendiéndose y apagándose según el temporizador— en el simulador, antes de armar el circuito real (y con tiempos acelerados para no tener que esperar los minutos reales):
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.
¿Cuánto prendido, cuánto apagado?
Discutan en grupo: los 10 minutos encendido y 15 apagado son un ejemplo. ¿De qué depende el tiempo ideal en una habitación real? Piensen en el tamaño del ambiente, el aislamiento de las paredes y la temperatura de afuera. ¿Qué problema tiene un temporizador “a ciegas” como este, que no sabe qué temperatura hace realmente adentro? Guarden la respuesta: el nivel intermedio la resuelve midiendo la temperatura con un sensor.
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. La gran limitación del temporizador es un excelente disparador: enciende y apaga por reloj, sin enterarse de si la habitación ya está caliente o si sigue helada. Que los grupos lo noten ahora hace que el salto al termostato del nivel intermedio se sienta como una necesidad, no como un capricho.
Verificá
Con el programa cargado y probando con el LED del pin 13 o una lámpara de baja tensión (no con
220 V): el radiador tiene que encenderse, mantenerse así el tiempo de TIEMPO_ENCENDIDO_MS,
apagarse y mantenerse apagado el tiempo de TIEMPO_APAGADO_MS, y volver a empezar. Para no
esperar los minutos reales durante la prueba, podés bajar temporalmente esas dos constantes a unos
pocos segundos y confirmar que el ciclo se cumple.
Cierre del nivel
Al terminar este nivel, la calefacción de Martina ya funciona sola: la placa comanda el relé y el relé enciende y apaga el radiador siguiendo un temporizador, con los cuidados de seguridad que exige trabajar cerca de 220 V. Además, ya sabés subir programas desde el Arduino IDE 2.x. Pero el temporizador tiene un punto ciego: enciende por reloj, sin saber qué temperatura hace realmente en la habitación. En el nivel intermedio le sumamos un sensor DHT11 para que el sistema decida según la temperatura, un potenciómetro para fijar la temperatura deseada y un display LCD para verla.
Evidencia de cierre
Al terminar este nivel, esto tiene que estar funcionando:
- El módulo relé está conectado a la placa (pin de señal, VCC y GND) y del lado de potencia conmuta la carga del aula (una lámpara de baja tensión o el LED del pin 13), con el circuito de 220 V reservado al docente y fuera de la protoboard.
- 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.
- La placa enciende el radiador durante TIEMPO_ENCENDIDO_MS (10 minutos) y lo apaga durante TIEMPO_APAGADO_MS (15 minutos), repitiendo el ciclo.
- Entendés por qué el relé permite que la placa de 5 V comande una carga de mayor tensión, y por qué la instalación de 220 V es una tarea exclusiva del docente.
Encender el radiador según la temperatura
Situación problema
Martina descubrió que existe un modo de hacer aún más eficiente el uso del calefactor y mantener la habitación dentro de un rango de temperaturas confortable: necesita un sistema que se regule automáticamente en función de la temperatura ambiente, en vez de encender y apagar por reloj.
Necesita, además, agregar un display donde se muestre la temperatura actual, para poder visualizar y configurar de forma sencilla la temperatura deseada, con la tranquilidad de que el sistema la mantendrá más o menos constante.
En este nivel se agrega un sensor de temperatura y, en función de sus mediciones, se enciende y apaga el relé. La temperatura a la que el sistema se prende y se apaga se controla con un potenciómetro y se visualiza en un display LCD.
Conectar el sensor de temperatura y humedad (DHT11)
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 (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.
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. El relé sigue en el
pin 3 (PIN_RELE), como en el nivel inicial.
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. El relé sigue en el GPIO 25
(PIN_RELE).
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.
Encender el radiador según la temperatura
Ahora el sistema deja de encender por reloj y pasa a comportarse como un termostato: enciende el radiador cuando hace frío y lo apaga cuando la habitación llegó a la temperatura deseada.
La lógica es un if/else que compara la temperatura medida contra una temperatura objetivo.
Como es un sistema de calefacción, la comparación va al revés que la intuición del “supera el
umbral”: el radiador se enciende cuando la temperatura medida está por debajo del objetivo, y
se apaga cuando lo alcanza o supera. Por ahora usamos un objetivo fijo, en la constante
UMBRAL_TEMP_C, con el valor del original: 22 °C (en el paso 4 lo haremos ajustable con el
potenciómetro).
Entre medición y medición, el programa espera el tiempo de la constante INTERVALO_MEDICION_MS,
que está en 2 segundos, porque el DHT11 no admite más de una lectura por segundo (y la
librería de Adafruit, además, guarda la última lectura durante 2 segundos). En este nivel usamos
delay(), que es lo más simple; en el nivel avanzado lo cambiaremos por código no bloqueante.
Verificá
Con este programa cargado (probando con el LED del pin 13 o una lámpara de baja tensión, no con 220 V): si la temperatura del ambiente está por debajo de 22 °C, el radiador tiene que estar encendido; al acercar calor al sensor y superar los 22 °C, tiene que apagarse. Como el sensor mide cada 2 segundos, puede haber una pequeña demora entre el cambio de temperatura y la reacción del relé: es lo esperado.
Conectar y probar el display LCD
Para ver la temperatura sumamos 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. 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 (los pines 8, 9, 4, 5, 6 y 7). Es importante no forzar las piezas: todos los pines deben coincidir. Como el shield ocupa varios pines, el DHT11, el relé y el potenciómetro se cablean a los pines que quedan libres.
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 sus seis pines:
LiquidCrystal lcd(8, 9, 4, 5, 6, 7).
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. Conectamos VCC a 5V (o 3,3 V según el módulo), GND a GND, SDA al GPIO 21 y SCL al GPIO 22 (los pines I2C por defecto del ESP32).
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, conviene un primer programa de prueba que escriba 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 Act”
y “Temp Set”).
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.
Fijar la temperatura objetivo con un potenciómetro
Hasta ahora la temperatura objetivo era fija (22 °C). Ahora la hacemos ajustable a mano con un potenciómetroPotenciómetroResistor cuyo valor de resistencia se ajusta de forma manual con una perilla. Conectando sus extremos a VCC y GND, la pata central entrega un nivel de tensión que representa la posición de la perilla y se lee por una entrada analógica del Arduino.: girando la perilla, Martina elige a qué temperatura quiere la habitación, y el display le muestra tanto la temperatura actual como la que está configurando.
El potenciómetro es analógico, así que va a una entrada analógica, y la placa lo lee como un
número que hay que convertir a una escala de temperatura. Como en el original, mapeamos el
recorrido de la perilla a un rango de 0 a 30 °C: con la perilla al mínimo, el objetivo es 0 °C;
al máximo, 30 °C. Ese valor convertido reemplaza a la constante fija del paso 2 y queda en la
variable tempObjetivo, que es la que el if/else compara contra la temperatura medida.
En el Arduino UNO el potenciómetro va al pin analógico A1 (la constante PIN_POT del código).
La placa lo lee con analogRead() como un número de 0 a 1023 (0 V a 5 V), y el código lo
convierte a la escala de 0 a 30 °C con map().
En el ESP32 el potenciómetro va al GPIO 34 (la constante PIN_POT del código), un pin de solo
entrada, ideal para leer un sensor o una perilla. Ojo con un detalle real: el conversor del ESP32
es de mayor resolución, así que analogRead() devuelve un número de 0 a 4095 (no de 0 a
1023 como en el UNO). El código lo contempla en el map() usando el rango correcto de la placa;
por lo demás, la lógica es idéntica.
Aprovechamos las dos filas del display: en la primera mostramos la temperatura actual
(Temp Act) y en la segunda la temperatura objetivo (Temp Set) que fija el potenciómetro.
Ver el código completo · termostato-lcd.ino
// Termostato con display: el sensor DHT11 mide la temperatura del ambiente y
// un potenciometro fija la temperatura OBJETIVO. Si hace frio (temperatura
// por debajo del objetivo) el rele enciende el radiador; si ya se alcanzo el
// objetivo, lo apaga. El LCD 16x2 muestra la temperatura actual y la objetivo.
//
// !! SEGURIDAD 220V !! El radiador es una carga de 220V conmutada por el
// rele. En el aula, usar una carga de BAJA TENSION (o el LED de prueba del
// pin 13) para probar sin riesgo. La conexion real a 220V queda reservada al
// docente o a una instalacion supervisada, y el rele con su cableado de 220V
// van SIEMPRE fuera de la protoboard (ver README).
//
// Plataforma: Arduino UNO.
// Conexion (la del esquema original): dato del DHT11 -> pin 2; senal del rele
// -> pin 3; potenciometro (cursor) -> A1; display LCD Keypad Shield en modo
// paralelo por los pines 8, 9, 4, 5, 6, 7. El shield se apila sobre el UNO;
// su preset RP1 ajusta el contraste si no se lee el texto.
//
// Nota del original: en el LCD no usar tildes ni la letra Ñ.
//
// Basado en: Fragmento de codigo del Nivel Intermedio (Paso 14, codigo
// completo) - Sistema de Calefaccion Automatico, Saberes Digitales
// (INET/EDUCAR 2019)
// Modernizacion: (1) constantes nombradas en vez del C de mBlock3. (2)
// Libreria "DHT sensor library" de Adafruit (instalar desde el Library
// Manager; arrastra "Adafruit Unified Sensor"), con dht.begin() en setup().
// (3) La temperatura se lee UNA vez por vuelta a una variable y ese valor se
// usa para el rele y para el display. (4) Se ignora una lectura fallida del
// DHT11 (isnan) para no accionar el radiador con un valor no numerico.
#include "DHT.h"
#include <LiquidCrystal.h>
// Pin de datos del sensor y tipo de sensor (DHT11).
const int PIN_DHT = 2;
#define DHTTYPE DHT11
// Senal del modulo rele del radiador.
const int PIN_RELE = 3;
// Cursor del potenciometro que fija la temperatura objetivo (entrada analogica).
const int PIN_POTENCIOMETRO = A1;
// El modulo rele es ACTIVO-ALTO: HIGH enciende el radiador. Si tu modulo es
// activo-bajo, invertí estas dos constantes (ENCENDIDO = LOW / APAGADO = HIGH).
const int RELE_ENCENDIDO = HIGH;
const int RELE_APAGADO = LOW;
// El potenciometro fija la temperatura objetivo. La entrada analogica del UNO
// lee 0..1023 (10 bits); la mapeamos a 0..30 grados con una regla de tres,
// tal como el original (potenciometro en 0 -> 0 grados; en el maximo -> 30).
const float TEMP_OBJETIVO_MAX_C = 30.0;
const int ADC_MAX = 1023;
// El DHT11 no admite mas de una lectura por segundo; medimos cada 2 s (el
// original pedia 1 s, pero 2 s es el valor honesto para este sensor y la
// libreria de Adafruit ademas cachea la lectura).
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);
void setup() {
pinMode(PIN_RELE, OUTPUT);
digitalWrite(PIN_RELE, RELE_APAGADO); // arranca apagado
pinMode(PIN_POTENCIOMETRO, INPUT);
dht.begin();
lcd.begin(16, 2);
lcd.clear();
}
void loop() {
// Temperatura actual del ambiente (grados Celsius).
float temperatura = dht.readTemperature();
// Temperatura objetivo fijada a mano con el potenciometro (0..30 grados).
float objetivo = (float)analogRead(PIN_POTENCIOMETRO) * TEMP_OBJETIVO_MAX_C / ADC_MAX;
// Una lectura fallida del DHT11 devuelve "no numerico": la salteamos para
// no accionar el radiador con basura (el rele conserva su ultimo estado).
if (!isnan(temperatura)) {
// Termostato: si la temperatura esta por debajo del objetivo, hace frio y
// encendemos el radiador; si ya se alcanzo el objetivo, lo apagamos.
if (temperatura < objetivo) {
digitalWrite(PIN_RELE, RELE_ENCENDIDO);
} else {
digitalWrite(PIN_RELE, RELE_APAGADO);
}
}
// Fila 0: temperatura actual. Fila 1: temperatura objetivo. Sin tildes ni Ñ.
lcd.setCursor(0, 0);
lcd.print("Temp Act: ");
lcd.print(temperatura);
lcd.setCursor(0, 1);
lcd.print("Temp Set: ");
lcd.print(objetivo);
delay(INTERVALO_MEDICION_MS);
} // Termostato con display: el sensor DHT11 mide la temperatura del ambiente y
// un potenciometro fija la temperatura OBJETIVO. Si hace frio (temperatura
// por debajo del objetivo) el rele enciende el radiador; si ya se alcanzo el
// objetivo, lo apaga. El LCD 16x2 muestra la temperatura actual y la objetivo.
//
// !! SEGURIDAD 220V !! El radiador es una carga de 220V conmutada por el
// rele. En el aula, usar una carga de BAJA TENSION (o un LED de prueba) para
// probar sin riesgo. La conexion real a 220V queda reservada al docente o a
// una instalacion supervisada, y el rele con su cableado de 220V van SIEMPRE
// fuera de la protoboard (ver README).
//
// Plataforma: ESP32 (DevKit v1).
// Conexion: dato del DHT11 -> GPIO 4; senal del rele -> GPIO 25; cursor del
// potenciometro -> GPIO 34 (entrada del ADC1, solo entrada); display LCD 1602
// por I2C: SDA -> GPIO 21, SCL -> GPIO 22.
//
// ADAPTACION DE PLATAFORMA (ver README): el original usa un LCD Keypad Shield
// paralelo (6 pines: 8, 9, 4, 5, 6, 7) que NO entra en un ESP32. Aca usamos un
// LCD 1602 con modulo I2C: solo DOS cables de senal (SDA y SCL) y la libreria
// LiquidCrystal_I2C. La direccion I2C tipica es 0x27 (algunos son 0x3F: si la
// pantalla no muestra nada, probá 0x3F o escaneá el bus I2C).
//
// ADAPTACION DEL ADC (¡importante!): el ADC del ESP32 es de 12 bits
// (0..4095), no de 10 bits (0..1023) como el UNO. Por eso ADC_MAX = 4095 al
// mapear el potenciometro a 0..30 grados. GPIO 34 es solo-entrada y pertenece
// al ADC1 (el unico usable con WiFi encendido).
// Nota electrica: el ESP32 trabaja a 3,3 V; DHT11 y el modulo I2C funcionan a
// 3,3 V, y el potenciometro se alimenta entre 3,3 V y GND.
//
// Nota del original: en el LCD no usar tildes ni la letra Ñ.
//
// Librerias requeridas (Library Manager): "DHT sensor library" (Adafruit) +
// "Adafruit Unified Sensor", y "LiquidCrystal_I2C".
//
// Basado en: Fragmento de codigo del Nivel Intermedio (Paso 14, codigo
// completo) - Sistema de Calefaccion Automatico, Saberes Digitales
// (INET/EDUCAR 2019)
// Modernizacion: constantes nombradas; DHT de Adafruit con dht.begin(); LCD
// paralelo del original adaptado a I2C para ESP32; ADC de 12 bits; una sola
// lectura por vuelta reutilizada para el rele y el display; se ignora la
// lectura fallida del DHT11 (isnan).
#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
// Senal del modulo rele del radiador (GPIO seguro del contrato ESP32).
const int PIN_RELE = 25;
// Cursor del potenciometro que fija la temperatura objetivo (ADC1, GPIO 34).
const int PIN_POTENCIOMETRO = 34;
// El modulo rele es ACTIVO-ALTO: HIGH enciende el radiador. Si tu modulo es
// activo-bajo, invertí estas dos constantes (ENCENDIDO = LOW / APAGADO = HIGH).
const int RELE_ENCENDIDO = HIGH;
const int RELE_APAGADO = LOW;
// Direccion I2C y tamaño del display (16 columnas x 2 filas).
const uint8_t LCD_DIRECCION_I2C = 0x27;
const uint8_t LCD_COLUMNAS = 16;
const uint8_t LCD_FILAS = 2;
// El potenciometro fija la temperatura objetivo. El ADC del ESP32 lee 0..4095
// (12 bits); lo mapeamos a 0..30 grados con una regla de tres.
const float TEMP_OBJETIVO_MAX_C = 30.0;
const int ADC_MAX = 4095;
// El DHT11 no admite mas 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_RELE, OUTPUT);
digitalWrite(PIN_RELE, RELE_APAGADO); // arranca apagado
dht.begin();
// Con I2C, primero arranca el bus y el display; backlight enciende la luz.
lcd.init();
lcd.backlight();
lcd.clear();
}
void loop() {
// Temperatura actual del ambiente (grados Celsius).
float temperatura = dht.readTemperature();
// Temperatura objetivo fijada a mano con el potenciometro (0..30 grados).
float objetivo = (float)analogRead(PIN_POTENCIOMETRO) * TEMP_OBJETIVO_MAX_C / ADC_MAX;
// Una lectura fallida del DHT11 devuelve "no numerico": la salteamos para
// no accionar el radiador con basura (el rele conserva su ultimo estado).
if (!isnan(temperatura)) {
// Termostato: si la temperatura esta por debajo del objetivo, hace frio y
// encendemos el radiador; si ya se alcanzo el objetivo, lo apagamos.
if (temperatura < objetivo) {
digitalWrite(PIN_RELE, RELE_ENCENDIDO);
} else {
digitalWrite(PIN_RELE, RELE_APAGADO);
}
}
// Fila 0: temperatura actual. Fila 1: temperatura objetivo. Sin tildes ni Ñ.
lcd.setCursor(0, 0);
lcd.print("Temp Act: ");
lcd.print(temperatura);
lcd.setCursor(0, 1);
lcd.print("Temp Set: ");
lcd.print(objetivo);
delay(INTERVALO_MEDICION_MS);
} Fijate, al comparar las dos plataformas con el selector, que el corazón del programa es el mismo: cambian solo la parte del display (qué librería se incluye y cómo se declara) y el rango del potenciómetro. La lógica del termostato es idéntica.
Podés ver el sistema completo —el sensor midiendo, la perilla fijando el objetivo y el radiador encendiéndose cuando hace frío— 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 condensa una decena de pasos del material original en una progresión más limpia: sensor, termostato, pantalla, perilla. Vale la pena detenerse en dos ideas. La primera: el termostato compara “medida contra objetivo”, y el sentido de la comparación depende de si el sistema calienta (enciende cuando falta calor) o ventila/enfría (enciende cuando sobra) —un gran contraste para plantear con el proyecto de invernadero. La segunda: el mismo problema se resuelve distinto según el hardware (el shield paralelo del UNO frente al LCD por I2C del ESP32, y el potenciómetro leído en 10 bits o en 12). Comparar los dos códigos lado a lado con el selector deja clarísimo qué parte del programa depende del periférico y qué parte es la lógica del proyecto.
El traqueteo del relé
Discutan en grupo: si la temperatura de la habitación se queda justo en el valor objetivo, las
pequeñas variaciones de la medición (el DHT11 tiene ruido) pueden hacer que el relé encienda y
apague sin parar. ¿Cómo evitarían ese “traqueteo”? Pista: pensá en dejar una pequeña franja entre
la temperatura a la que enciende y la temperatura a la que apaga (lo que en el proyecto de tanques
se llamó histéresis). ¿Dónde iría ese margen en el if/else?
Verificá
Con el programa cargado, el display tiene que mostrar Temp Act con la temperatura medida en la
primera fila y Temp Set con la temperatura objetivo en la segunda. Girá la perilla del
potenciómetro: el valor de Temp Set tiene que cambiar entre 0 y 30 °C. Poné el objetivo por
encima de la temperatura ambiente: el radiador (LED del pin 13 o lámpara de baja tensión) tiene que
encenderse. Bajá el objetivo por debajo de la temperatura ambiente: tiene que apagarse. 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, la calefacción de Martina dejó de encender por reloj: ahora mide la temperatura con el DHT11 y se comporta como un termostato, encendiendo el radiador solo cuando la habitación está por debajo de la temperatura que ella misma fija con el potenciómetro, y mostrando todo en el display. 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 el potenciómetro leído en 10 o en 12 bits—. 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 la calefacción desde el celular.
Evidencia de cierre
Al terminar este nivel, esto tiene que estar funcionando:
- El sensor DHT11 está conectado a su pin de datos y el sistema funciona como termostato: enciende el radiador cuando la temperatura medida está por debajo de la temperatura objetivo y lo apaga cuando la alcanza.
- El display muestra la temperatura actual y la temperatura objetivo, según la plataforma (LCD Keypad Shield en modo paralelo en el UNO, LCD 1602 por I2C en el ESP32), sin tildes ni Ñ.
- El potenciómetro fija a mano la temperatura objetivo (de 0 a 30 °C) y el display refleja ese valor al girar la perilla.
Monitorear la calefacción con Internet de las Cosas
Situación problema
La ciudad donde vive Martina tiene inviernos muy fríos. Por eso, a veces debe dejar el calefactor encendido incluso cuando no está en casa. Ella quiere monitorear el funcionamiento del calefactor en esos momentos, para asegurarse de que no esté consumiendo más energía de la necesaria. Para lograrlo, necesita un sistema que le informe por internet la temperatura del ambiente en todo momento.
En esta tercera instancia se agrega IoT para relevar el estado de la temperatura. Aprovechando que el sensor DHT11 también mide la humedad, se agrega ese dato al registro, y todo se monitorea de forma remota.
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 la habitación que estamos calefaccionando.
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. 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 comandando el radiador, 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. Al finalizar, el panel replicará a distancia la evolución de las condiciones de la habitación.
Por último, necesitamos las credenciales que le permitirán a nuestro dispositivo publicar en la cuenta: en Adafruit IO, tocá el ícono de la llave (API Key). Vas a ver dos datos, el nombre de usuario (Username) y la clave (Active Key): anotalos, porque los vamos a copiar dentro del código en el paso 4. La clave es secreta: cualquiera que la tenga puede publicar en tus feeds.
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, 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 nivel introduce, además, un cambio para probar con seguridad: como en el original, reemplazamos el relé por un LED que enciende cuando el radiador debería estar calentando, así verificamos toda la lógica sin cablear 220 V. La programación del LED es exactamente igual a la del relé, de modo que una vez comprobado se podría volver a conectar el relé. El LED queda encendido fijo mientras la calefacción está activa (temperatura por debajo del objetivo) y apagado cuando la habitación ya alcanzó la temperatura deseada.
Ver el código completo · calefaccion-no-bloqueante.ino
// Calefaccion SIN delay(): el DHT11 mide la temperatura, el LCD 16x2 la
// muestra y el radiador se enciende mientras haga frio (temperatura por
// debajo del objetivo). Todo con millis() en lugar de delay(): cada vuelta de
// loop() pregunta si ya paso el intervalo y, si no, deja el procesador libre.
// Esta es la base directa de la version IoT (solo ESP32): sin codigo
// bloqueante, el micro puede sumar WiFi y MQTT sin desatender el sensor.
//
// !! PRUEBA SIN 220V !! Como en el original, en este nivel REEMPLAZAMOS el
// rele por un LED conectado al pin 13 para probar la logica sin cablear los
// 220V. La programacion del LED es identica a la del rele: cuando el LED se
// enciende, el radiador real se prenderia. Una vez verificado, se vuelve a
// conectar el rele (pin 3) con las precauciones de 220V del README.
//
// Plataforma: Arduino UNO.
// Conexion: dato del DHT11 -> pin 2; LED de prueba -> pin 13 (el UNO ademas
// tiene el LED integrado en ese pin); 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: Fragmento de codigo del Nivel Avanzado (Paso 5, patron millis,
// LED por if/else, sin la parte IoT) - Sistema de Calefaccion Automatico,
// Saberes Digitales (INET/EDUCAR 2019)
// Modernizacion: (1) constantes nombradas; millisAnterior como unsigned long
// (el long del original desborda la cuenta). (2) DHT de Adafruit con
// dht.begin(). (3) Se ignora una lectura fallida del DHT11 (isnan).
// CORRECCION DE LOGICA (ver README): el codigo original de este paso encendia
// el LED cuando la temperatura SUPERABA los 22 grados (texto y codigo heredados
// del proyecto de invernadero, donde el LED significaba "ventilar"). Para un
// sistema de CALEFACCION eso esta invertido: aca el radiador se enciende
// cuando la temperatura esta POR DEBAJO del objetivo (hace frio), coherente
// con el termostato del nivel intermedio.
#include "DHT.h"
#include <LiquidCrystal.h>
// Pin de datos del sensor y tipo de sensor (DHT11).
const int PIN_DHT = 2;
#define DHTTYPE DHT11
// En el avanzado, el original reemplaza el rele por un LED en el pin 13 para
// probar sin 220V. Encendido = el radiador se prenderia. Para el sistema real,
// volvé a poner el rele del pin 3 (la programacion es la misma).
const int PIN_LED = 13;
// Temperatura objetivo fija del original (22 grados). Por debajo de este
// valor hace frio y se enciende el radiador.
const float TEMP_OBJETIVO_C = 22.0;
// El DHT11 no admite mas 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 ultima medicion.
unsigned long millisAnterior = 0;
void setup() {
pinMode(PIN_LED, OUTPUT);
dht.begin();
lcd.begin(16, 2);
lcd.clear();
}
void loop() {
unsigned long millisActual = millis();
// ¿Ya paso el intervalo desde la ultima medicion? 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
// Una lectura fallida del DHT11 devuelve "no numerico": la salteamos.
if (!isnan(temperatura)) {
// Logica de calefaccion: encender si hace frio (por debajo del objetivo).
if (temperatura < TEMP_OBJETIVO_C) {
digitalWrite(PIN_LED, HIGH);
} else {
digitalWrite(PIN_LED, LOW);
}
// Mostramos la temperatura actual. Sin tildes ni Ñ (limitacion LCD).
lcd.setCursor(0, 0);
lcd.print("Temp Act: ");
lcd.print(temperatura);
}
}
// En este punto el procesador queda libre para realizar otras tareas (en la
// version IoT del ESP32, aca se atiende la conexion WiFi/MQTT).
} // Calefaccion SIN delay(): el DHT11 mide la temperatura, el LCD 16x2 (I2C) la
// muestra y el radiador se enciende mientras haga frio (temperatura por
// debajo del objetivo). Todo con millis() en lugar de delay(): cada vuelta de
// loop() pregunta si ya paso el intervalo y, si no, deja el procesador libre.
// Esta es la base directa de calefaccion-iot.ino: sin codigo bloqueante, el
// ESP32 puede sumar WiFi y MQTT sin desatender el sensor.
//
// !! PRUEBA SIN 220V !! Como en el original, en este nivel REEMPLAZAMOS el
// rele por un LED (aca en GPIO 16) para probar la logica sin cablear los 220V.
// La programacion del LED es identica a la del rele: cuando el LED se
// enciende, el radiador real se prenderia. Una vez verificado, se vuelve a
// conectar el rele (GPIO 25) con las precauciones de 220V del README.
//
// Plataforma: ESP32 (DevKit v1).
// Conexion: dato del DHT11 -> GPIO 4; LED de prueba externo -> GPIO 16;
// display LCD 1602 por I2C: SDA -> GPIO 21, SCL -> GPIO 22.
//
// ADAPTACION DE PLATAFORMA (ver README): el original usa un LCD Keypad Shield
// paralelo (6 pines) que NO entra en un ESP32; aca usamos un LCD 1602 con
// modulo I2C (2 cables, libreria LiquidCrystal_I2C, direccion tipica 0x27;
// probá 0x3F si no muestra nada). Nota electrica: el ESP32 trabaja a 3,3 V.
//
// Nota del original: en el LCD no usar tildes ni la letra Ñ.
//
// Librerias requeridas (Library Manager): "DHT sensor library" (Adafruit) +
// "Adafruit Unified Sensor", y "LiquidCrystal_I2C".
//
// Basado en: Fragmento de codigo del Nivel Avanzado (Paso 5, patron millis,
// LED por if/else, sin la parte IoT) - Sistema de Calefaccion Automatico,
// Saberes Digitales (INET/EDUCAR 2019)
// Modernizacion: constantes nombradas; millisAnterior como unsigned long; DHT
// de Adafruit con dht.begin(); LCD paralelo del original adaptado a I2C; se
// ignora la lectura fallida del DHT11 (isnan).
// CORRECCION DE LOGICA (ver README): el codigo original encendia el LED cuando
// la temperatura SUPERABA los 22 grados (texto heredado del proyecto de
// invernadero, donde el LED significaba "ventilar"). Para un sistema de
// CALEFACCION eso esta invertido: aca el radiador se enciende cuando la
// temperatura esta POR DEBAJO del objetivo (hace frio).
#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
// En el avanzado se reemplaza el rele por un LED (GPIO 16) para probar sin
// 220V. Encendido = el radiador se prenderia. Para el sistema real, volvé a
// poner el rele del GPIO 25 (la programacion es la misma).
const int PIN_LED = 16;
// Direccion 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;
// Temperatura objetivo fija del original (22 grados). Por debajo de este
// valor hace frio y se enciende el radiador.
const float TEMP_OBJETIVO_C = 22.0;
// El DHT11 no admite mas 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 ultima medicion.
unsigned long millisAnterior = 0;
void setup() {
pinMode(PIN_LED, OUTPUT);
dht.begin();
lcd.init();
lcd.backlight();
lcd.clear();
}
void loop() {
unsigned long millisActual = millis();
// ¿Ya paso el intervalo desde la ultima medicion? 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
// Una lectura fallida del DHT11 devuelve "no numerico": la salteamos.
if (!isnan(temperatura)) {
// Logica de calefaccion: encender si hace frio (por debajo del objetivo).
if (temperatura < TEMP_OBJETIVO_C) {
digitalWrite(PIN_LED, HIGH);
} else {
digitalWrite(PIN_LED, LOW);
}
// Mostramos la temperatura actual. Sin tildes ni Ñ (limitacion LCD).
lcd.setCursor(0, 0);
lcd.print("Temp Act: ");
lcd.print(temperatura);
}
}
// En este punto el procesador queda libre para realizar otras tareas (en
// calefaccion-iot.ino, aca se atiende la conexion WiFi/MQTT).
} En el Arduino UNO el LED de prueba es el del pin 13 (la constante PIN_LED del código), que ya
está integrado en la placa: no hace falta cablear nada para verlo.
En el ESP32 usamos un LED externo en el GPIO 16 (la constante PIN_LED del código), un pin de
salida seguro. No usamos el LED integrado del DevKit, que está en el GPIO 2, un strapping pin que
interviene en el arranque de la placa y conviene dejar libre.
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 invernadero 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 —mide cada 2 segundos y decide según la temperatura objetivo—, con un cambio visible: el
aviso se ve en el LED del pin 13 (o GPIO 16), encendido fijo mientras la temperatura está por
debajo del objetivo y apagado cuando lo alcanza. Que por fuera se comporte casi igual 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 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 (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.
Un detalle que corregimos del original: su programa IoT publicaba en el feed humedad una variable
humedad que nunca leía del sensor (no estaba declarada). En esta reedición leemos la humedad
real con dht.readHumidity() y publicamos ese valor, aprovechando que el DHT11 mide humedad y
temperatura a la vez.
Ver el código completo · calefaccion-iot.ino
Nota: Este código está disponible para ESP32. Cambiá la plataforma con el selector del encabezado para verlo y practicarlo.
// Calefaccion con monitoreo IoT via Adafruit IO (MQTT).
// La logica no bloqueante de calefaccion-no-bloqueante.ino sigue midiendo con
// el DHT11, mostrando en el LCD (I2C) y accionando el radiador (aca, el LED de
// prueba) segun la temperatura; ademas publica dos feeds para ver el ambiente
// desde el Panel de Control de Adafruit (como en la guia):
//
// "temperatura": grados Celsius.
// "humedad": humedad relativa en % (aprovechando que el DHT11 tambien la mide).
//
// !! PRUEBA SIN 220V !! Igual que en el nivel avanzado, se REEMPLAZA el rele
// por un LED (GPIO 16) para probar sin cablear los 220V. Encendido = el
// radiador real se prenderia. Para el sistema real, volvé a poner el rele
// (GPIO 25) con las precauciones de 220V del README.
//
// Plataforma: ESP32 (DevKit v1) - usa su WiFi integrado.
// Conexion: dato del DHT11 -> GPIO 4; LED de prueba externo -> GPIO 16;
// display LCD 1602 por I2C: SDA -> GPIO 21, SCL -> GPIO 22.
//
// ADAPTACION DE PLATAFORMA (ver README): el original usa un LCD Keypad Shield
// paralelo (6 pines) que no entra en un ESP32; aca usamos un LCD 1602 con
// modulo I2C (2 cables, libreria LiquidCrystal_I2C, direccion tipica 0x27;
// probá 0x3F si no muestra nada). Nota electrica: el ESP32 trabaja a 3,3 V.
//
// Nota del original: en el LCD no usar tildes ni la letra Ñ.
//
// Librerias requeridas (Library Manager): "DHT sensor library" (Adafruit) +
// "Adafruit Unified Sensor", "LiquidCrystal_I2C" y "Adafruit MQTT Library".
//
// Basado en: Fragmento de codigo del Nivel Avanzado (Paso 6, IoT) - Sistema de
// Calefaccion Automatico, Saberes Digitales (INET/EDUCAR 2019)
// Modernizacion: (1) el modulo OBLOQ y la libreria ObloqAdafruit quedaron
// discontinuados; se reemplazan por el WiFi nativo del ESP32 (WiFi.h) y MQTT
// contra io.adafruit.com. (2) BUG DEL ORIGINAL CORREGIDO: el original publicaba
// olq.publish("humedad", humedad) pero la variable "humedad" NUNCA se leia del
// sensor (no compilaba / publicaba basura); aca se lee humedad de verdad con
// dht.readHumidity() y se publica ese valor real. (3) El original publicaba
// temperatura y humedad en CADA medicion (1 s = 120 publicaciones/min, muy por
// encima del limite gratuito de Adafruit IO de 30/min); aca se publican SOLO
// cuando cambian de forma apreciable (temperatura >= 0,5 grados; humedad >= 1 %)
// respecto de lo ultimo publicado. (4) Se ignora una lectura fallida del DHT11
// (isnan). (5) El LCD paralelo del original se adapta a I2C.
// CORRECCION DE LOGICA (ver README): el original encendia el LED cuando la
// temperatura SUPERABA los 22 grados (herencia del proyecto de invernadero,
// "ventilar"); para una CALEFACCION eso esta invertido, asi que aca el radiador
// se enciende cuando la temperatura esta POR DEBAJO del objetivo (hace frio).
#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 icono de la "llave" (igual que en la guia 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 de prueba que reemplaza al rele para trabajar sin 220V (ver encabezado).
const int PIN_LED = 16;
// Direccion 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;
// Temperatura objetivo fija del original (22 grados): por debajo hace frio y
// se enciende el radiador.
const float TEMP_OBJETIVO_C = 22.0;
// El DHT11 no admite mas de una lectura por segundo; medimos cada 2 s.
const unsigned long INTERVALO_MEDICION_MS = 2000;
// Un valor se re-publica solo si cambio al menos esto respecto del ultimo
// publicado: evita gastar el cupo gratuito de Adafruit IO (30 pub/min).
const float UMBRAL_PUBLICACION_TEMP_C = 0.5;
const float UMBRAL_PUBLICACION_HUM = 1.0;
// Si se cae la conexion 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 ultima medicion y del ultimo intento de conexion MQTT.
unsigned long millisAnterior = 0;
unsigned long millisUltimoIntentoMqtt = 0;
// Ultimo valor que llego a publicarse de cada feed. Separarlo de la medicion
// permite publicar solo los cambios y ponerse al dia tras una desconexion.
// Se arranca con valores imposibles para forzar la primera publicacion.
float temperaturaPublicada = -1000.0;
float humedadPublicada = -1000.0;
bool hayMedicion = false;
// Diferencia sin signo entre dos numeros (para comparar contra el umbral).
float diferenciaAbsoluta(float a, float b) {
if (a > b) {
return a - b;
}
return b - a;
}
// Mantiene viva la conexion MQTT sin bloquear el sensor: si esta caida,
// 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; // todavia no toca reintentar
}
millisUltimoIntentoMqtt = millisActual;
Serial.print("Conectando a Adafruit IO... ");
int8_t resultado = mqtt.connect(); // 0 = conexion exitosa
if (resultado == 0) {
Serial.println("conectado.");
} else {
Serial.println(mqtt.connectErrorString(resultado));
mqtt.disconnect(); // deja el socket limpio para el proximo intento
}
}
// Publica cada feed SOLO si cambio lo suficiente desde la ultima publicacion
// exitosa. Si publish() falla o no hay conexion, no se actualiza el registro:
// se reintenta en la proxima vuelta.
void publicarCambios(float temperatura, float humedad) {
if (!mqtt.connected() || !hayMedicion) {
return; // sin internet o sin datos reales todavia: 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, OUTPUT);
dht.begin();
lcd.init();
lcd.backlight();
lcd.clear();
// Conexion WiFi. Esperar aca con delay() es aceptable porque el ciclo de
// medicion todavia no arranco; 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();
// --- Medicion, control del radiador 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 numerico": la salteamos para
// no mostrar ni publicar basura.
if (!isnan(temperatura) && !isnan(humedad)) {
hayMedicion = true;
// Logica de calefaccion: encender si hace frio (por debajo del objetivo).
if (temperatura < TEMP_OBJETIVO_C) {
digitalWrite(PIN_LED, HIGH);
} else {
digitalWrite(PIN_LED, LOW);
}
// Fila 0: temperatura. Fila 1: humedad. Sin tildes ni Ñ (limitacion LCD).
lcd.setCursor(0, 0);
lcd.print("Temp Act: ");
lcd.print(temperatura);
lcd.setCursor(0, 1);
lcd.print("Hum.: ");
lcd.print(humedad);
// Publicamos temperatura y humedad (solo si cambiaron lo suficiente).
publicarCambios(temperatura, humedad);
}
}
// --- Mantenimiento de la conexion IoT, en cada vuelta ---
// Gracias a que la medicion no bloquea, esto se atiende continuamente: esta
// es la razon de fondo por la que la guia abandona delay() antes de IoT.
mantenerConexionMqtt();
} Así queda funcionando el tablero que permite monitorear la calefacción a distancia. Podés ver el sistema completo —el sensor midiendo, el aviso 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 monitorear a controlar
Hoy el sistema informa la temperatura y la humedad, pero encender el radiador sigue siendo decisión local del termostato. Piensen en grupo: ¿cómo harían para controlar el radiador a distancia, encendiéndolo o apagándolo desde el panel de Adafruit IO? Pista: además de publicar en un feed, un dispositivo puede suscribirse a otro y reaccionar a lo que reciba. ¿Y si publicaran en un tercer feed el estado “calentando / en reposo”, para saber a distancia si el radiador está prendido?
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, la calefacción de Martina 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 de la habitación. Con
esto queda completo el recorrido del proyecto. Si el grupo quiere seguir, en la ficha del proyecto
están las extensiones sugeridas: controlar el radiador a distancia por IoT, sumar un sistema de
humidificación, o combinarlo con el proyecto “Sistema Lumínico Automático” para seguir trabajando
el uso eficiente de la energía.
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 para probar sin 220 V el aviso se muestra con un LED encendido fijo (pin 13 en el UNO, GPIO 16 en el ESP32) en lugar del relé.
- 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 ambiente publicadas por el dispositivo (variante ESP32), con la humedad leída de verdad del DHT11.