Monitorear la cerradura con Internet de las Cosas
Recorrido del nivel
Esa barra de arriba te dice en qué paso estás. Tocala para ver el índice completo y saltar a cualquier paso.
Introducción a Internet de las Cosas (IoT)
Internet de las Cosas (en inglés Internet of Things, abreviado IoTIoT (Internet de las Cosas)Interconexión digital de objetos cotidianos con internet. Permite, por ejemplo, enviar los datos de un sensor a una central y visualizarlos desde un dispositivo móvil. Requiere un dispositivo capaz de conectarse a internet y un servidor que reciba y aloje los datos.) es un concepto que refiere a la interconexión digital de objetos cotidianos con internet. Por ejemplo, enviar el estado de una cerradura a una central donde se recopile la información, para verla desde el celular sin estar en la casa.
Para incorporar IoT a nuestro proyecto necesitamos:
- Un dispositivo capaz de conectarse a internet.
- Un servidor que reciba y aloje los datos.
Lo resolveremos 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: 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, más simple en el circuito.
El ESP32 cumple los dos roles a la vez: es el microcontrolador que ya está leyendo el teclado, moviendo el servo y manejando los LED y el buzzer, 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
Creamos 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 uso gratuito orientada a lo educativo, que centraliza la información que reportan los dispositivos. Empezamos con una cuenta en io.adafruit.com.
El trabajo tiene dos partes: crear el feed 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 y a la que puede suscribirse. Desde la sección Feeds creamos uno, con este nombre exacto (sin tildes, porque forma parte de la dirección con la que se publica):
cerradura: recibirá el estado de la cerradura, con la convención 1 = abierta, 0 = cerrada.
Luego, desde Dashboards, creamos un panel y le agregamos un bloque del tipo Indicator
(indicador): un círculo que cambia de color según el valor del feed. Lo configuramos para que esté
en verde cuando cerradura sea 1 (abierta) y en rojo cuando sea distinto de 1 (cerrada),
replicando a distancia los mismos colores de los LED del dispositivo.
Por último, necesitamos las credenciales para que el dispositivo pueda publicar: en Adafruit IO, tocá el ícono de la llave (API Key). Vas a ver el nombre de usuario (Username) y la clave (Active Key): anotalos, porque los copiaremos en el código del paso 5. La clave es secreta: cualquiera que la tenga puede publicar en tus feeds.
Programar sin código bloqueante
Antes de sumar IoT hay que hacer una aclaración sobre la función delay() que venimos usando
para los pitidos del buzzer. 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.: mientras el programa está dentro de esa función, se detiene todo el
procesamiento hasta que se cumple el tiempo. El programa queda “colgado” ese rato.
Al usar IoT, eso es un problema: mientras el procesamiento está detenido, el sistema no puede
atender otras tareas que corren en simultáneo, como mantener viva la conexión a internet y
publicar. Si el buzzer estuviera sonando con delay(), durante ese tiempo el dispositivo estaría
“sordo” a la red.
La alternativa no bloqueante es la función millis(), que devuelve el tiempo transcurrido
desde que arrancó el sistema, como un cronómetro que se consulta sin detener nada. Con eso armamos
una pequeña máquina de estadosMáquina de estadosForma de organizar un programa donde una variable guarda el estado actual del sistema y, en cada vuelta del loop, solo se ejecuta lo que corresponde a ese estado. El semáforo no bloqueante usa ocho estados que se recorren según el tiempo transcurrido. para el buzzer: en lugar de
“sonar y esperar”, el programa programa los pitidos (cuántos y de qué tono) y, en cada vuelta
de loop(), actualizarBuzzer() dispara el siguiente cuando corresponde. El loop() nunca se
frena: atiende el teclado y el buzzer a la vez.
Ver el código completo · cerradura-no-bloqueante.ino
// Cerradura automatizada — Nivel Avanzado (Arduino UNO), NO BLOQUEANTE.
//
// Misma cerradura por teclado del nivel intermedio, pero SIN delay(): los
// pitidos del buzzer se manejan con una pequena maquina de estados basada en
// millis(). Asi loop() nunca se detiene y el sistema queda listo para tareas
// concurrentes (como el monitoreo IoT). El teclado 4x4 lee la clave, la
// cerradura conmuta con clave correcta y los LED indican el estado
// (verde = abierta, rojo = cerrada).
//
// Clave del original: '1' '5' '9' 'D'.
//
// >>> IoT EN UNO: CONCEPTUAL <<<
// El nivel avanzado del original agrega IoT, pero su implementacion en UNO es
// IRREALIZABLE: usa el modulo OBLOQ sobre SoftwareSerial cableado a los pines
// 0/1 en el codigo (que son el USB/serial de programacion: lo rompe) y a los
// pines 8/9 en el texto (que CHOCAN con dos filas del teclado 4x4). Ademas el
// OBLOQ y su libreria ObloqAdafruit estan discontinuados. Por eso, en UNO el
// IoT queda como EXPLICACION CONCEPTUAL y la practica se hace con el ESP32
// (WiFi nativo), en esp32/nivel-avanzado/cerradura-iot.ino. Este sketch de UNO
// deja el sistema NO BLOQUEANTE justamente como base sobre la que el IoT
// podria montarse — la misma progresion didactica que ensena el original.
//
// Plataforma: Arduino UNO. Proyecto de BAJA TENSION: sin 220 V ni agua.
//
// Basado en: Fragmentos de codigo de los Niveles Intermedio y Avanzado —
// Cerradura Automatica, Saberes Digitales (INET/EDUCAR 2019).
// Desvios (ver README): pines unificados; buzzer con tone(); pitidos NO
// bloqueantes con millis(); IoT conceptual (se resuelve en ESP32).
#include <Keypad.h>
#if defined(ESP32)
#include <ESP32Servo.h>
#else
#include <Servo.h>
#endif
// --- Pines (esquema UNIFICADO del contrato) ---
const int PIN_SERVO = 10;
const int PIN_LED_VERDE = 11; // encendido = ABIERTA
const int PIN_BUZZER = 12;
const int PIN_LED_ROJO = 13; // encendido = CERRADA
// Angulos del servo (unificados en los tres niveles).
const int ANGULO_ABIERTA = 90;
const int ANGULO_CERRADA = 0;
// --- Teclado matricial 4x4 ---
const byte FILAS = 4;
const byte COLUMNAS = 4;
char teclas[FILAS][COLUMNAS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
byte pinesFilas[FILAS] = {9, 8, 7, 6};
byte pinesColumnas[COLUMNAS] = {5, 4, 3, 2};
Keypad teclado = Keypad(makeKeymap(teclas), pinesFilas, pinesColumnas,
FILAS, COLUMNAS);
// --- Clave (la del original) y buffer de lo tecleado ---
const byte LARGO_CLAVE = 4;
char clave[LARGO_CLAVE] = {'1', '5', '9', 'D'};
char tecleado[LARGO_CLAVE] = {0, 0, 0, 0};
byte indice = 0;
// --- Buzzer NO bloqueante ---
// tone(pin, freq, duracion) ya suena sin bloquear (lo maneja un timer). Para
// encadenar varios pitidos con pausas, programamos cada uno con millis() en
// vez de usar delay().
const unsigned int FREQ_OK = 1000; // Hz, agudo = correcta
const unsigned int FREQ_ERROR = 400; // Hz, grave = incorrecta
const unsigned long DURACION_BEEP_MS = 200;
const unsigned long PERIODO_BEEP_MS = 500; // 200 ms de tono + 300 ms de pausa
byte beepsPendientes = 0; // cuantos pitidos quedan por emitir
unsigned int freqBeep = 0; // frecuencia del pitido en curso
unsigned long proximoBeepMs = 0; // cuando disparar el proximo pitido
// Estado de la puerta: true = abierta, false = cerrada.
bool puertaAbierta = false;
Servo cerradura;
void mostrarEstadoEnLeds() {
digitalWrite(PIN_LED_VERDE, puertaAbierta ? HIGH : LOW);
digitalWrite(PIN_LED_ROJO, puertaAbierta ? LOW : HIGH);
}
// Programa una serie de pitidos (los emite actualizarBuzzer sin bloquear).
void programarBeeps(byte cantidad, unsigned int frecuencia) {
beepsPendientes = cantidad;
freqBeep = frecuencia;
proximoBeepMs = millis(); // el primero suena ya
}
// Debe llamarse en cada vuelta de loop(): dispara el proximo pitido cuando
// corresponde, sin detener el programa.
void actualizarBuzzer() {
if (beepsPendientes == 0) {
return;
}
if (millis() >= proximoBeepMs) {
tone(PIN_BUZZER, freqBeep, DURACION_BEEP_MS); // no bloqueante
beepsPendientes--;
proximoBeepMs = millis() + PERIODO_BEEP_MS;
}
}
bool claveCorrecta() {
for (byte i = 0; i < LARGO_CLAVE; i++) {
if (tecleado[i] != clave[i]) {
return false;
}
}
return true;
}
void setup() {
pinMode(PIN_LED_VERDE, OUTPUT);
pinMode(PIN_LED_ROJO, OUTPUT);
pinMode(PIN_BUZZER, OUTPUT);
cerradura.attach(PIN_SERVO);
puertaAbierta = false; // arranca CERRADA (LED rojo)
cerradura.write(ANGULO_CERRADA);
mostrarEstadoEnLeds();
}
void loop() {
// 1) Atender el teclado (no bloquea: getKey devuelve 0 si no hay tecla).
char tecla = teclado.getKey();
if (tecla) {
tecleado[indice] = tecla;
indice++;
if (indice == LARGO_CLAVE) {
indice = 0;
if (claveCorrecta()) {
programarBeeps(1, FREQ_OK); // un pitido de OK
puertaAbierta = !puertaAbierta; // conmuta la cerradura
cerradura.write(puertaAbierta ? ANGULO_ABIERTA : ANGULO_CERRADA);
mostrarEstadoEnLeds();
} else {
programarBeeps(2, FREQ_ERROR); // doble pitido de error
}
}
}
// 2) Atender el buzzer sin bloquear.
actualizarBuzzer();
// 3) (Aca iria el mantenimiento IoT: en UNO es conceptual — ver ESP32.)
} // Cerradura automatizada — Nivel Avanzado (ESP32 DevKit v1), NO BLOQUEANTE.
//
// Misma cerradura por teclado del nivel intermedio, pero SIN delay(): los
// pitidos del buzzer se manejan con una pequena maquina de estados basada en
// millis(). Asi loop() nunca se detiene y el sistema queda listo para sumar el
// monitoreo IoT (ver cerradura-iot.ino en esta misma carpeta). El teclado 4x4
// lee la clave, la cerradura conmuta con clave correcta y los LED indican el
// estado (verde = abierta, rojo = cerrada).
//
// Clave del original: '1' '5' '9' 'D'.
//
// Este sketch es el paso previo al IoT: deja toda la logica NO bloqueante para
// que agregar WiFi + MQTT (cerradura-iot.ino) no desatienda el teclado.
//
// Plataforma: ESP32 (DevKit v1). Servo con ESP32Servo; buzzer con tone().
// Nota electrica: 3,3 V (alimentar el servo desde 5 V con GND comun).
// Proyecto de BAJA TENSION: sin 220 V ni agua.
//
// Basado en: Fragmentos de codigo de los Niveles Intermedio y Avanzado —
// Cerradura Automatica, Saberes Digitales (INET/EDUCAR 2019).
// Desvios (ver README): pines GPIO seguros del contrato; buzzer con tone();
// pitidos NO bloqueantes con millis().
#include <Keypad.h>
#if defined(ESP32)
#include <ESP32Servo.h>
#else
#include <Servo.h>
#endif
// --- Pines (GPIO seguros del contrato) ---
const int PIN_SERVO = 13;
const int PIN_LED_VERDE = 25; // encendido = ABIERTA
const int PIN_LED_ROJO = 26; // encendido = CERRADA
const int PIN_BUZZER = 14;
// Angulos del servo (unificados en los tres niveles).
const int ANGULO_ABIERTA = 90;
const int ANGULO_CERRADA = 0;
// --- Teclado matricial 4x4 ---
const byte FILAS = 4;
const byte COLUMNAS = 4;
char teclas[FILAS][COLUMNAS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
byte pinesFilas[FILAS] = {16, 17, 18, 19};
byte pinesColumnas[COLUMNAS] = {21, 22, 23, 27};
Keypad teclado = Keypad(makeKeymap(teclas), pinesFilas, pinesColumnas,
FILAS, COLUMNAS);
// --- Clave (la del original) y buffer de lo tecleado ---
const byte LARGO_CLAVE = 4;
char clave[LARGO_CLAVE] = {'1', '5', '9', 'D'};
char tecleado[LARGO_CLAVE] = {0, 0, 0, 0};
byte indice = 0;
// --- Buzzer NO bloqueante ---
const unsigned int FREQ_OK = 1000; // Hz, agudo = correcta
const unsigned int FREQ_ERROR = 400; // Hz, grave = incorrecta
const unsigned long DURACION_BEEP_MS = 200;
const unsigned long PERIODO_BEEP_MS = 500; // 200 ms de tono + 300 ms de pausa
byte beepsPendientes = 0;
unsigned int freqBeep = 0;
unsigned long proximoBeepMs = 0;
// Estado de la puerta: true = abierta, false = cerrada.
bool puertaAbierta = false;
Servo cerradura;
void mostrarEstadoEnLeds() {
digitalWrite(PIN_LED_VERDE, puertaAbierta ? HIGH : LOW);
digitalWrite(PIN_LED_ROJO, puertaAbierta ? LOW : HIGH);
}
void programarBeeps(byte cantidad, unsigned int frecuencia) {
beepsPendientes = cantidad;
freqBeep = frecuencia;
proximoBeepMs = millis();
}
void actualizarBuzzer() {
if (beepsPendientes == 0) {
return;
}
if (millis() >= proximoBeepMs) {
tone(PIN_BUZZER, freqBeep, DURACION_BEEP_MS); // no bloqueante
beepsPendientes--;
proximoBeepMs = millis() + PERIODO_BEEP_MS;
}
}
bool claveCorrecta() {
for (byte i = 0; i < LARGO_CLAVE; i++) {
if (tecleado[i] != clave[i]) {
return false;
}
}
return true;
}
void setup() {
pinMode(PIN_LED_VERDE, OUTPUT);
pinMode(PIN_LED_ROJO, OUTPUT);
pinMode(PIN_BUZZER, OUTPUT);
cerradura.attach(PIN_SERVO);
puertaAbierta = false; // arranca CERRADA (LED rojo)
cerradura.write(ANGULO_CERRADA);
mostrarEstadoEnLeds();
}
void loop() {
char tecla = teclado.getKey();
if (tecla) {
tecleado[indice] = tecla;
indice++;
if (indice == LARGO_CLAVE) {
indice = 0;
if (claveCorrecta()) {
programarBeeps(1, FREQ_OK);
puertaAbierta = !puertaAbierta;
cerradura.write(puertaAbierta ? ANGULO_ABIERTA : ANGULO_CERRADA);
mostrarEstadoEnLeds();
} else {
programarBeeps(2, FREQ_ERROR);
}
}
}
actualizarBuzzer();
} Conectar el dispositivo a internet
El material original conectaba al Arduino UNO un módulo OBLOQ por dos pines serie. Además de
estar discontinuado, esa implementación en el UNO era irrealizable: el código lo cableaba
por SoftwareSerial a los pines 0 y 1, que son el USB/serial de programación (lo dejarían sin
poder subirse código); y el texto lo mandaba a los pines 8 y 9, que chocan con dos filas del
teclado 4x4 de este proyecto. En cualquiera de las dos formas, el sistema no puede funcionar.
Por eso, la parte práctica de conexión se hace con la variante ESP32: cambiá la plataforma con el selector. Todo lo conceptual —qué se publica, por qué el código no debe bloquear— ya lo tenés resuelto con tu UNO en los pasos anteriores.
En el ESP32 no hay que conectar ningún módulo ni cable extra: la conexión se resuelve por software, con el WiFi integrado de la placa. Solo necesitamos instalar la librería que habla con Adafruit IO: desde el Gestor de bibliotecas del Arduino IDE, buscá e instalá la “Adafruit MQTT Library”. Es la que reemplaza a la biblioteca ObloqAdafruit del original (discontinuada junto con su módulo) y publica los datos por el protocolo MQTT.
Programar IoT
Con el panel listo y el código ya libre de bloqueos, falta el último eslabón: que el sistema publique el estado de la cerradura en Adafruit IO.
Esta parte práctica corresponde a la variante ESP32 (el Arduino UNO clásico no tiene WiFi, y el OBLOQ del original era irrealizable en este circuito). Cambiá la plataforma con el selector para ver el código y las instrucciones. Todo lo conceptual —qué se publica en el feed, el porqué del código no bloqueante— ya lo tenés de 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 por MQTT. Antes de subirlo, completá 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 es por evento: cada vez que la puerta cambia de estado (una clave correcta
la abre o la cierra), el programa publica en el feed cerradura el nuevo valor —1 si quedó
abierta, 0 si quedó cerrada—. No publica en cada vuelta del loop(), sino solo en el instante
del cambio, así que el cupo gratuito de Adafruit IO (30 publicaciones por minuto) queda de sobra.
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 como bonus del mismo diseño: si internet se cae, la
cerradura sigue funcionando localmente, y al recuperar la conexión el panel se pone al día con el
estado actual.
Ver el código completo · cerradura-iot.ino
Nota: Este código está disponible para ESP32. Cambiá la plataforma con el selector del encabezado para verlo y practicarlo.
// Cerradura automatizada — Nivel Avanzado + IoT (ESP32 DevKit v1).
//
// Toma la cerradura por teclado NO bloqueante (cerradura-no-bloqueante.ino) y
// le suma monitoreo remoto: cada vez que la puerta CAMBIA de estado, publica
// en el feed "cerradura" de Adafruit IO:
//
// 1 = ABIERTA 0 = CERRADA
//
// Asi el abuelo puede ver desde el celular si la puerta se abrio o cerro, tal
// como plantea la situacion-problema del nivel avanzado. La publicacion es
// EVENT-DRIVEN: solo se publica en el momento del cambio (no en cada vuelta),
// por lo que se respeta de sobra el cupo gratuito de Adafruit IO (30/min).
//
// Clave del original: '1' '5' '9' 'D'.
//
// Plataforma: ESP32 (DevKit v1) — WiFi integrado. Servo con ESP32Servo; buzzer
// con tone(). Nota electrica: 3,3 V (alimentar el servo desde 5 V, GND comun).
// Proyecto de BAJA TENSION: sin 220 V ni agua.
//
// Basado en: Fragmento de codigo del Nivel Avanzado (Paso 6, publicacion del
// estado de la cerradura) — Cerradura Automatica, Saberes Digitales
// (INET/EDUCAR 2019).
// Desvios / modernizaciones (ver README):
// (1) OBLOQ IRREALIZABLE EN EL ORIGINAL: usaba SoftwareSerial(0,1) — que son
// el USB/serial de programacion del UNO (lo rompe) — y el texto lo cableaba
// a (8,9), que CHOCAN con dos filas del teclado 4x4. El OBLOQ y su libreria
// ObloqAdafruit ademas estan discontinuados. Se reemplaza por el WiFi
// NATIVO del ESP32 + MQTT contra io.adafruit.com (libreria "Adafruit MQTT
// Library"). El IoT de este proyecto solo es realizable en ESP32.
// (2) Publicacion EVENT-DRIVEN (solo al cambiar el estado), no en cada vuelta.
// (3) Reconexion MQTT NO bloqueante (reintento cada 5 s), sin frenar teclado
// ni buzzer.
// (4) Pines GPIO seguros del contrato; buzzer con tone(); pitidos NO
// bloqueantes con millis().
#include <WiFi.h>
#include <Keypad.h>
#include <ESP32Servo.h>
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"
// ---------------------------------------------------------------------------
// Credenciales: reemplazar por las propias. El usuario y la key de Adafruit se
// copian desde io.adafruit.com con el icono de la "llave" (como en la guia).
// ---------------------------------------------------------------------------
#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;
// --- Pines (GPIO seguros del contrato) ---
const int PIN_SERVO = 13;
const int PIN_LED_VERDE = 25; // encendido = ABIERTA
const int PIN_LED_ROJO = 26; // encendido = CERRADA
const int PIN_BUZZER = 14;
// Angulos del servo (unificados en los tres niveles).
const int ANGULO_ABIERTA = 90;
const int ANGULO_CERRADA = 0;
// --- Teclado matricial 4x4 ---
const byte FILAS = 4;
const byte COLUMNAS = 4;
char teclas[FILAS][COLUMNAS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
byte pinesFilas[FILAS] = {16, 17, 18, 19};
byte pinesColumnas[COLUMNAS] = {21, 22, 23, 27};
Keypad teclado = Keypad(makeKeymap(teclas), pinesFilas, pinesColumnas,
FILAS, COLUMNAS);
// --- Clave (la del original) y buffer de lo tecleado ---
const byte LARGO_CLAVE = 4;
char clave[LARGO_CLAVE] = {'1', '5', '9', 'D'};
char tecleado[LARGO_CLAVE] = {0, 0, 0, 0};
byte indice = 0;
// --- Buzzer NO bloqueante ---
const unsigned int FREQ_OK = 1000; // Hz, agudo = correcta
const unsigned int FREQ_ERROR = 400; // Hz, grave = incorrecta
const unsigned long DURACION_BEEP_MS = 200;
const unsigned long PERIODO_BEEP_MS = 500;
byte beepsPendientes = 0;
unsigned int freqBeep = 0;
unsigned long proximoBeepMs = 0;
// Estado de la puerta: true = abierta, false = cerrada.
bool puertaAbierta = false;
// --- WiFi + MQTT (Adafruit IO) ---
WiFiClient clienteWifi;
Adafruit_MQTT_Client mqtt(&clienteWifi, SERVIDOR_MQTT, PUERTO_MQTT,
IO_USERNAME, IO_KEY);
Adafruit_MQTT_Publish feedCerradura(&mqtt, IO_USERNAME "/feeds/cerradura");
// Reintento de conexion MQTT sin bloquear.
const unsigned long INTERVALO_REINTENTO_MQTT_MS = 5000;
unsigned long millisUltimoIntentoMqtt = 0;
// Ultimo valor publicado con exito (-1 fuerza la primera publicacion).
int cerraduraPublicada = -1;
Servo cerradura;
void mostrarEstadoEnLeds() {
digitalWrite(PIN_LED_VERDE, puertaAbierta ? HIGH : LOW);
digitalWrite(PIN_LED_ROJO, puertaAbierta ? LOW : HIGH);
}
void programarBeeps(byte cantidad, unsigned int frecuencia) {
beepsPendientes = cantidad;
freqBeep = frecuencia;
proximoBeepMs = millis();
}
void actualizarBuzzer() {
if (beepsPendientes == 0) {
return;
}
if (millis() >= proximoBeepMs) {
tone(PIN_BUZZER, freqBeep, DURACION_BEEP_MS); // no bloqueante
beepsPendientes--;
proximoBeepMs = millis() + PERIODO_BEEP_MS;
}
}
bool claveCorrecta() {
for (byte i = 0; i < LARGO_CLAVE; i++) {
if (tecleado[i] != clave[i]) {
return false;
}
}
return true;
}
// Mantiene viva la conexion MQTT sin bloquear: si esta caida, reintenta como
// mucho una vez cada 5 s (mismo patron que el resto de la coleccion).
void mantenerConexionMqtt() {
if (mqtt.connected()) {
return;
}
unsigned long millisActual = millis();
if (millisActual - millisUltimoIntentoMqtt < INTERVALO_REINTENTO_MQTT_MS) {
return;
}
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();
}
}
// Publica el estado SOLO cuando cambia (event-driven). Si no hay conexion o el
// publish falla, no se actualiza el registro: se reintenta en el proximo cambio
// o cuando vuelva la conexion.
void publicarEstadoSiCambio() {
int valor = puertaAbierta ? 1 : 0;
if (valor == cerraduraPublicada) {
return; // sin cambios: nada que publicar
}
if (!mqtt.connected()) {
return; // sin internet: se publicara cuando reconecte
}
if (feedCerradura.publish((int32_t) valor)) {
cerraduraPublicada = valor;
}
}
void setup() {
Serial.begin(115200);
pinMode(PIN_LED_VERDE, OUTPUT);
pinMode(PIN_LED_ROJO, OUTPUT);
pinMode(PIN_BUZZER, OUTPUT);
cerradura.attach(PIN_SERVO);
puertaAbierta = false; // arranca CERRADA (LED rojo)
cerradura.write(ANGULO_CERRADA);
mostrarEstadoEnLeds();
// Conexion WiFi. Bloquear aca con delay() es aceptable: el ciclo de teclado
// todavia no arranco. Una vez en 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() {
// 1) Teclado (no bloquea).
char tecla = teclado.getKey();
if (tecla) {
tecleado[indice] = tecla;
indice++;
if (indice == LARGO_CLAVE) {
indice = 0;
if (claveCorrecta()) {
programarBeeps(1, FREQ_OK);
puertaAbierta = !puertaAbierta;
cerradura.write(puertaAbierta ? ANGULO_ABIERTA : ANGULO_CERRADA);
mostrarEstadoEnLeds();
publicarEstadoSiCambio(); // publica el cambio al instante
} else {
programarBeeps(2, FREQ_ERROR);
}
}
}
// 2) Buzzer no bloqueante.
actualizarBuzzer();
// 3) Mantenimiento IoT en cada vuelta. Si el publish previo fallo por estar
// sin conexion, al reconectar se reintenta el estado actual.
mantenerConexionMqtt();
publicarEstadoSiCambio();
} Podés ver el sistema completo —la clave abriendo la cerradura y el panel reflejando el cambio de estado— 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 comandar
Hoy el sistema avisa a distancia si la cerradura está abierta o cerrada, pero abrirla sigue siendo presencial (hay que tipear la clave en el teclado). Piensen en grupo: ¿cómo harían para abrir la cerradura desde el celular, a través del mismo Adafruit IO? Pista: en vez de solo publicar en un feed, el dispositivo tendría que suscribirse a uno y reaccionar a lo que llega. ¿Y qué nuevos problemas de seguridad aparecen cuando una puerta se puede abrir por internet?
Verificá
Con el programa cargado y el ESP32 conectado a tu red WiFi, abrí el panel de Adafruit IO en el
celular o la computadora. Tipeá la clave correcta en el teclado: el indicador cerradura tiene
que ponerse verde (1, abierta). Volvé a tipearla: la cerradura cierra y el indicador pasa a
rojo (0), con una pequeña demora de red. Como se publica por cambios, entre apertura y
apertura no llega nada nuevo: es lo esperado. Si el indicador no cambia, revisá las credenciales y
el nombre de la red.
Cierre del nivel
Al terminar este nivel, la cerradura dejó de ser solo 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 lado, si la puerta está abierta o cerrada. Con esto queda completo el recorrido del
proyecto. Si el grupo quiere seguir, en la ficha están las extensiones sugeridas: sumar un
lector RFID para abrir con tarjeta, o dar el salto de monitorear a comandar la cerradura de
forma remota.
Evidencia de cierre
Al terminar este nivel, esto tiene que estar funcionando:
- El sistema funciona con código no bloqueante: los pitidos del buzzer se manejan con millis() en una pequeña máquina de estados y no queda ningún delay() que detenga el loop entre teclas.
- 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 el estado de la cerradura (feed cerradura, 1 = abierta / 0 = cerrada) publicado por el dispositivo cada vez que la puerta cambia de estado (variante ESP32).