Saltar al contenido
Volver a la ficha del proyecto

Manual del proyecto · Proyecto 09

Cerradura Automatizada

Diseño, construcción y programación de una cerradura domiciliaria automática que no necesita llave: primero se abre y se cierra con dos pulsadores y avisa su estado con dos LED (verde/rojo); luego suma un teclado matricial 4x4 para abrirla con una clave y un buzzer que confirma si el código es correcto; por último, se monitorea a distancia por Internet de las Cosas (IoT).

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

Saberes Digitales

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

2019

Contenido del manual

  1. 01 Materiales y equipamiento
  2. 02 Abrir y cerrar la cerradura con dos pulsadores Nivel inicial
  3. 03 Abrir la cerradura con una clave Nivel intermedio
  4. 04 Monitorear la cerradura con Internet de las Cosas Nivel avanzado

Materiales y equipamiento

Insumos

Imagen Insumo Cantidad Plataforma
Foto de Servomotor Servomotor SG5010 Mueve el mecanismo (piñón y cremallera) que traba y destraba la cerradura. En esta reedición gira a 90° para abrir y a 0° para cerrar, el mismo criterio en los tres niveles. En el UNO se controla con la librería Servo; en el ESP32, con ESP32Servo. Se alimenta con 5 V y GND común con la placa. 1 Todas
Pulsadores Solo en el nivel inicial: uno abre y otro cierra la cerradura. En el nivel intermedio los reemplaza el teclado. Se conectan entre el pin y GND, usando la resistencia interna de pull-up de la placa (no necesitan resistencia externa). 2 Todas
1 2 3 A 4 5 6 B 7 8 9 C * 0 # D
Teclado matricial 4x4 de membrana Desde el nivel intermedio: permite ingresar una clave de 4 dígitos. Sus 16 teclas se leen con solo 8 pines (4 filas + 4 columnas) mediante la librería Keypad. 1 Todas
Foto de LED 5 mm LED verde de 5 mm difuso Indica que la cerradura está ABIERTA. La pata larga (positiva) va al pin de la placa a través de una resistencia de 220 Ω. 1 Todas
Foto de LED 5 mm LED rojo de 5 mm difuso Indica que la cerradura está CERRADA (estado inicial al encender el sistema). 1 Todas
+
Buzzer (zumbador) Desde el nivel intermedio: suena un pitido agudo cuando la clave es correcta y un doble pitido grave cuando es incorrecta. Se maneja con la función tone(). 1 Todas
Foto de Resistencia Resistencia de 220 Ω Dos limitan la corriente de los LED (verde y rojo). El original lista cinco; las restantes quedan de repuesto. 5 Todas
Foto de Cables dupont macho-macho Cable dupont macho-hembra 20 Todas
Foto de Protoboard Protoboard 1 Todas
Filamento para impresora 3D (PLA) Para imprimir las piezas del mecanismo de la cerradura (piñón, cremallera y soportes). 1 Todas
Foto de Arduino UNO R3 Arduino UNO R3 El original de 2019 sumaba, para el nivel avanzado, un módulo IoT OBLOQ que hoy está discontinuado. Con UNO, el nivel IoT se sigue de manera conceptual y la práctica se realiza con la variante ESP32. 1 Arduino UNO R3
Foto de Cable USB tipo B Cable USB tipo B 1 Arduino UNO R3
Foto de Placa ESP32 DevKit Placa ESP32 DevKit v1 Reemplaza al conjunto Arduino UNO + módulo OBLOQ del original: el ESP32 trae WiFi integrado, necesario para el nivel avanzado (IoT). Trabaja a 3,3 V (no 5 V como el UNO) y maneja el servo con la librería ESP32Servo. 1 ESP32 DevKit
Foto de Cable micro USB Cable micro USB (con línea de datos) 1 ESP32 DevKit
Foto de Fuente de alimentación Fuente de 9 V 1 A (plug centro positivo, 5,5 x 2,1 mm) (opcional) Para alimentar el sistema sin la computadora. El ESP32 se alimenta por USB con una fuente de 5 V. 1 Arduino UNO R3

Equipamiento del taller

  • Computadora
  • Impresora 3D
  • Soldador
  • Estaño
  • Alicate
  • Pinza de punta
  • Pinza brusela

Plano de conexionado en protoboard

Arduino UNO Conexionado en protoboard · Cerradura electrónica: servo + LEDs verde/rojo + teclado matricial 4x4 + buzzer.

Deslizá horizontalmente para ver el plano completo

Arduino UNO
Servo de la cerradura (SG90)
SG90
VCCseñalGND
LED verde
AK
LED rojo
AK
Teclado matricial 4x4
1 2 3 A 4 5 6 B 7 8 9 C * 0 # D
F1F2F3F4C1C2C3C4
Buzzer
+
+
ESP32 DevKit v1 Conexionado en protoboard · Cerradura electrónica: servo + LEDs verde/rojo + teclado matricial 4x4 + buzzer.

Deslizá horizontalmente para ver el plano completo

ESP32 DevKit v1
Servo de la cerradura (SG90)
SG90
VCCseñalGND
LED verde
AK
LED rojo
AK
Teclado matricial 4x4
1 2 3 A 4 5 6 B 7 8 9 C * 0 # D
F1F2F3F4C1C2C3C4
Buzzer
+
+
Arduino UNOConexionado en protoboard
Conexiones · Arduino UNO. Convención de colores: alimentación VCC = rojo, GND = negro; señal del servo = azul; teclado matricial (filas y columnas) = amarillo; buzzer = violeta; LED verde = verde, LED rojo = rojo. El servo siempre se alimenta a 5 V (en el ESP32, desde el pin 5V/VIN, no desde 3V3), compartiendo GND con la placa.
Desde Hacia Color de cable
Arduino UNO · 5VBus + de la protoboardrojo
Arduino UNO · GNDBus − de la protoboardnegro
Servo de la cerradura · VCCBus + de la protoboardrojo
Servo de la cerradura · GNDBus − de la protoboardnegro
Servo de la cerradura · señalArduino UNO · 10azul
LED verde · ánodo (+)Arduino UNO · 11verde
LED verde · cátodo (−)Resistencia 220 Ω (verde)negro
Resistencia 220 Ω (verde)Bus − de la protoboardnegro
LED rojo · ánodo (+)Arduino UNO · 13rojo
LED rojo · cátodo (−)Resistencia 220 Ω (rojo)negro
Resistencia 220 Ω (rojo)Bus − de la protoboardnegro
Teclado matricial 4x4 · filasArduino UNO · 9, 8, 7, 6amarillo
Teclado matricial 4x4 · columnasArduino UNO · 5, 4, 3, 2amarillo
Buzzer · (+)Arduino UNO · 12violeta
Buzzer · (−)Bus − de la protoboardnegro
ESP32 DevKit v1Conexionado en protoboard
Conexiones · ESP32 DevKit v1. Convención de colores: alimentación VCC = rojo, GND = negro; señal del servo = azul; teclado matricial (filas y columnas) = amarillo; buzzer = violeta; LED verde = verde, LED rojo = rojo. El servo siempre se alimenta a 5 V (en el ESP32, desde el pin 5V/VIN, no desde 3V3), compartiendo GND con la placa.
Desde Hacia Color de cable
ESP32 DevKit v1 · 5V (VIN)Bus + de la protoboardrojo
ESP32 DevKit v1 · GNDBus − de la protoboardnegro
Servo de la cerradura · VCCBus + de la protoboardrojo
Servo de la cerradura · GNDBus − de la protoboardnegro
Servo de la cerradura · señalESP32 DevKit v1 · GPIO13azul
LED verde · ánodo (+)ESP32 DevKit v1 · GPIO25verde
LED verde · cátodo (−)Resistencia 220 Ω (verde)negro
Resistencia 220 Ω (verde)Bus − de la protoboardnegro
LED rojo · ánodo (+)ESP32 DevKit v1 · GPIO26rojo
LED rojo · cátodo (−)Resistencia 220 Ω (rojo)negro
Resistencia 220 Ω (rojo)Bus − de la protoboardnegro
Teclado matricial 4x4 · filasESP32 DevKit v1 · GPIO16, 17, 18, 19amarillo
Teclado matricial 4x4 · columnasESP32 DevKit v1 · GPIO21, 22, 23, 27amarillo
Buzzer · (+)ESP32 DevKit v1 · GPIO14violeta
Buzzer · (−)Bus − de la protoboardnegro
Nivel inicial

Abrir y cerrar la cerradura con dos pulsadores

Montar el servomotor y los pulsadores en la protoboard

El proyecto arranca por el circuito que controla el movimiento de la cerradura. El corazón es un servomotor: un motor con un sistema de control que le permite ubicarse en un ángulo preciso (entre 0° y 180°) y quedarse ahí. Ese giro es el que, más adelante, va a mover el mecanismo que traba y destraba la puerta.

Sumamos también los dos pulsadoresABRIR y CERRAR— sobre la protoboard. Un pulsador está normalmente abierto y, mientras se lo mantiene apretado, cierra el circuito entre sus patas: así la placa detecta que lo presionamos.

Esquema de conexión: una placa Arduino UNO a la izquierda y una protoboard a la derecha con dos pulsadores rotulados ABRIR y CERRAR, cada uno con una resistencia, y un servomotor. Cables llevan las señales de los pulsadores y del servo a la placa, y la alimentación a 5V y GND.
Montaje del servomotor y los dos pulsadores en la protoboard (figura original del proyecto). En esta reedición, los pulsadores se conectan entre el pin y GND usando la resistencia interna de pull-up de la placa, sin las resistencias externas del esquema.

En el Arduino UNO seguimos el esquema unificado de este proyecto: la señal del servo va al pin 10, el pulsador ABRIR al pin 2 y el pulsador CERRAR al pin 3. La alimentación del servo se toma de 5V y GND. Estos pines son los mismos en los tres niveles: al avanzar, el circuito no se rehace, solo se le suman componentes.

En el ESP32 la señal del servo va al GPIO 13, el pulsador ABRIR al GPIO 32 y el CERRAR al GPIO 33 (GPIO de entrada seguros). El ESP32 trabaja a 3,3 V, pero el servo se alimenta con 5 V compartiendo GND con la placa. Además, el servo en el ESP32 se maneja con la librería ESP32Servo (la Servo.h clásica del UNO no funciona en esta placa).

Programar el movimiento del servomotor

Ahora escribimos la lógica de apertura y cierre. La programación la hacemos directamente en el lenguaje de Arduino (C++), con el Arduino IDE en su versión 2.x, que se descarga gratis de 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 repite después. Todo lo que va después de // es un comentario: no afecta al programa, sirve para leerlo mejor.

La idea es simple: en cada vuelta de loop() el programa mira los pulsadores. Si está presionado ABRIR, lleva el servo a la posición abierta; si está presionado CERRAR, lo lleva a la cerrada. Al servo se le ordena un ángulo con cerradura.write(...). En esta reedición unificamos los ángulos para todos los niveles: abierta = 90° y cerrada = 0°.

Un detalle sobre cómo leemos los botones: los conectamos con la resistencia interna de pull-up de la placa (INPUT_PULLUP). Con eso, el pin lee ALTO en reposo y BAJO cuando el botón está presionado. Por eso en el código preguntamos if (digitalRead(...) == LOW). Es la forma moderna y con menos componentes que el “leer al aire” del material original.

Subir el código a la placa

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

Para el Arduino UNO: usá el cable USB tipo B, elegí la placa Arduino UNO y el puerto que se agregó al conectarla (en Windows es un puerto COM numerado; en Linux suele ser /dev/ttyACM0 o similar). La librería Servo ya viene incluida con el IDE, no hay que instalar nada.

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

Conectar los LED indicadores

Sumamos ahora los dos LED que muestran el estado de la cerradura: el verde para abierta y el rojo para cerrada. El LED tiene polaridad: la pata larga es la positiva y va al pin de la placa; la pata corta (negativa) va a masa a través de una resistencia de 220 Ω, que limita la corriente para que no se dañe.

Esquema de conexión ampliado: Arduino UNO y protoboard con los dos pulsadores ABRIR y CERRAR, el servomotor y, a la derecha, un LED verde y un LED rojo, cada uno con su resistencia de 220 ohm hacia la línea negativa de la protoboard.
El circuito del nivel inicial con los dos LED indicadores agregados (figura original del proyecto): verde para cerradura abierta, rojo para cerrada, cada uno con su resistencia de 220 Ω.

En el Arduino UNO, el LED verde va al pin 11 y el LED rojo al pin 13 (las constantes PIN_LED_VERDE y PIN_LED_ROJO del código). El pin 13 tiene además el LED integrado de la placa, útil para ver el estado aun sin el LED externo.

En el ESP32, el LED verde va al GPIO 25 y el LED rojo al GPIO 26. Como el ESP32 trabaja a 3,3 V, la resistencia de 220 Ω es más que suficiente.

Programar los LED indicadores

Completamos el programa para que, además de mover el servo, encienda el LED que corresponde a cada estado. La regla es: cuando la cerradura queda abierta, se enciende el verde y se apaga el rojo; cuando queda cerrada, se enciende el rojo y se apaga el verde. El sistema arranca cerrado (LED rojo), su estado seguro por defecto.

Fijate en el código cómo las dos acciones —“abrir” y “cerrar”— quedan agrupadas en funciones (abrirPuerta() y cerrarPuerta()): cada una mueve el servo y actualiza los dos LED de una vez. Así el programa se lee como lo que hace.

Ver el código completo · cerradura-pulsador.ino
cerradura-pulsador.ino Arduino UNO
// Cerradura automatizada — Nivel Inicial (Arduino UNO).
//
// Dos pulsadores controlan el servomotor que traba/destraba la cerradura:
//   - Pulsador ABRIR  -> el servo va a la posicion "abierta"  + LED verde.
//   - Pulsador CERRAR -> el servo va a la posicion "cerrada"  + LED rojo.
// Los LED indican de un vistazo el estado de la puerta (verde = abierta,
// rojo = cerrada), tal como pide la situacion-problema del abuelo de Josefina.
//
// Plataforma: Arduino UNO. Proyecto de BAJA TENSION (servo 5 V, LED,
// pulsadores): no hay 220 V ni agua, no lleva advertencias electricas.
//
// Nivel inicial: se permite delay() (fiel a la progresion original); el
// antirrebote se resuelve con una espera corta. El nivel avanzado pasa a
// millis() sin bloquear.
//
// Basado en: Fragmentos de codigo del Nivel Inicial (Pasos 2 y 5) — Cerradura
// Automatica, Saberes Digitales (INET/EDUCAR 2019).
// Desvios respecto del original (ver README):
//  (1) PINES UNIFICADOS: el original ataba el servo al pin 0 (rompe USB/serial)
//      y usaba pines de LED distintos por nivel. Aca el servo va al pin 10 y
//      los LED a 11/13 — el MISMO esquema en los tres niveles.
//  (2) Angulos del servo unificados (ABIERTA=90, CERRADA=0) para que el mismo
//      criterio valga en inicial, intermedio y avanzado.
//  (3) Pulsadores con INPUT_PULLUP (activo en LOW): el original leia entradas
//      "al aire"; el pull-up interno evita lecturas erraticas sin resistencias.

#if defined(ESP32)
  #include <ESP32Servo.h>
#else
  #include <Servo.h>
#endif

// --- Pines (esquema UNIFICADO del contrato) ---
const int PIN_SERVO      = 10;  // senal del servomotor de la cerradura
const int PIN_LED_VERDE  = 11;  // encendido = cerradura ABIERTA
const int PIN_LED_ROJO   = 13;  // encendido = cerradura CERRADA
// En el nivel inicial NO hay teclado, asi que los botones ocupan dos pines
// libres (2 y 3). En intermedio, el teclado matricial los reemplaza.
const int PIN_BOTON_ABRIR  = 2;
const int PIN_BOTON_CERRAR = 3;

// Angulos del servo (unificados en los tres niveles).
const int ANGULO_ABIERTA = 90;
const int ANGULO_CERRADA = 0;

// Antirrebote simple del pulsador (ms).
const unsigned long ANTIRREBOTE_MS = 200;

Servo cerradura;

void abrirPuerta() {
  cerradura.write(ANGULO_ABIERTA);
  digitalWrite(PIN_LED_VERDE, HIGH);
  digitalWrite(PIN_LED_ROJO, LOW);
}

void cerrarPuerta() {
  cerradura.write(ANGULO_CERRADA);
  digitalWrite(PIN_LED_VERDE, LOW);
  digitalWrite(PIN_LED_ROJO, HIGH);
}

void setup() {
  pinMode(PIN_LED_VERDE, OUTPUT);
  pinMode(PIN_LED_ROJO, OUTPUT);
  // Con INPUT_PULLUP el pulsador lee HIGH en reposo y LOW al presionarse.
  pinMode(PIN_BOTON_ABRIR, INPUT_PULLUP);
  pinMode(PIN_BOTON_CERRAR, INPUT_PULLUP);

  cerradura.attach(PIN_SERVO);
  // Estado inicial: CERRADA (LED rojo). Es el estado seguro por defecto de
  // una cerradura.
  cerrarPuerta();
}

void loop() {
  // Pulsador presionado = LOW (por el pull-up interno).
  if (digitalRead(PIN_BOTON_ABRIR) == LOW) {
    abrirPuerta();
    delay(ANTIRREBOTE_MS);
  }

  if (digitalRead(PIN_BOTON_CERRAR) == LOW) {
    cerrarPuerta();
    delay(ANTIRREBOTE_MS);
  }
}
cerradura-pulsador.ino ESP32
// Cerradura automatizada — Nivel Inicial (ESP32 DevKit v1).
//
// Dos pulsadores controlan el servomotor que traba/destraba la cerradura:
//   - Pulsador ABRIR  -> el servo va a la posicion "abierta"  + LED verde.
//   - Pulsador CERRAR -> el servo va a la posicion "cerrada"  + LED rojo.
// Los LED indican de un vistazo el estado (verde = abierta, rojo = cerrada).
//
// Plataforma: ESP32 (DevKit v1). El servo se maneja con la libreria
// ESP32Servo (la Servo.h clasica no funciona en ESP32). Nota electrica: el
// ESP32 trabaja a 3,3 V (el UNO a 5 V); alimentar el servo desde 5 V con GND
// comun. Proyecto de BAJA TENSION: sin 220 V ni agua.
//
// Nivel inicial: se permite delay() (fiel a la progresion original).
//
// Basado en: Fragmentos de codigo del Nivel Inicial (Pasos 2 y 5) — Cerradura
// Automatica, Saberes Digitales (INET/EDUCAR 2019).
// Desvios (ver README): pines GPIO seguros del contrato; angulos del servo
// unificados (ABIERTA=90, CERRADA=0); pulsadores con INPUT_PULLUP.

#if defined(ESP32)
  #include <ESP32Servo.h>
#else
  #include <Servo.h>
#endif

// --- Pines (GPIO seguros del contrato) ---
const int PIN_SERVO      = 13;  // ESP32Servo
const int PIN_LED_VERDE  = 25;  // encendido = cerradura ABIERTA
const int PIN_LED_ROJO   = 26;  // encendido = cerradura CERRADA
// En el nivel inicial no hay teclado: los botones ocupan dos GPIO libres de
// entrada (32 y 33). En intermedio los reemplaza el teclado matricial.
const int PIN_BOTON_ABRIR  = 32;
const int PIN_BOTON_CERRAR = 33;

// Angulos del servo (unificados en los tres niveles).
const int ANGULO_ABIERTA = 90;
const int ANGULO_CERRADA = 0;

// Antirrebote simple del pulsador (ms).
const unsigned long ANTIRREBOTE_MS = 200;

Servo cerradura;

void abrirPuerta() {
  cerradura.write(ANGULO_ABIERTA);
  digitalWrite(PIN_LED_VERDE, HIGH);
  digitalWrite(PIN_LED_ROJO, LOW);
}

void cerrarPuerta() {
  cerradura.write(ANGULO_CERRADA);
  digitalWrite(PIN_LED_VERDE, LOW);
  digitalWrite(PIN_LED_ROJO, HIGH);
}

void setup() {
  pinMode(PIN_LED_VERDE, OUTPUT);
  pinMode(PIN_LED_ROJO, OUTPUT);
  // Con INPUT_PULLUP el pulsador lee HIGH en reposo y LOW al presionarse.
  pinMode(PIN_BOTON_ABRIR, INPUT_PULLUP);
  pinMode(PIN_BOTON_CERRAR, INPUT_PULLUP);

  cerradura.attach(PIN_SERVO);
  // Estado inicial: CERRADA (LED rojo), estado seguro por defecto.
  cerrarPuerta();
}

void loop() {
  // Pulsador presionado = LOW (por el pull-up interno).
  if (digitalRead(PIN_BOTON_ABRIR) == LOW) {
    abrirPuerta();
    delay(ANTIRREBOTE_MS);
  }

  if (digitalRead(PIN_BOTON_CERRAR) == LOW) {
    cerrarPuerta();
    delay(ANTIRREBOTE_MS);
  }
}
Cerradura con pulsadores: ABRIR lleva el servo a 90° y enciende el LED verde; CERRAR lo lleva a 0° y enciende el rojo. Arranca cerrada. Lo único que cambia entre plataformas son los pines y la librería del servo (Servo en el UNO, ESP32Servo en el ESP32).

Podés ver el comportamiento —los botones moviendo el servo y los LED cambiando de estado— en el simulador, antes de armar el circuito real:

Simulación del comportamiento

La misma máquina de estados del firmware, portada a TypeScript, maneja el comportamiento del dispositivo. No se ejecuta el binario real: la ejecución del .hex con avr8js está en el roadmap.

Simulación lista. Presioná «Iniciar» para arrancar la secuencia.

Comenzar a armar la maqueta

Con el circuito probado, decidimos si cerramos el proyecto en este nivel o si avanzamos al intermedio para sumarle el teclado. Si vamos a dejar la maqueta armada, conviene pasar de la protoboard a un montaje más firme: soldar los cables a los componentes para que las conexiones no se suelten cuando el mecanismo se mueva.

Imprimir las piezas 3D

El mecanismo de la cerradura se fabrica con piezas impresas en 3D. La idea es un piñón-cremallera: el servo hace girar un piñón (un engranaje) que empuja una cremallera (una barra dentada), y esa barra es la que se corre para trabar o destrabar la puerta, como un pasador.

Render 3D de una cremallera: una barra alargada con dientes en la mitad superior de un extremo y una sección lisa hacia el otro.
La cremallera del mecanismo (figura original del proyecto): el servo mueve el piñón que engrana con estos dientes y desplaza la barra para trabar o destrabar la cerradura.

Estas piezas forman parte de los paquetes de fabricación del proyecto, que se descargan desde su ficha. Si querés modificar el modelo, sea cual sea el programa de diseño 3D que uses, tenés que exportar las piezas en formato STL, el estándar para intercambiar e imprimir modelos sólidos.

Ensamblar el sistema mecánico con el servomotor

Con las piezas impresas, ensamblamos el mecanismo sobre el servomotor: el piñón se calza en el eje del servo y engrana con la cremallera, de modo que al girar el servo (a 90° o a 0°) la barra se corra y trabe o destrabe la cerradura.

Render 3D del mecanismo montado: una placa Arduino conectada por dos cables a un servomotor, que tiene acoplado un piñón dentado engranado con una cremallera dentro de una base impresa; en la punta de la cremallera hay un saliente que hace de pasador.
El mecanismo de la cerradura ensamblado con el servomotor (figura original del proyecto): el piñón en el eje del servo mueve la cremallera que traba y destraba la puerta. Aquí se muestra solo el servo; hay que conectar también la placa, los pulsadores y los LED.

Una vez armado, el sistema se fija a la puerta (o a una apertura similar) y allí se ubican los LED y los pulsadores, al alcance de quien la va a usar.

Cierre del nivel

Al terminar este nivel, tu cerradura ya funciona: se abre y se cierra con dos pulsadores, el servo mueve el mecanismo impreso en 3D y los LED muestran el estado (verde = abierta, rojo = cerrada). En el camino armaste el circuito, subiste el programa desde el Arduino IDE 2.x y ensamblaste el mecanismo. Pero abrir con un botón es poco seguro: cualquiera que lo presione entra. En el nivel intermedio vamos a reemplazar los pulsadores por un teclado para exigir una clave, y sumaremos un buzzer que avise si el código es correcto o no.

Evidencia de cierre

Al terminar este nivel, esto tiene que estar funcionando:

  • El mecanismo de la cerradura está impreso y montado sobre el servomotor, y el circuito armado en la protoboard: el servo, los dos pulsadores (ABRIR y CERRAR) y los dos LED (verde y rojo) con sus resistencias de 220 Ω.
  • Sabés subir un programa desde el Arduino IDE 2.x: elegir la placa, elegir el puerto e instalar la librería del servo (Servo en el UNO, ESP32Servo en el ESP32).
  • Al presionar ABRIR, el servo gira a 90° y se enciende el LED verde; al presionar CERRAR, gira a 0° y se enciende el LED rojo. El sistema arranca cerrado (LED rojo).
  • Entendés que los pulsadores usan la resistencia interna de pull-up (presionado = BAJO) y que los pines y los ángulos del servo son los mismos en los tres niveles del proyecto.
Nivel intermedio

Abrir la cerradura con una clave

Conectar el teclado matricial 4x4 y el buzzer

Sumamos un teclado matricial 4x4 de membrana (16 teclas) y un buzzer. El teclado reemplaza a los pulsadores del nivel inicial: en vez de dos botones sueltos, ahora tenemos las 16 teclas para ingresar una clave. El servo y los dos LED siguen conectados igual que antes (por eso unificamos los pines: el circuito no se rehace, solo se le suman el teclado y el buzzer).

Lo interesante del teclado es su diseño matricial. En lugar de un cable por tecla (que serían 16), las teclas se organizan como una matriz de 4 filas por 4 columnas: cada tecla es el cruce de una fila con una columna. Así, con solo 8 pines (4 filas + 4 columnas) la placa puede distinguir las 16 teclas: recorre filas y columnas y detecta en qué cruce se cerró el contacto.

Esquema de conexión del nivel intermedio: un teclado matricial 4x4 (teclas 1-9, 0, A, B, C, D, asterisco y numeral) con un conector de 8 pines hacia el Arduino UNO, un buzzer a la izquierda, y en la protoboard un LED verde, un LED rojo y el servomotor.
El sistema del nivel intermedio (figura original del proyecto): el teclado 4x4 y el buzzer conectados al Arduino, junto al servomotor y los dos LED que vienen del nivel inicial.

En el Arduino UNO, el teclado usa las filas en los pines 9, 8, 7 y 6 y las columnas en los pines 5, 4, 3 y 2. El buzzer va al pin 12. El servo (pin 10) y los LED verde (11) y rojo (13) quedan donde estaban.

En el ESP32, el teclado usa las filas en los GPIO 16, 17, 18 y 19 y las columnas en los GPIO 21, 22, 23 y 27. El buzzer va al GPIO 14. El servo (GPIO 13) y los LED verde (GPIO 25) y rojo (GPIO 26) quedan donde estaban.

Incluir la librería Keypad

Leer un teclado matricial “a mano” —recorrer filas y columnas para ver qué cruce se cerró— es tedioso. Por suerte existe una librería que lo resuelve: Keypad. En el Arduino IDE 2.x abrimos el Gestor de bibliotecas (el ícono de los libritos en la barra lateral), buscamos “Keypad” (la de Mark Stanley y Alexander Brevig) y la instalamos.

Con la librería instalada, en el código describimos el teclado una sola vez: el mapa de teclas (qué carácter tiene cada posición de la matriz) y los pines de filas y columnas. A partir de ahí, con teclado.getKey() obtenemos directamente la tecla presionada, sin ocuparnos del escaneo.

Programar el teclado y la clave

Ahora la lógica de la clave. Primero definimos cuántos dígitos tiene y cuáles son: en este caso, 4 dígitos y la clave del original, 1 5 9 D. La guardamos en una lista (clave) y usamos otra (tecleado) para ir juntando lo que la persona ingresa.

El programa acumula las teclas que se presionan. Cuando junta 4, las compara con la clave:

  • Si coinciden: suena un pitido agudo (clave correcta) y la cerradura conmuta su estado —si estaba cerrada, abre; si estaba abierta, cierra—, moviendo el servo y actualizando los LED.
  • Si no coinciden: suena un doble pitido grave (clave incorrecta) y la cerradura no se mueve. El sistema queda listo para un nuevo intento de 4 teclas.

Para el aviso sonoro usamos la función tone(pin, frecuencia, duración): un tono agudo (1000 Hz) para el acierto y uno grave (400 Hz) para el error. Así los dos avisos se distinguen de oído, algo que el simple encendido/apagado del buzzer del original no permitía.

Ver el código completo · cerradura-teclado.ino
cerradura-teclado.ino Arduino UNO
// Cerradura automatizada — Nivel Intermedio (Arduino UNO).
//
// Se reemplazan los dos pulsadores por un TECLADO MATRICIAL 4x4 y se suma un
// ZUMBADOR (buzzer). El usuario tipea una clave de 4 digitos; si coincide con
// la clave guardada, la cerradura conmuta su estado (abre si estaba cerrada,
// cierra si estaba abierta) y suena un pitido de OK. Si la clave es incorrecta,
// suena un doble pitido de error y no se mueve la cerradura. Los LED siguen
// indicando el estado (verde = abierta, rojo = cerrada), como pidio el abuelo.
//
// Clave del original: '1' '5' '9' 'D'.
//
// Plataforma: Arduino UNO. Proyecto de BAJA TENSION (servo 5 V, teclado, LED,
// buzzer): sin 220 V ni agua.
//
// Nivel intermedio: se permite delay() (fiel al original) para los pitidos.
//
// Basado en: Fragmento de codigo del Nivel Intermedio (Paso 3) — Cerradura
// Automatica, Saberes Digitales (INET/EDUCAR 2019).
// Desvios respecto del original (ver README):
//  (1) PINES UNIFICADOS: servo=10, LED verde=11, buzzer=12, LED rojo=13, y el
//      teclado en filas {9,8,7,6} / columnas {5,4,3,2} — el MISMO esquema que
//      inicial y avanzado (el original cambiaba pines entre niveles).
//  (2) BUZZER con tone(): el original prendia/apagaba el buzzer con
//      digitalWrite (un buzzer "activo"); tone() genera una frecuencia audible
//      y sirve para cualquier buzzer, ademas de permitir distinguir el tono de
//      OK del de error.
//  (3) Angulos del servo unificados (ABIERTA=90, CERRADA=0). El sistema arranca
//      CERRADO (LED rojo), estado seguro por defecto.

#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: tonos distintos para OK y error ---
const unsigned int FREQ_OK        = 1000;  // Hz, pitido agudo = correcta
const unsigned int FREQ_ERROR     = 400;   // Hz, pitido grave = incorrecta
const unsigned long DURACION_BEEP_MS = 200;
const unsigned long PAUSA_BEEP_MS    = 300;

// 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 beepCorrecta() {
  tone(PIN_BUZZER, FREQ_OK, DURACION_BEEP_MS);
  delay(DURACION_BEEP_MS);
  noTone(PIN_BUZZER);
}

void beepIncorrecta() {
  // Doble pitido grave.
  for (byte i = 0; i < 2; i++) {
    tone(PIN_BUZZER, FREQ_ERROR, DURACION_BEEP_MS);
    delay(DURACION_BEEP_MS);
    noTone(PIN_BUZZER);
    delay(PAUSA_BEEP_MS);
  }
}

// Compara lo tecleado con la clave guardada.
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);
  // Arranca CERRADA (LED rojo).
  puertaAbierta = false;
  cerradura.write(ANGULO_CERRADA);
  mostrarEstadoEnLeds();
}

void loop() {
  char tecla = teclado.getKey();
  if (!tecla) {
    return;  // no se presiono ninguna tecla en esta vuelta
  }

  // Guarda la tecla y avanza el indice.
  tecleado[indice] = tecla;
  indice++;

  // Cuando se completan 4 digitos, se evalua la clave.
  if (indice == LARGO_CLAVE) {
    indice = 0;

    if (claveCorrecta()) {
      beepCorrecta();
      // Conmuta el estado de la cerradura.
      puertaAbierta = !puertaAbierta;
      cerradura.write(puertaAbierta ? ANGULO_ABIERTA : ANGULO_CERRADA);
      mostrarEstadoEnLeds();
    } else {
      beepIncorrecta();  // no toca la cerradura
    }
  }
}
cerradura-teclado.ino ESP32
// Cerradura automatizada — Nivel Intermedio (ESP32 DevKit v1).
//
// Se reemplazan los dos pulsadores por un TECLADO MATRICIAL 4x4 y se suma un
// ZUMBADOR (buzzer). El usuario tipea una clave de 4 digitos; si coincide con
// la clave guardada, la cerradura conmuta su estado (abre si estaba cerrada,
// cierra si estaba abierta) y suena un pitido de OK. Si es incorrecta, suena
// un doble pitido de error y la cerradura no se mueve. Los LED indican el
// estado (verde = abierta, rojo = cerrada).
//
// Clave del original: '1' '5' '9' 'D'.
//
// Plataforma: ESP32 (DevKit v1). Servo con ESP32Servo; el buzzer usa tone(),
// disponible en el core de ESP32. Nota electrica: el ESP32 trabaja a 3,3 V
// (alimentar el servo desde 5 V con GND comun). Proyecto de BAJA TENSION.
//
// Nivel intermedio: se permite delay() (fiel al original) para los pitidos.
//
// Basado en: Fragmento de codigo del Nivel Intermedio (Paso 3) — Cerradura
// Automatica, Saberes Digitales (INET/EDUCAR 2019).
// Desvios (ver README): pines GPIO seguros del contrato; buzzer con tone();
// angulos unificados (ABIERTA=90, CERRADA=0); arranque CERRADO (LED rojo).

#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: tonos distintos para OK y error ---
const unsigned int FREQ_OK        = 1000;  // Hz, pitido agudo = correcta
const unsigned int FREQ_ERROR     = 400;   // Hz, pitido grave = incorrecta
const unsigned long DURACION_BEEP_MS = 200;
const unsigned long PAUSA_BEEP_MS    = 300;

// 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 beepCorrecta() {
  tone(PIN_BUZZER, FREQ_OK, DURACION_BEEP_MS);
  delay(DURACION_BEEP_MS);
  noTone(PIN_BUZZER);
}

void beepIncorrecta() {
  // Doble pitido grave.
  for (byte i = 0; i < 2; i++) {
    tone(PIN_BUZZER, FREQ_ERROR, DURACION_BEEP_MS);
    delay(DURACION_BEEP_MS);
    noTone(PIN_BUZZER);
    delay(PAUSA_BEEP_MS);
  }
}

// Compara lo tecleado con la clave guardada.
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);
  // Arranca CERRADA (LED rojo).
  puertaAbierta = false;
  cerradura.write(ANGULO_CERRADA);
  mostrarEstadoEnLeds();
}

void loop() {
  char tecla = teclado.getKey();
  if (!tecla) {
    return;  // no se presiono ninguna tecla en esta vuelta
  }

  tecleado[indice] = tecla;
  indice++;

  if (indice == LARGO_CLAVE) {
    indice = 0;

    if (claveCorrecta()) {
      beepCorrecta();
      puertaAbierta = !puertaAbierta;
      cerradura.write(puertaAbierta ? ANGULO_ABIERTA : ANGULO_CERRADA);
      mostrarEstadoEnLeds();
    } else {
      beepIncorrecta();  // no toca la cerradura
    }
  }
}
Cerradura por clave: el teclado 4x4 junta 4 dígitos y los compara con la clave 1 5 9 D. Si coincide, un pitido agudo y la cerradura conmuta; si no, un doble pitido grave y no se mueve. La única diferencia entre plataformas son los pines y la librería del servo.

Podés probar el ingreso de la clave —el pitido de acierto o de error y la cerradura conmutando— en el simulador:

Simulación del comportamiento

La misma máquina de estados del firmware, portada a TypeScript, maneja el comportamiento del dispositivo. No se ejecuta el binario real: la ejecución del .hex con avr8js está en el roadmap.

Simulación lista. Presioná «Iniciar» para arrancar la secuencia.

Cierre del nivel

Al terminar este nivel, la cerradura dejó de abrirse con un botón cualquiera: ahora exige una clave de 4 dígitos que se ingresa por el teclado matricial 4x4, y un buzzer confirma con un tono si el código fue correcto o no. Viste cómo un teclado organiza 16 teclas en una matriz de filas y columnas para usar solo 8 pines, y cómo el programa recuerda el estado de la puerta para alternarlo. En el nivel avanzado vamos a que el abuelo pueda enterarse de la apertura o el cierre aunque no esté en la casa: sumaremos Internet de las Cosas para monitorear la cerradura a distancia.

Evidencia de cierre

Al terminar este nivel, esto tiene que estar funcionando:

  • El teclado matricial 4x4 y el buzzer están conectados sobre el mismo circuito del nivel inicial (servo y LED): los pulsadores fueron reemplazados por el teclado.
  • Instalaste la librería Keypad desde el Gestor de bibliotecas del Arduino IDE.
  • Al tipear la clave correcta (1 5 9 D) suena un pitido agudo y la cerradura conmuta su estado (abre si estaba cerrada, cierra si estaba abierta), acompañada por los LED; con una clave incorrecta suena un doble pitido grave y la cerradura no se mueve.
Nivel avanzado

Monitorear la cerradura con Internet de las Cosas

Introducción a Internet de las Cosas (IoT)

Internet de las Cosas (en inglés Internet of Things, abreviado IoT) es un concepto que refiere a la interconexión digital de objetos cotidianos con internet. 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:

  1. Un dispositivo capaz de conectarse a internet.
  2. Un servidor que reciba y aloje los datos.

Lo resolveremos con el WiFi integrado de la placa ESP32 y con los servidores de Adafruit IO.

El material original de 2019 resolvía este punto agregando al Arduino UNO un módulo externo llamado OBLOQ, un adaptador WiFi-serie de DFRobot hoy discontinuado (igual que su biblioteca ObloqAdafruit). Como el UNO clásico no tiene conectividad propia, en esta reedición la práctica de IoT se realiza con la variante ESP32: 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 IO, 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 feed 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 bloqueante: 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 estados 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-no-bloqueante.ino Arduino UNO
// 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-no-bloqueante.ino 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();
}
La misma cerradura por clave del nivel intermedio, ahora SIN delay(): los pitidos del buzzer se manejan con millis() en una máquina de estados, así el loop queda libre para, en el ESP32, atender internet.

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-iot.ino ESP32
// 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();
}
La cerradura IoT completa: teclado y buzzer no bloqueantes + WiFi del ESP32 + publicación del estado (1 abierta / 0 cerrada) en el feed cerradura de Adafruit IO, cada vez que la puerta cambia.

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.

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