Manual del proyecto · Proyecto 06
Estacionamiento Automatizado
Diseño, construcción y programación de un sistema de gestión para un estacionamiento: barreras automáticas movidas por servomotores, conteo de autos con pulsadores y dos LED que indican si hay lugar; luego un display de 7 segmentos que muestra la cantidad exacta de plazas libres y, por último, el monitoreo remoto de la ocupación por Internet de las Cosas (IoT).
- Dificultad:
- Nivel educativo:
- Secundario. Ciclo Básico.
Contenido del manual
- 01 Materiales y equipamiento
- 02 Barreras automáticas y conteo de plazas Nivel inicial
- 03 Mostrar las plazas libres en un display de 7 segmentos Nivel intermedio
- 04 Monitorear el estacionamiento con Internet de las Cosas Nivel avanzado
Materiales y equipamiento
Insumos
| Imagen | Insumo | Cantidad | Plataforma |
|---|---|---|---|
| | Servomotor SG-5010 Uno para la barrera de entrada y otro para la de salida. Se ubica en un ángulo específico (0° barrera baja, 90° barrera levantada); tiene tres cables: alimentación (VCC, GND) y señal. | 2 | Todas |
| | Pulsador Uno en la entrada y otro en la salida. Se colocan debajo de una plataforma para autos: el auto los presiona al llegar y así el sistema cuenta el ingreso o el egreso. En esta reedición se conectan entre el pin y GND usando la resistencia interna de pull-up (INPUT_PULLUP). | 2 | Todas |
| | Resistencia de 220 Ω Limitan la corriente de los LED y de los siete segmentos del display para que no se dañen (un LED, una resistencia). | 10 | Todas |
| | Resistencia de 10 kΩ (opcional) El esquema original de 2019 las usaba como pull-down de los pulsadores. En esta reedición no son necesarias: se usa la resistencia interna de pull-up del microcontrolador, con el pulsador conectado directo entre el pin y GND. | 2 | Todas |
| | LED rojo de 5 mm Indicador de «estacionamiento completo». La pata larga (positiva) va, a través de su resistencia, al pin de la placa. En el nivel intermedio lo reemplaza el display de 7 segmentos. | 1 | Todas |
| | LED verde de 5 mm Indicador de «hay lugar disponible». En el nivel intermedio lo reemplaza el display de 7 segmentos. | 1 | Todas |
| | Display de 7 segmentos Muestra en pantalla la cantidad exacta de plazas libres (nivel intermedio). Cada segmento es un LED con su resistencia; se maneja desde siete salidas digitales de la placa. | 1 | Todas |
| | Cable dupont macho-hembra | 20 | Todas |
| | Cable dupont hembra-hembra | 20 | Todas |
| | Protoboard | 1 | Todas |
| | Arduino UNO R3 El original de 2019 sumaba un módulo IoT OBLOQ para el nivel avanzado; ese módulo está discontinuado. Con UNO, el nivel IoT se sigue de manera conceptual y la práctica se realiza con la variante ESP32. | 1 | Arduino UNO R3 |
| | Cable USB tipo B | 1 | Arduino UNO R3 |
| | Fuente de 9 V 1 A (plug centro positivo, 5,5 x 2,1 mm) (opcional) Para alimentar el sistema sin la computadora. Los dos servos consumen bastante: conviene alimentarlos con una fuente aparte y no solo desde el USB. El ESP32 se alimenta 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 |
| | 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 |
| Servo barrera entrada · VCC | Bus + de la protoboard | rojo |
| Servo barrera entrada · GND | Bus − de la protoboard | negro |
| Servo barrera entrada · señal | Arduino UNO · 4 | azul |
| Servo barrera salida · VCC | Bus + de la protoboard | rojo |
| Servo barrera salida · GND | Bus − de la protoboard | negro |
| Servo barrera salida · señal | Arduino UNO · 8 | naranja |
| Pulsador entrada | Arduino UNO · 2 | amarillo |
| Pulsador entrada (otro terminal) | Bus − de la protoboard | negro |
| Pulsador salida | Arduino UNO · 3 | violeta |
| Pulsador salida (otro terminal) | Bus − de la protoboard | negro |
| Display 7 segmentos · segmento a (R 220 Ω) | Arduino UNO · 12 | verde |
| Display 7 segmentos · segmento b (R 220 Ω) | Arduino UNO · 13 | verde |
| Display 7 segmentos · segmento c (R 220 Ω) | Arduino UNO · 5 | verde |
| Display 7 segmentos · segmento d (R 220 Ω) | Arduino UNO · 6 | verde |
| Display 7 segmentos · segmento e (R 220 Ω) | Arduino UNO · 7 | verde |
| Display 7 segmentos · segmento f (R 220 Ω) | Arduino UNO · 11 | verde |
| Display 7 segmentos · segmento g (R 220 Ω) | Arduino UNO · 10 | verde |
| Display 7 segmentos · común | Bus − de la protoboard | negro |
| Desde | Hacia | Color de cable |
|---|---|---|
| Fuente externa 5 V | Bus + de la protoboard | rojo |
| Fuente externa 5 V · GND | Bus − de la protoboard | negro |
| ESP32 DevKit v1 · GND | Bus − de la protoboard | negro |
| Servo barrera entrada · VCC | Bus + de la protoboard | rojo |
| Servo barrera entrada · GND | Bus − de la protoboard | negro |
| Servo barrera entrada · señal | ESP32 DevKit v1 · GPIO25 | azul |
| Servo barrera salida · VCC | Bus + de la protoboard | rojo |
| Servo barrera salida · GND | Bus − de la protoboard | negro |
| Servo barrera salida · señal | ESP32 DevKit v1 · GPIO26 | naranja |
| Pulsador entrada | ESP32 DevKit v1 · GPIO32 | amarillo |
| Pulsador entrada (otro terminal) | Bus − de la protoboard | negro |
| Pulsador salida | ESP32 DevKit v1 · GPIO33 | violeta |
| Pulsador salida (otro terminal) | Bus − de la protoboard | negro |
| Display 7 segmentos · segmento a (R 220 Ω) | ESP32 DevKit v1 · GPIO16 | verde |
| Display 7 segmentos · segmento b (R 220 Ω) | ESP32 DevKit v1 · GPIO17 | verde |
| Display 7 segmentos · segmento c (R 220 Ω) | ESP32 DevKit v1 · GPIO18 | verde |
| Display 7 segmentos · segmento d (R 220 Ω) | ESP32 DevKit v1 · GPIO19 | verde |
| Display 7 segmentos · segmento e (R 220 Ω) | ESP32 DevKit v1 · GPIO21 | verde |
| Display 7 segmentos · segmento f (R 220 Ω) | ESP32 DevKit v1 · GPIO22 | verde |
| Display 7 segmentos · segmento g (R 220 Ω) | ESP32 DevKit v1 · GPIO23 | verde |
| Display 7 segmentos · común | Bus − de la protoboard | negro |
Barreras automáticas y conteo de plazas
Montar el servomotor de la barrera en la protoboard
El sistema arranca por la barrera. Vamos a controlar su movimiento con un servomotorServomotorMotor que se ubica en un ángulo específico (los comunes, de 0° a 180°) y se mantiene ahí. Se programa indicando el ángulo deseado; tiene tres cables: alimentación (VCC, GND) y señal.: cuando llegue un auto a la entrada, la barrera se levantará con un giro del servo a un ángulo de 90° y luego volverá a bajar a 0°.
El servo tiene tres cables: VCC (alimentación, cable rojo o marrón), GND (masa, cable negro o marrón) y señal (cable naranja o amarillo), que es el que se conecta a una salida digital de la placa y le indica el ángulo. Empezamos conectando un servo (la barrera de entrada); en el paso 4 sumamos el segundo, para la salida.
En el Arduino UNO, el cable de señal del servo de entrada va al pin 4 (la constante
PIN_SERVO_ENTRADA del código), VCC a 5V y GND a GND.
Ojo con un error del material original: el esquema de 2019 conectaba los servos a los pines 0 y 1. Esos dos pines son los que el UNO usa para comunicarse con la computadora por USB (RX y TX): si se los ocupa con los servos, no se puede cargar el programa ni usar el Monitor Serie, y el servo se mueve de forma errática mientras la placa se comunica. Por eso en esta reedición los servos van a pines libres: la barrera de entrada al 4 y la de salida al 8.
En el ESP32, el cable de señal del servo de entrada va al GPIO 25 (la constante
PIN_SERVO_ENTRADA del código). La alimentación del servo conviene tomarla de una fuente de 5 V
aparte (los servos consumen más de lo que el ESP32 entrega con comodidad) y unir siempre el GND
de esa fuente con el GND del ESP32. Para el ESP32 se usa la librería ESP32Servo, que reemplaza
a la Servo.h clásica del UNO.
Programar el movimiento de la barrera
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.
Para mover el servo alcanza con indicarle el ángulo: en el código, una instrucción lleva la
barrera a 90 (levantada) y otra a 0 (baja). Como primera prueba conviene hacer la secuencia
completa —subir la barrera, esperar dos segundos, bajarla, esperar dos segundos— y ver el brazo
moverse. En este primer nivel usamos delay() para las esperas: es lo más simple y hace que el
código se lea igual que su comportamiento (en el nivel avanzado lo cambiaremos, porque el IoT lo
exige).
Accionar la barrera con el pulsador de entrada
Ahora sumamos el pulsadorPulsadorInterruptor que normalmente está abierto y, mientras se lo mantiene presionado, cierra el circuito entre sus patas. Se usa como botón para que el usuario interactúe con el dispositivo, o como sensor de contacto (fin de carrera). de entrada, para que la barrera se accione sola cuando llega un auto. Más adelante lo colocaremos debajo de una plataforma en la entrada: el auto la pisa, el pulsador se presiona y la barrera se levanta.
Un pulsador es un interruptor que normalmente está abierto y, mientras se lo mantiene presionado,
cierra el circuito. En esta reedición lo conectamos directo entre el pin y GND, aprovechando
la resistencia interna de pull-up del microcontrolador (INPUT_PULLUP): así el pin lee un
valor estable en reposo y otro al presionarse, sin necesidad de la resistencia externa de 10 kΩ
que usaba el esquema original.
La lógica que programamos es: cuando se presiona el pulsador de entrada, la barrera se abre (servo a 90°); mientras el auto sigue sobre la plataforma esperamos, y una vez que pasó, esperamos tres segundos y bajamos la barrera (servo a 0°).
En el Arduino UNO, el pulsador de entrada va al pin 2 (la constante PIN_PULSADOR_ENTRADA).
En el ESP32, el pulsador de entrada va al GPIO 32 (la constante PIN_PULSADOR_ENTRADA).
Contar los autos: barrera de salida y variable de lugares
Con una sola barrera no alcanza para llevar la cuenta. Sumamos la barrera de salida con su propio servo y su propio pulsador, iguales a los de la entrada, y una variable que guarde cuántos lugares quedan libres.
Consideramos que el estacionamiento tiene 8 plazas. La variable lugares arranca en 8. Cada
vez que un auto entra, hay un lugar menos: el programa resta 1. Cada vez que un auto
sale, hay un lugar más: suma 1. Así, en todo momento, lugares refleja los espacios
disponibles.
En el Arduino UNO, la barrera de salida usa el servo en el pin 8 (PIN_SERVO_SALIDA) y el
pulsador de salida en el pin 3 (PIN_PULSADOR_SALIDA).
En el ESP32, la barrera de salida usa el servo en el GPIO 26 (PIN_SERVO_SALIDA) y el pulsador
de salida en el GPIO 33 (PIN_PULSADOR_SALIDA).
Indicar la ocupación con dos LED e impedir el ingreso si está lleno
Falta que el sistema avise si hay lugar. Agregamos dos LEDLEDComponente electrónico tipo diodo que emite luz. Tiene polaridad: la pata larga es la positiva y la corta (o el lado plano del encapsulado) la negativa. Conectado al revés no se daña, pero no enciende.: uno verde que se enciende cuando hay al menos una plaza libre y uno rojo que se enciende cuando están todas ocupadas. Cada LED tiene polaridad —la pata larga es la positiva y va al pin de la placa— y una resistenciaResistenciaComponente que se opone al paso de la corriente eléctrica. Su valor se mide en ohm (Ω). Se usa para determinar la cantidad de corriente de una rama del circuito, por ejemplo para evitar que un LED se queme por exceso de corriente. de 220 Ω en serie que limita la corriente.
En el Arduino UNO, el LED verde («hay lugar») va al pin 9 (PIN_LED_VERDE) y el rojo
(«completo») al pin 13 (PIN_LED_ROJO). Como el pin 8 ya lo usa el servo de salida, el LED
rojo se lleva a un pin de salida libre. Recordá que los pines se reasignan según lo que ya tengas
ocupado; lo que manda son las constantes del código, no un número fijo.
En el ESP32, el LED verde va al GPIO 27 (PIN_LED_VERDE) y el rojo al GPIO 14
(PIN_LED_ROJO), dos GPIO de salida libres.
La última pieza de la lógica es impedir el ingreso cuando no hay lugar: para abrir la barrera
de entrada no alcanza con que el pulsador esté presionado, también tiene que haber al menos una
plaza disponible (lugares mayor que 0). La barrera de salida, en cambio, siempre puede abrirse.
Este es el programa completo del nivel inicial: las dos barreras, el conteo con lugares y los
dos LED.
Ver el código completo · barrera-pulsadores.ino
// Estacionamiento automatizado — Nivel Inicial (Arduino UNO).
//
// Maqueta a escala de un estacionamiento de 8 plazas con DOS barreras:
// - Barrera de ENTRADA: un pulsador la abre (servo a 90) SOLO si quedan
// lugares; al pasar el auto, se cierra y se resta un lugar.
// - Barrera de SALIDA: un pulsador la abre; al salir el auto, se cierra y
// se suma un lugar.
// Dos LED indican de un vistazo la ocupacion, como pide la situacion-problema
// de Juana: VERDE = hay lugar, ROJO = estacionamiento completo.
//
// Plataforma: Arduino UNO. Proyecto de BAJA TENSION (servos 5 V, LED,
// pulsadores): no hay 220 V ni agua, no lleva advertencias electricas.
//
// Nivel inicial: se permite delay() (fiel a la progresion del original).
//
// Basado en: Fragmentos de codigo del Nivel Inicial (Pasos 2, 4 y 5) —
// Estacionamiento Automatizado, Saberes Digitales (INET/EDUCAR 2019).
// CORRECCION DE BUG CRITICO (ver README):
// El original ataba servoEntrada al pin 0 y servoSalida al pin 1. Los pines
// 0 y 1 del UNO son el RX/TX del USB/serial de programacion: usarlos para
// los servos ROMPE la carga del sketch y el Monitor Serie. Se remapean a
// los pines 4 (entrada) y 8 (salida), que estan libres.
#if defined(ESP32)
#include <ESP32Servo.h>
#else
#include <Servo.h>
#endif
// --- Capacidad del estacionamiento ---
const int CAPACIDAD = 8;
// --- Pines de las barreras (servos) — REMAPEADOS (bug del original) ---
const int PIN_SERVO_ENTRADA = 4; // original: pin 0 (RX) = BUG, rompe el USB
const int PIN_SERVO_SALIDA = 8; // original: pin 1 (TX) = BUG, rompe el USB
// --- Pines de los pulsadores ---
// El original usa resistencias de 10K como PULLDOWN (estan en los insumos):
// el pulsador lee HIGH cuando se presiona y LOW en reposo.
const int PIN_PULSADOR_ENTRADA = 2;
const int PIN_PULSADOR_SALIDA = 3;
// --- Pines de los LED de estado ---
// Estos dos LED existen SOLO en el nivel inicial. En el nivel intermedio se
// reemplazan por el display de 7 segmentos (que reutiliza estos pines).
const int PIN_LED_VERDE = 9; // encendido = hay lugares disponibles
const int PIN_LED_ROJO = 13; // encendido = estacionamiento completo
// --- Angulos de la barrera ---
const int ANGULO_ABIERTA = 90;
const int ANGULO_CERRADA = 0;
// Tiempo (ms) que se espera, tras liberar el pulsador, a que el auto termine
// de cruzar antes de bajar la barrera.
const unsigned long TIEMPO_PASO_MS = 3000;
// Lugares disponibles. Arranca vacio: 8 lugares libres.
int lugares = CAPACIDAD;
Servo servoEntrada;
Servo servoSalida;
void setup() {
pinMode(PIN_PULSADOR_ENTRADA, INPUT);
pinMode(PIN_PULSADOR_SALIDA, INPUT);
pinMode(PIN_LED_VERDE, OUTPUT);
pinMode(PIN_LED_ROJO, OUTPUT);
servoEntrada.attach(PIN_SERVO_ENTRADA);
servoSalida.attach(PIN_SERVO_SALIDA);
// Ambas barreras arrancan bajas (cerradas).
servoEntrada.write(ANGULO_CERRADA);
servoSalida.write(ANGULO_CERRADA);
}
void loop() {
// --- ENTRADA: solo se abre si hay al menos un lugar libre ---
if (digitalRead(PIN_PULSADOR_ENTRADA) == HIGH && lugares >= 1) {
servoEntrada.write(ANGULO_ABIERTA);
// Mientras el auto sigue sobre el pulsador, esperamos.
while (digitalRead(PIN_PULSADOR_ENTRADA) == HIGH) {
}
delay(TIEMPO_PASO_MS);
servoEntrada.write(ANGULO_CERRADA);
lugares = lugares - 1; // entro un auto: un lugar menos
}
// --- SALIDA: solo tiene sentido si hay algun auto adentro (lugares < 8) ---
// El guard (lugares < CAPACIDAD) evita que el contador supere la capacidad.
if (digitalRead(PIN_PULSADOR_SALIDA) == HIGH && lugares < CAPACIDAD) {
servoSalida.write(ANGULO_ABIERTA);
while (digitalRead(PIN_PULSADOR_SALIDA) == HIGH) {
}
delay(TIEMPO_PASO_MS);
servoSalida.write(ANGULO_CERRADA);
lugares = lugares + 1; // salio un auto: un lugar mas
}
// --- LED de estado ---
if (lugares > 0) {
digitalWrite(PIN_LED_VERDE, HIGH);
digitalWrite(PIN_LED_ROJO, LOW);
} else {
digitalWrite(PIN_LED_VERDE, LOW);
digitalWrite(PIN_LED_ROJO, HIGH);
}
} // Estacionamiento automatizado — Nivel Inicial (ESP32 DevKit v1).
//
// Maqueta a escala de un estacionamiento de 8 plazas con DOS barreras:
// - Barrera de ENTRADA: un pulsador la abre (servo a 90) SOLO si quedan
// lugares; al pasar el auto, se cierra y se resta un lugar.
// - Barrera de SALIDA: un pulsador la abre; al salir el auto, se cierra y
// se suma un lugar.
// Dos LED indican la ocupacion: VERDE = hay lugar, ROJO = completo.
//
// Plataforma: ESP32 (DevKit v1). Servo con la libreria ESP32Servo (la Servo.h
// clasica no funciona en ESP32). Proyecto de BAJA TENSION: sin 220 V ni agua.
// Nota electrica: el ESP32 trabaja a 3,3 V; los servos se alimentan desde 5 V
// con GND comun con la placa.
//
// Nivel inicial: se permite delay() (fiel a la progresion del original).
//
// Basado en: Fragmentos de codigo del Nivel Inicial (Pasos 2, 4 y 5) —
// Estacionamiento Automatizado, Saberes Digitales (INET/EDUCAR 2019).
// Los servos van a GPIO seguros del contrato (25/26); no aplica el bug de los
// pines 0/1 del UNO, pero la eleccion de GPIO evita strapping pins.
#if defined(ESP32)
#include <ESP32Servo.h>
#else
#include <Servo.h>
#endif
// --- Capacidad del estacionamiento ---
const int CAPACIDAD = 8;
// --- Pines de las barreras (servos) — GPIO del contrato ---
const int PIN_SERVO_ENTRADA = 25;
const int PIN_SERVO_SALIDA = 26;
// --- Pines de los pulsadores (con resistencia de 10K como pulldown) ---
const int PIN_PULSADOR_ENTRADA = 32;
const int PIN_PULSADOR_SALIDA = 33;
// --- Pines de los LED de estado (solo en el nivel inicial) ---
// GPIO libres y seguros; en intermedio el display reemplaza a los LED.
const int PIN_LED_VERDE = 27; // encendido = hay lugares disponibles
const int PIN_LED_ROJO = 14; // encendido = estacionamiento completo
// --- Angulos de la barrera ---
const int ANGULO_ABIERTA = 90;
const int ANGULO_CERRADA = 0;
// Espera (ms) a que el auto cruce antes de bajar la barrera.
const unsigned long TIEMPO_PASO_MS = 3000;
// Lugares disponibles. Arranca vacio: 8 lugares libres.
int lugares = CAPACIDAD;
Servo servoEntrada;
Servo servoSalida;
void setup() {
pinMode(PIN_PULSADOR_ENTRADA, INPUT);
pinMode(PIN_PULSADOR_SALIDA, INPUT);
pinMode(PIN_LED_VERDE, OUTPUT);
pinMode(PIN_LED_ROJO, OUTPUT);
servoEntrada.attach(PIN_SERVO_ENTRADA);
servoSalida.attach(PIN_SERVO_SALIDA);
servoEntrada.write(ANGULO_CERRADA);
servoSalida.write(ANGULO_CERRADA);
}
void loop() {
// --- ENTRADA: solo se abre si hay al menos un lugar libre ---
if (digitalRead(PIN_PULSADOR_ENTRADA) == HIGH && lugares >= 1) {
servoEntrada.write(ANGULO_ABIERTA);
while (digitalRead(PIN_PULSADOR_ENTRADA) == HIGH) {
}
delay(TIEMPO_PASO_MS);
servoEntrada.write(ANGULO_CERRADA);
lugares = lugares - 1;
}
// --- SALIDA: guard (lugares < CAPACIDAD) evita superar la capacidad ---
if (digitalRead(PIN_PULSADOR_SALIDA) == HIGH && lugares < CAPACIDAD) {
servoSalida.write(ANGULO_ABIERTA);
while (digitalRead(PIN_PULSADOR_SALIDA) == HIGH) {
}
delay(TIEMPO_PASO_MS);
servoSalida.write(ANGULO_CERRADA);
lugares = lugares + 1;
}
// --- LED de estado ---
if (lugares > 0) {
digitalWrite(PIN_LED_VERDE, HIGH);
digitalWrite(PIN_LED_ROJO, LOW);
} else {
digitalWrite(PIN_LED_VERDE, LOW);
digitalWrite(PIN_LED_ROJO, HIGH);
}
} Podés ver el comportamiento —los autos entrando y saliendo, el conteo y los LED cambiando— en el simulador, antes de armar el circuito real:
Simulación del comportamiento
La misma máquina de estados del firmware, portada a TypeScript, maneja el
comportamiento del dispositivo. No se ejecuta el binario real: la ejecución
del .hex con avr8js está en el roadmap.
Simulación lista. Presioná «Iniciar» para arrancar la secuencia.
Para subir el código a la placa, conectala a la computadora con el cable USB, elegí en el Arduino IDE 2.x la placa y el puertoPuerto COMPuerto de comunicaciones por el cual el sistema operativo se comunica con un dispositivo externo, como la placa. Se asigna automáticamente al conectarla por USB y puede cambiar de número; en el IDE hay que indicar cuál usar. (en el IDE 2.x se eligen juntos en el menú desplegable de la barra superior) y apretá el botón Subir (la flecha →). Al terminar, aparece el mensaje de carga completada en la barra inferior.
Para el Arduino UNO: usá el cable USB tipo B, elegí la placa Arduino UNO y el puerto que se
agregó al conectarla (en Windows es un puerto COM numerado; en Linux suele llamarse
/dev/ttyACM0 o similar). Como ahora los servos están en los pines 4 y 8 (y no en el 0/1), la
carga funciona sin desconectar 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 del IDE (buscá “esp32” e instalá el paquete de Espressif) y la librería ESP32Servo desde el gestor de bibliotecas. Después elegí la placa ESP32 Dev Module 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.
Verificá
Con el programa cargado, presioná el pulsador de entrada: la barrera de entrada tiene que levantarse, esperar y volver a bajar, y el LED verde debe seguir encendido mientras queden plazas. Repetí hasta “llenar” las 8 plazas: al llegar a cero, el LED verde se apaga, se enciende el rojo y la barrera de entrada ya no se abre aunque presiones. Presioná el pulsador de salida: la barrera de salida se abre, suma un lugar y el LED verde vuelve a encenderse. Si un servo se mueve solo o no podés cargar el programa, revisá que no esté en los pines 0 o 1.
Armar la maqueta del estacionamiento
Con el circuito probado sobre la protoboard, podemos pasar a la maqueta. Una vez que las conexiones funcionan, se puede prescindir de la protoboard soldando cada componente con un cable a la placa y ubicando cada pieza en su lugar dentro de la maqueta.
Para diseñar la planta del estacionamiento con los estudiantes hay que tener en cuenta la ubicación de los dos servomotores con sus barreras (entrada y salida, en calles distintas), la de los dos pulsadores bajo sus plataformas basculantes, la de los LED y la distribución de las 8 plazas.
Esta parte es de fabricación. Los brazos de las barreras y los soportes de los servos (piezas impresas en 3D) y la planta del estacionamiento (corte láser en MDF 3 mm) forman parte de los paquetes de fabricación del proyecto, que se descargan desde su ficha. Mientras tanto, la maqueta puede construirse con materiales simples: una base de cartón o madera con las plazas dibujadas, palitos o cartulina para las barreras encastradas en el brazo del servo y pequeñas plataformas de cartón sobre cada pulsador.
Para el aula
En el nivel intermedio, por la complejidad del cableado del display, el original propone cerrar el proyecto en la protoboard en lugar de pasar a maqueta. Por eso conviene decidir con el grupo: si el recorrido termina en el nivel inicial, vale la pena invertir en una maqueta prolija; si van a seguir a intermedio, quizás convenga dejar la maqueta para el final. Es una buena oportunidad para conversar sobre planificación de un proyecto.
Cierre del nivel
Al terminar este nivel, el estacionamiento de Juana ya se gestiona solo: las barreras se levantan
al llegar un auto, el sistema lleva la cuenta de las plazas con la variable lugares y los LED
avisan si hay lugar o si está completo, bloqueando la entrada cuando no quedan espacios. Además,
corregiste el error del original que conectaba los servos a los pines del USB. Pero los LED solo
dicen “hay / no hay” lugar: no cuántos. En el nivel intermedio vamos a agregar un display de
7 segmentos para ver en pantalla el número exacto de plazas libres.
Evidencia de cierre
Al terminar este nivel, esto tiene que estar funcionando:
- El circuito está montado en la protoboard: dos servomotores (barreras de entrada y de salida), dos pulsadores y los LED verde y rojo, con los servos conectados a pines libres (no a los pines 0 y 1 del USB).
- Sabés subir un programa desde el Arduino IDE 2.x: elegir la placa, elegir el puerto y verificar que quedó cargado.
- Al presionar el pulsador de entrada la barrera se levanta (servo a 90°), espera y vuelve a bajar (0°), y el programa descuenta un lugar; el pulsador de salida hace lo inverso y suma un lugar.
- El LED verde queda encendido mientras hay plazas libres y el rojo cuando el estacionamiento está completo; con el estacionamiento lleno, la barrera de entrada no se abre.
- La maqueta del estacionamiento está armada con sus 8 plazas, las dos barreras, los pulsadores y los LED.
Mostrar las plazas libres en un display de 7 segmentos
Situación problema
A veces se forma fila de autos para entrar al estacionamiento. El sistema del nivel inicial indica si hay al menos un espacio libre, pero no cuántos: por eso Juana no puede saber si hay lugar suficiente para todos los vehículos que esperan. Decidió, entonces, mejorar el sistema para ver la cantidad exacta de plazas libres.
En esta segunda etapa vamos a agregar a la maqueta un display de 7 segmentosDisplay de 7 segmentosComponente que representa números encendiendo o apagando siete LED con forma de línea, identificados con letras de la «a» a la «g». No tiene circuito de control propio: cada segmento se maneja desde una salida digital de la placa. que permita visualizar la cantidad exacta de lugares disponibles.
Conectar el display de 7 segmentos
El display de 7 segmentosDisplay de 7 segmentosComponente que representa números encendiendo o apagando siete LED con forma de línea, identificados con letras de la «a» a la «g». No tiene circuito de control propio: cada segmento se maneja desde una salida digital de la placa. es un componente que representa números encendiendo o apagando siete LED con forma de línea. Cada segmento se identifica con una letra, de la a a la g: por ejemplo, el segmento g es el trazo horizontal del centro. Como cada segmento es un LED, cada uno necesita su resistencia de 220 Ω en serie, y el pin común del display va a GND.
En el Arduino UNO, los siete segmentos se conectan a los pines a=12, b=13, c=5, d=6, e=7, f=11, g=10 (las constantes del código). Son los mismos pines del esquema original.
En el ESP32, los siete segmentos se conectan a los GPIO a=16, b=17, c=18, d=19, e=21, f=22, g=23 (las constantes del código), todos GPIO de salida seguros. La alimentación del display se toma de 3,3 V y GND.
Programar el display: la tabla de segmentos
Programar el display es igual que encender y apagar LED: para mostrar una cifra hay que prender los segmentos correctos y apagar el resto. Si queremos representar un 8, encendemos los siete; para un 4, solo los segmentos b, c, f y g.
Cada carácter tiene su combinación de segmentos prendidos y apagados. En lugar de escribir el encendido a mano para cada número, conviene guardar esa correspondencia en una tabla dentro del código: una fila por cada cifra (del 0 al 9) y una columna por cada segmento (a, b, c, d, e, f, g), con un 1 si el segmento va encendido y un 0 si va apagado. Esa tabla se parece mucho a la de lápiz y papel donde marcamos, para cada número, qué segmentos se dibujan.
Para el aula
Este paso es una excelente entrada a arrays de dos dimensiones y al hecho de que en
programación los índices arrancan en 0, no en 1: la fila segmentos[4] es la del número 4, y
segmentos[4][0] es su segmento a. Vale la pena que el grupo complete primero la tabla en papel
(diez números x siete segmentos) y recién después la traslade al código: se apropian de la idea de
que el dato y la lógica van separados.
Escribir cualquier cifra con escribir_numero()
Con la tabla lista, necesitamos una función que reciba un número y encienda o apague los
segmentos que le corresponden. La llamamos escribir_numero(): recibe un valor (por ejemplo, 2) y
usa la fila de la tabla de ese número para escribir cada segmento. Así, en vez de repetir siete
instrucciones cada vez, con una sola línea —escribir_numero(2)— mostramos el 2 en el display.
Como prueba, se puede recorrer del 0 al 9 con una demora de un segundo entre cada número: el display irá mostrando la cuenta y, al terminar, volverá a empezar. Ese ejercicio confirma que la tabla y la función están bien antes de conectarlas al estacionamiento.
Incorporar el display al estacionamiento
Ahora conectamos el display al sistema del nivel inicial para que muestre cuántas plazas libres
quedan y reemplace a los dos LED verde y rojo. La lógica de las barreras y el conteo es la
misma: lo único que cambia es que, cada vez que lugares sube o baja, llamamos a
escribir_numero(lugares) para actualizar la pantalla. La variable arranca en 8, así que al
encender el display muestra un 8.
Ver el código completo · contador-display.ino
// Estacionamiento automatizado — Nivel Intermedio (Arduino UNO).
//
// Igual que el nivel inicial (dos barreras + contador de lugares), pero los dos
// LED verde/rojo se reemplazan por un DISPLAY DE 7 SEGMENTOS que muestra el
// numero EXACTO de lugares libres (0 a 8), como pide la situacion-problema
// (Juana quiere saber cuantos lugares quedan, no solo si hay o no hay).
//
// Display de 7 segmentos de CATODO COMUN: cada segmento (a..g) se enciende con
// un HIGH en su pin. La tabla segmentos[10][7] define que segmentos van
// prendidos para cada digito, y escribir_numero() la aplica.
//
// Plataforma: Arduino UNO. Proyecto de BAJA TENSION (servos 5 V, display,
// pulsadores): no hay 220 V ni agua, no lleva advertencias electricas.
//
// Nivel intermedio: se permite delay() (fiel a la progresion del original).
//
// Basado en: Fragmentos de codigo del Nivel Intermedio (Pasos 3 y 4) —
// Estacionamiento Automatizado, Saberes Digitales (INET/EDUCAR 2019).
// CORRECCION DE BUG CRITICO (ver README): servoEntrada/servoSalida se
// remapean de los pines 0/1 (RX/TX del USB, que rompen la carga y el serial)
// a los pines 4 y 8.
#if defined(ESP32)
#include <ESP32Servo.h>
#else
#include <Servo.h>
#endif
// --- Capacidad del estacionamiento ---
const int CAPACIDAD = 8;
// --- Pines de las barreras (servos) — REMAPEADOS (bug del original) ---
const int PIN_SERVO_ENTRADA = 4; // original: pin 0 (RX) = BUG
const int PIN_SERVO_SALIDA = 8; // original: pin 1 (TX) = BUG
// --- Pines de los pulsadores (10K como pulldown, del original) ---
const int PIN_PULSADOR_ENTRADA = 2;
const int PIN_PULSADOR_SALIDA = 3;
// --- Pines del display de 7 segmentos (segmentos a..g), fieles al original ---
const int PIN_A = 12;
const int PIN_B = 13;
const int PIN_C = 5;
const int PIN_D = 6;
const int PIN_E = 7;
const int PIN_F = 11;
const int PIN_G = 10;
// --- Angulos de la barrera ---
const int ANGULO_ABIERTA = 90;
const int ANGULO_CERRADA = 0;
// Espera (ms) a que el auto cruce antes de bajar la barrera.
const unsigned long TIEMPO_PASO_MS = 3000;
// Lugares disponibles. Arranca vacio: 8 lugares libres.
int lugares = CAPACIDAD;
// Vector de 10x7 con la configuracion de los segmentos {a,b,c,d,e,f,g} de cada
// caracter numerico (1 = segmento encendido). Es la tabla del original.
int segmentos[10][7] = { { 1, 1, 1, 1, 1, 1, 0 }, // 0
{ 0, 1, 1, 0, 0, 0, 0 }, // 1
{ 1, 1, 0, 1, 1, 0, 1 }, // 2
{ 1, 1, 1, 1, 0, 0, 1 }, // 3
{ 0, 1, 1, 0, 0, 1, 1 }, // 4
{ 1, 0, 1, 1, 0, 1, 1 }, // 5
{ 1, 0, 1, 1, 1, 1, 1 }, // 6
{ 1, 1, 1, 0, 0, 0, 0 }, // 7
{ 1, 1, 1, 1, 1, 1, 1 }, // 8
{ 1, 1, 1, 1, 0, 1, 1 } }; // 9
Servo servoEntrada;
Servo servoSalida;
// escribir_numero recibe un valor (0 a 9) y enciende/apaga los segmentos
// correspondientes segun la tabla "segmentos".
void escribir_numero(int num) {
digitalWrite(PIN_A, segmentos[num][0]);
digitalWrite(PIN_B, segmentos[num][1]);
digitalWrite(PIN_C, segmentos[num][2]);
digitalWrite(PIN_D, segmentos[num][3]);
digitalWrite(PIN_E, segmentos[num][4]);
digitalWrite(PIN_F, segmentos[num][5]);
digitalWrite(PIN_G, segmentos[num][6]);
}
void setup() {
pinMode(PIN_PULSADOR_ENTRADA, INPUT);
pinMode(PIN_PULSADOR_SALIDA, INPUT);
pinMode(PIN_A, OUTPUT);
pinMode(PIN_B, OUTPUT);
pinMode(PIN_C, OUTPUT);
pinMode(PIN_D, OUTPUT);
pinMode(PIN_E, OUTPUT);
pinMode(PIN_F, OUTPUT);
pinMode(PIN_G, OUTPUT);
servoEntrada.attach(PIN_SERVO_ENTRADA);
servoSalida.attach(PIN_SERVO_SALIDA);
servoEntrada.write(ANGULO_CERRADA);
servoSalida.write(ANGULO_CERRADA);
// Mostramos el valor inicial de lugares (8) en el display.
escribir_numero(lugares);
}
void loop() {
// --- ENTRADA: solo se abre si hay al menos un lugar libre ---
if (digitalRead(PIN_PULSADOR_ENTRADA) == HIGH && lugares >= 1) {
servoEntrada.write(ANGULO_ABIERTA);
while (digitalRead(PIN_PULSADOR_ENTRADA) == HIGH) {
}
delay(TIEMPO_PASO_MS);
servoEntrada.write(ANGULO_CERRADA);
lugares = lugares - 1;
escribir_numero(lugares);
}
// --- SALIDA: guard lugares < CAPACIDAD evita superar 8 (y un indice fuera
// de rango en la tabla segmentos[]) ---
if (digitalRead(PIN_PULSADOR_SALIDA) == HIGH && lugares < CAPACIDAD) {
servoSalida.write(ANGULO_ABIERTA);
while (digitalRead(PIN_PULSADOR_SALIDA) == HIGH) {
}
delay(TIEMPO_PASO_MS);
servoSalida.write(ANGULO_CERRADA);
lugares = lugares + 1;
escribir_numero(lugares);
}
} // Estacionamiento automatizado — Nivel Intermedio (ESP32 DevKit v1).
//
// Igual que el nivel inicial (dos barreras + contador de lugares), pero los dos
// LED verde/rojo se reemplazan por un DISPLAY DE 7 SEGMENTOS que muestra el
// numero EXACTO de lugares libres (0 a 8).
//
// Display de 7 segmentos de CATODO COMUN: cada segmento (a..g) se enciende con
// un HIGH. La tabla segmentos[10][7] define que segmentos van prendidos para
// cada digito y escribir_numero() la aplica.
//
// Plataforma: ESP32 (DevKit v1). Servo con ESP32Servo. Proyecto de BAJA
// TENSION: sin 220 V ni agua. Nota electrica: el ESP32 trabaja a 3,3 V; los
// servos se alimentan desde 5 V con GND comun.
//
// Nivel intermedio: se permite delay() (fiel a la progresion del original).
//
// Basado en: Fragmentos de codigo del Nivel Intermedio (Pasos 3 y 4) —
// Estacionamiento Automatizado, Saberes Digitales (INET/EDUCAR 2019).
#if defined(ESP32)
#include <ESP32Servo.h>
#else
#include <Servo.h>
#endif
// --- Capacidad del estacionamiento ---
const int CAPACIDAD = 8;
// --- Pines de las barreras (servos) — GPIO del contrato ---
const int PIN_SERVO_ENTRADA = 25;
const int PIN_SERVO_SALIDA = 26;
// --- Pines de los pulsadores (con resistencia de 10K como pulldown) ---
const int PIN_PULSADOR_ENTRADA = 32;
const int PIN_PULSADOR_SALIDA = 33;
// --- Pines del display de 7 segmentos (segmentos a..g) — GPIO del contrato ---
const int PIN_A = 16;
const int PIN_B = 17;
const int PIN_C = 18;
const int PIN_D = 19;
const int PIN_E = 21;
const int PIN_F = 22;
const int PIN_G = 23;
// --- Angulos de la barrera ---
const int ANGULO_ABIERTA = 90;
const int ANGULO_CERRADA = 0;
// Espera (ms) a que el auto cruce antes de bajar la barrera.
const unsigned long TIEMPO_PASO_MS = 3000;
// Lugares disponibles. Arranca vacio: 8 lugares libres.
int lugares = CAPACIDAD;
// Vector de 10x7 con la configuracion de los segmentos {a,b,c,d,e,f,g}.
int segmentos[10][7] = { { 1, 1, 1, 1, 1, 1, 0 }, // 0
{ 0, 1, 1, 0, 0, 0, 0 }, // 1
{ 1, 1, 0, 1, 1, 0, 1 }, // 2
{ 1, 1, 1, 1, 0, 0, 1 }, // 3
{ 0, 1, 1, 0, 0, 1, 1 }, // 4
{ 1, 0, 1, 1, 0, 1, 1 }, // 5
{ 1, 0, 1, 1, 1, 1, 1 }, // 6
{ 1, 1, 1, 0, 0, 0, 0 }, // 7
{ 1, 1, 1, 1, 1, 1, 1 }, // 8
{ 1, 1, 1, 1, 0, 1, 1 } }; // 9
Servo servoEntrada;
Servo servoSalida;
void escribir_numero(int num) {
digitalWrite(PIN_A, segmentos[num][0]);
digitalWrite(PIN_B, segmentos[num][1]);
digitalWrite(PIN_C, segmentos[num][2]);
digitalWrite(PIN_D, segmentos[num][3]);
digitalWrite(PIN_E, segmentos[num][4]);
digitalWrite(PIN_F, segmentos[num][5]);
digitalWrite(PIN_G, segmentos[num][6]);
}
void setup() {
pinMode(PIN_PULSADOR_ENTRADA, INPUT);
pinMode(PIN_PULSADOR_SALIDA, INPUT);
pinMode(PIN_A, OUTPUT);
pinMode(PIN_B, OUTPUT);
pinMode(PIN_C, OUTPUT);
pinMode(PIN_D, OUTPUT);
pinMode(PIN_E, OUTPUT);
pinMode(PIN_F, OUTPUT);
pinMode(PIN_G, OUTPUT);
servoEntrada.attach(PIN_SERVO_ENTRADA);
servoSalida.attach(PIN_SERVO_SALIDA);
servoEntrada.write(ANGULO_CERRADA);
servoSalida.write(ANGULO_CERRADA);
escribir_numero(lugares);
}
void loop() {
// --- ENTRADA: solo se abre si hay al menos un lugar libre ---
if (digitalRead(PIN_PULSADOR_ENTRADA) == HIGH && lugares >= 1) {
servoEntrada.write(ANGULO_ABIERTA);
while (digitalRead(PIN_PULSADOR_ENTRADA) == HIGH) {
}
delay(TIEMPO_PASO_MS);
servoEntrada.write(ANGULO_CERRADA);
lugares = lugares - 1;
escribir_numero(lugares);
}
// --- SALIDA: guard lugares < CAPACIDAD evita superar 8 (indice fuera de
// rango en segmentos[]) ---
if (digitalRead(PIN_PULSADOR_SALIDA) == HIGH && lugares < CAPACIDAD) {
servoSalida.write(ANGULO_ABIERTA);
while (digitalRead(PIN_PULSADOR_SALIDA) == HIGH) {
}
delay(TIEMPO_PASO_MS);
servoSalida.write(ANGULO_CERRADA);
lugares = lugares + 1;
escribir_numero(lugares);
}
} Podés ver el comportamiento —los autos entrando y saliendo y el display mostrando la cuenta— 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.
¿Y si fueran más de 9 plazas?
Este display muestra un solo dígito, así que llega hasta el 9. Pregunten en grupo: ¿qué harían si el estacionamiento tuviera 12 plazas? ¿Cuántos displays harían falta y cómo repartirían las decenas y las unidades? No hace falta programarlo: alcanza con esbozar la idea y darse cuenta de que la tabla de segmentos ya la tienen resuelta.
Verificá
Con el programa cargado, el display tiene que arrancar mostrando 8. Presioná el pulsador de entrada: al ingresar un auto, la cuenta baja a 7, después a 6, y así. Presioná el de salida: la cuenta sube. Al llegar a 0, la barrera de entrada no debe abrirse (aunque el display no tenga LED de “lleno”, la regla de bloqueo del nivel inicial sigue vigente). Si el display muestra un número equivocado, revisá la tabla de segmentos y que cada segmento esté en el pin correcto.
Cierre del nivel
Al terminar este nivel, el estacionamiento ya no solo dice si hay lugar: muestra cuántos. En el camino aprendiste a manejar un display de 7 segmentos, a guardar datos en una tabla y a encapsular la lógica en una función reutilizable. Pero la información sigue estando solo en el lugar: para saber si hay plazas hay que ir hasta el estacionamiento y mirar el display. En el nivel avanzado vamos a publicar esa información en internet, para poder consultarla a distancia desde un dispositivo móvil.
Evidencia de cierre
Al terminar este nivel, esto tiene que estar funcionando:
- El display de 7 segmentos está conectado: cada segmento (a–g) a su salida digital con una resistencia de 220 Ω, y el común a GND.
- Entendés cómo se representa una cifra encendiendo y apagando segmentos, y podés escribir cualquier número de 0 a 9 con la función escribir_numero() apoyada en la tabla de segmentos.
- El display reemplaza a los dos LED del nivel inicial y muestra en todo momento la cantidad exacta de plazas libres, que baja al entrar un auto y sube al salir.
Monitorear el estacionamiento con Internet de las Cosas
Situación problema
Los primos de Juana tienen otro estacionamiento en el mismo barrio. Cuando se llenan, les sugieren a los conductores que vayan al de Juana, y la llaman por teléfono para preguntarle si tiene lugar; ella hace lo mismo cuando el suyo está completo. Para evitarse esas llamadas, Juana decidió modificar su sistema para poder ver la cantidad de espacios disponibles desde un dispositivo móvil, así sus primos consultan la información sin llamarla.
En esta tercera instancia vamos a agregar conectividad IoT para publicar la ocupación del estacionamiento y poder monitorearla a distancia a través de una web o un dispositivo móvil.
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 los datos de un sensor a una central donde se recopile la información, para visualizarla desde un dispositivo móvil: la ocupación de un estacionamiento, la temperatura de un laboratorio o de un invernadero.
Para poder incorporar IoT a nuestro proyecto es necesario:
- Un dispositivo capaz de conectarse a internet.
- Un servidor que reciba y aloje los datos.
Existen diversas formas de registrar y almacenar los datos del estacionamiento. 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 4, 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.
El ESP32 cumple los dos roles a la vez: es el microcontrolador que ya está moviendo las barreras y contando las plazas, 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 uno con este nombre exacto (sin tildes, porque forma parte de la dirección con la que se publica):
estacionamiento1: recibirá la cantidad de plazas ocupadas del estacionamiento.
Luego, desde la sección Dashboards, creamos un nuevo panel y le agregamos un bloque del tipo
Gauge (medidor), asociado al feed estacionamiento1, para ver de un vistazo qué tan lleno está:
podemos ponerle un título, el rango de 0 a 8 y colores que indiquen cuán ocupado está. Al finalizar,
el panel mostrará a distancia el estado del estacionamiento.
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 5. La clave es secreta: cualquiera que la tenga puede publicar en tus feeds.
Para el aula
Ojo con un detalle heredado del original: el material de 2019 arrastraba, por copiar y pegar de otro proyecto, la frase «informar cada vez que cambie el estado del semáforo». Acá no hay semáforo: el sistema es un estacionamiento y el feed publica plazas ocupadas. Es un buen ejemplo para el aula de por qué conviene leer y adaptar el código heredado en lugar de copiarlo tal cual.
Verificá
Para probar el panel sin la placa, entrá al feed estacionamiento1 y agregale valores a mano (Add
Data): el medidor tiene que moverse según el número que cargues (por ejemplo, 0 = vacío, 8 =
completo). Si el bloque no reacciona, revisá que esté asociado al feed correcto.
Conectar el dispositivo a internet
En el original, este paso consistía en cablear el módulo OBLOQ al Arduino UNO. Como ese módulo está discontinuado, en esta reedición la conexión a internet se hace con el ESP32 y su WiFi nativo: no hay nada que cablear. Cambiá la plataforma a ESP32 con el selector para ver cómo se resuelve; lo conceptual —qué se publica y por qué— lo seguís igual con tu UNO en los pasos siguientes.
En el ESP32 no hay que conectar ningún módulo: la radio WiFi ya está integrada en la placa. Toda la conexión —unirse a la red, mantenerla viva y publicar— se resuelve desde el código, que veremos en el paso 5. Alcanza con tener a mano el nombre (SSID) y la contraseña de una red WiFi con salida a internet.
Programar sin código bloqueante
Antes de usar IoT debemos hacer una aclaración sobre la función delay() que venimos usando (el
programa espera unos segundos con la barrera levantada). Esta función brinda un tiempo de espera 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. 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 mantener viva la
conexión a internet y publicar los datos. Para evitarlo, usamos una alternativa no bloqueante, la
función millis(), que 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
manejar los tiempos de las barreras sin detener el resto del programa.
Ver el código completo · estacionamiento-no-bloqueante.ino
// Estacionamiento automatizado — Nivel Avanzado, no bloqueante (Arduino UNO).
//
// Misma logica que el intermedio (dos barreras + display de 7 segmentos con los
// lugares libres), pero SIN delay() ni while() de espera: cada barrera se
// maneja con una pequena MAQUINA DE ESTADOS basada en millis(). Asi el programa
// nunca queda "colgado" y puede atender ambas barreras a la vez; es la base
// sobre la que se monta el IoT (estacionamiento-iot.ino, solo ESP32).
//
// Estados de cada barrera:
// CERRADA -> si el pulsador esta presionado (y corresponde), abre la barrera.
// ABIERTA -> espera a que el auto libere el pulsador; ahi anota el instante.
// CERRANDO -> pasados 3 s del cruce, baja la barrera y actualiza el contador.
//
// Plataforma: Arduino UNO. Proyecto de BAJA TENSION: no hay 220 V ni agua.
//
// Basado en: Nivel Intermedio (Paso 4) + Nivel Avanzado (Paso 5, "codigo no
// bloqueante con millis()") — Estacionamiento Automatizado, Saberes Digitales
// (INET/EDUCAR 2019).
// CORRECCION DE BUG CRITICO (ver README): servos remapeados de los pines 0/1
// (RX/TX del USB) a los pines 4 y 8.
#if defined(ESP32)
#include <ESP32Servo.h>
#else
#include <Servo.h>
#endif
// --- Capacidad del estacionamiento ---
const int CAPACIDAD = 8;
// --- Pines de las barreras (servos) — REMAPEADOS (bug del original) ---
const int PIN_SERVO_ENTRADA = 4; // original: pin 0 (RX) = BUG
const int PIN_SERVO_SALIDA = 8; // original: pin 1 (TX) = BUG
// --- Pines de los pulsadores (10K como pulldown, del original) ---
const int PIN_PULSADOR_ENTRADA = 2;
const int PIN_PULSADOR_SALIDA = 3;
// --- Pines del display de 7 segmentos (segmentos a..g) ---
const int PIN_A = 12;
const int PIN_B = 13;
const int PIN_C = 5;
const int PIN_D = 6;
const int PIN_E = 7;
const int PIN_F = 11;
const int PIN_G = 10;
// --- Angulos de la barrera ---
const int ANGULO_ABIERTA = 90;
const int ANGULO_CERRADA = 0;
// Espera (ms) a que el auto cruce antes de bajar la barrera.
const unsigned long TIEMPO_PASO_MS = 3000;
// Estados de la maquina de cada barrera.
const int CERRADA = 0;
const int ABIERTA = 1;
const int CERRANDO = 2;
// Lugares disponibles. Arranca vacio: 8 lugares libres.
int lugares = CAPACIDAD;
// Estado y cronometro de cada barrera.
int estadoEntrada = CERRADA;
int estadoSalida = CERRADA;
unsigned long tLiberadoEntrada = 0;
unsigned long tLiberadoSalida = 0;
int segmentos[10][7] = { { 1, 1, 1, 1, 1, 1, 0 }, // 0
{ 0, 1, 1, 0, 0, 0, 0 }, // 1
{ 1, 1, 0, 1, 1, 0, 1 }, // 2
{ 1, 1, 1, 1, 0, 0, 1 }, // 3
{ 0, 1, 1, 0, 0, 1, 1 }, // 4
{ 1, 0, 1, 1, 0, 1, 1 }, // 5
{ 1, 0, 1, 1, 1, 1, 1 }, // 6
{ 1, 1, 1, 0, 0, 0, 0 }, // 7
{ 1, 1, 1, 1, 1, 1, 1 }, // 8
{ 1, 1, 1, 1, 0, 1, 1 } }; // 9
Servo servoEntrada;
Servo servoSalida;
void escribir_numero(int num) {
digitalWrite(PIN_A, segmentos[num][0]);
digitalWrite(PIN_B, segmentos[num][1]);
digitalWrite(PIN_C, segmentos[num][2]);
digitalWrite(PIN_D, segmentos[num][3]);
digitalWrite(PIN_E, segmentos[num][4]);
digitalWrite(PIN_F, segmentos[num][5]);
digitalWrite(PIN_G, segmentos[num][6]);
}
// Maquina de estados de UNA barrera (no bloqueante). esEntrada decide si el
// paso de un auto RESTA (entrada) o SUMA (salida) un lugar, y si la barrera
// puede abrirse (entrada: solo si quedan lugares; salida: solo si hay algun
// auto adentro).
void gestionarBarrera(Servo &servo, int pinPulsador, int &estado,
unsigned long &tLiberado, bool esEntrada) {
switch (estado) {
case CERRADA: {
bool puedeAbrir = esEntrada ? (lugares >= 1) : (lugares < CAPACIDAD);
if (digitalRead(pinPulsador) == HIGH && puedeAbrir) {
servo.write(ANGULO_ABIERTA);
estado = ABIERTA;
}
break;
}
case ABIERTA: {
// Esperamos a que el auto libere el pulsador (sin bloquear).
if (digitalRead(pinPulsador) == LOW) {
tLiberado = millis();
estado = CERRANDO;
}
break;
}
case CERRANDO: {
// Pasados 3 s del cruce, bajamos la barrera y actualizamos el contador.
if (millis() - tLiberado >= TIEMPO_PASO_MS) {
servo.write(ANGULO_CERRADA);
if (esEntrada) {
lugares = lugares - 1;
} else {
lugares = lugares + 1;
}
escribir_numero(lugares);
estado = CERRADA;
}
break;
}
}
}
void setup() {
pinMode(PIN_PULSADOR_ENTRADA, INPUT);
pinMode(PIN_PULSADOR_SALIDA, INPUT);
pinMode(PIN_A, OUTPUT);
pinMode(PIN_B, OUTPUT);
pinMode(PIN_C, OUTPUT);
pinMode(PIN_D, OUTPUT);
pinMode(PIN_E, OUTPUT);
pinMode(PIN_F, OUTPUT);
pinMode(PIN_G, OUTPUT);
servoEntrada.attach(PIN_SERVO_ENTRADA);
servoSalida.attach(PIN_SERVO_SALIDA);
servoEntrada.write(ANGULO_CERRADA);
servoSalida.write(ANGULO_CERRADA);
escribir_numero(lugares);
}
void loop() {
// Ambas barreras se atienden en cada vuelta, sin que ninguna bloquee.
gestionarBarrera(servoEntrada, PIN_PULSADOR_ENTRADA, estadoEntrada,
tLiberadoEntrada, true);
gestionarBarrera(servoSalida, PIN_PULSADOR_SALIDA, estadoSalida,
tLiberadoSalida, false);
} // Estacionamiento automatizado — Nivel Avanzado, no bloqueante (ESP32 DevKit v1).
//
// Misma logica que el intermedio (dos barreras + display de 7 segmentos con los
// lugares libres), pero SIN delay() ni while() de espera: cada barrera se
// maneja con una MAQUINA DE ESTADOS basada en millis(). El programa nunca queda
// "colgado" y puede atender ambas barreras a la vez; es la base sobre la que se
// monta el IoT (estacionamiento-iot.ino).
//
// Estados de cada barrera:
// CERRADA -> si el pulsador esta presionado (y corresponde), abre la barrera.
// ABIERTA -> espera a que el auto libere el pulsador; ahi anota el instante.
// CERRANDO -> pasados 3 s del cruce, baja la barrera y actualiza el contador.
//
// Plataforma: ESP32 (DevKit v1). Servo con ESP32Servo. Proyecto de BAJA
// TENSION: sin 220 V ni agua. Nota electrica: 3,3 V; servos desde 5 V, GND comun.
//
// Basado en: Nivel Intermedio (Paso 4) + Nivel Avanzado (Paso 5, "codigo no
// bloqueante con millis()") — Estacionamiento Automatizado, Saberes Digitales
// (INET/EDUCAR 2019).
#if defined(ESP32)
#include <ESP32Servo.h>
#else
#include <Servo.h>
#endif
// --- Capacidad del estacionamiento ---
const int CAPACIDAD = 8;
// --- Pines de las barreras (servos) — GPIO del contrato ---
const int PIN_SERVO_ENTRADA = 25;
const int PIN_SERVO_SALIDA = 26;
// --- Pines de los pulsadores (con resistencia de 10K como pulldown) ---
const int PIN_PULSADOR_ENTRADA = 32;
const int PIN_PULSADOR_SALIDA = 33;
// --- Pines del display de 7 segmentos (segmentos a..g) — GPIO del contrato ---
const int PIN_A = 16;
const int PIN_B = 17;
const int PIN_C = 18;
const int PIN_D = 19;
const int PIN_E = 21;
const int PIN_F = 22;
const int PIN_G = 23;
// --- Angulos de la barrera ---
const int ANGULO_ABIERTA = 90;
const int ANGULO_CERRADA = 0;
// Espera (ms) a que el auto cruce antes de bajar la barrera.
const unsigned long TIEMPO_PASO_MS = 3000;
// Estados de la maquina de cada barrera.
const int CERRADA = 0;
const int ABIERTA = 1;
const int CERRANDO = 2;
// Lugares disponibles. Arranca vacio: 8 lugares libres.
int lugares = CAPACIDAD;
// Estado y cronometro de cada barrera.
int estadoEntrada = CERRADA;
int estadoSalida = CERRADA;
unsigned long tLiberadoEntrada = 0;
unsigned long tLiberadoSalida = 0;
int segmentos[10][7] = { { 1, 1, 1, 1, 1, 1, 0 }, // 0
{ 0, 1, 1, 0, 0, 0, 0 }, // 1
{ 1, 1, 0, 1, 1, 0, 1 }, // 2
{ 1, 1, 1, 1, 0, 0, 1 }, // 3
{ 0, 1, 1, 0, 0, 1, 1 }, // 4
{ 1, 0, 1, 1, 0, 1, 1 }, // 5
{ 1, 0, 1, 1, 1, 1, 1 }, // 6
{ 1, 1, 1, 0, 0, 0, 0 }, // 7
{ 1, 1, 1, 1, 1, 1, 1 }, // 8
{ 1, 1, 1, 1, 0, 1, 1 } }; // 9
Servo servoEntrada;
Servo servoSalida;
void escribir_numero(int num) {
digitalWrite(PIN_A, segmentos[num][0]);
digitalWrite(PIN_B, segmentos[num][1]);
digitalWrite(PIN_C, segmentos[num][2]);
digitalWrite(PIN_D, segmentos[num][3]);
digitalWrite(PIN_E, segmentos[num][4]);
digitalWrite(PIN_F, segmentos[num][5]);
digitalWrite(PIN_G, segmentos[num][6]);
}
// Maquina de estados de UNA barrera (no bloqueante). esEntrada decide si el
// paso de un auto RESTA (entrada) o SUMA (salida) un lugar, y si la barrera
// puede abrirse (entrada: solo si quedan lugares; salida: solo si hay algun
// auto adentro).
void gestionarBarrera(Servo &servo, int pinPulsador, int &estado,
unsigned long &tLiberado, bool esEntrada) {
switch (estado) {
case CERRADA: {
bool puedeAbrir = esEntrada ? (lugares >= 1) : (lugares < CAPACIDAD);
if (digitalRead(pinPulsador) == HIGH && puedeAbrir) {
servo.write(ANGULO_ABIERTA);
estado = ABIERTA;
}
break;
}
case ABIERTA: {
if (digitalRead(pinPulsador) == LOW) {
tLiberado = millis();
estado = CERRANDO;
}
break;
}
case CERRANDO: {
if (millis() - tLiberado >= TIEMPO_PASO_MS) {
servo.write(ANGULO_CERRADA);
if (esEntrada) {
lugares = lugares - 1;
} else {
lugares = lugares + 1;
}
escribir_numero(lugares);
estado = CERRADA;
}
break;
}
}
}
void setup() {
pinMode(PIN_PULSADOR_ENTRADA, INPUT);
pinMode(PIN_PULSADOR_SALIDA, INPUT);
pinMode(PIN_A, OUTPUT);
pinMode(PIN_B, OUTPUT);
pinMode(PIN_C, OUTPUT);
pinMode(PIN_D, OUTPUT);
pinMode(PIN_E, OUTPUT);
pinMode(PIN_F, OUTPUT);
pinMode(PIN_G, OUTPUT);
servoEntrada.attach(PIN_SERVO_ENTRADA);
servoSalida.attach(PIN_SERVO_SALIDA);
servoEntrada.write(ANGULO_CERRADA);
servoSalida.write(ANGULO_CERRADA);
escribir_numero(lugares);
}
void loop() {
gestionarBarrera(servoEntrada, PIN_PULSADOR_ENTRADA, estadoEntrada,
tLiberadoEntrada, true);
gestionarBarrera(servoSalida, PIN_PULSADOR_SALIDA, estadoSalida,
tLiberadoSalida, false);
} Para el aula
El original de p6 consideraba mantener el código bloqueante en este proyecto, porque usa poco
delay(). En esta reedición lo pasamos a no bloqueante de todos modos, para que el IoT del paso 5
funcione bien y para dejar instalado 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.
Programar IoT: publicar la ocupación
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 el 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 4, 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.
Cada vez que un auto entra o sale, el programa publica en el feed estacionamiento1 la cantidad de
plazas ocupadas (es decir, 8 - lugares). Como solo publica ante ese evento —una entrada o una
salida— y no en cada vuelta del loop(), no gasta el cupo de la cuenta gratuita de Adafruit IO (30
publicaciones por minuto). 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.
Ver el código completo · estacionamiento-iot.ino
Nota: Este código está disponible para ESP32. Cambiá la plataforma con el selector del encabezado para verlo y practicarlo.
// Estacionamiento automatizado — Nivel Avanzado + IoT (ESP32 DevKit v1).
//
// Toma el estacionamiento no bloqueante (estacionamiento-no-bloqueante.ino) y
// le suma monitoreo remoto: cada vez que ENTRA o SALE un auto (es decir, cada
// vez que cambia "lugares"), publica en Adafruit IO la cantidad de plazas
// OCUPADAS ( = 8 - lugares ), tal como hace el original. Asi los primos de
// Juana pueden ver desde el celular cuan lleno esta el estacionamiento.
//
// Feed: "estacionamiento1" (cambiar IO_FEED a "estacionamiento2" para la
// segunda maqueta, como indica la guia: si no, ambas publicarian al mismo feed
// y en el panel se mezclaria la informacion).
//
// La publicacion es EVENT-DRIVEN: solo se publica en el instante del cambio
// (no en cada vuelta de loop()), de modo que el sistema NO supera el cupo
// gratuito de Adafruit IO (30 publicaciones/minuto) — el original publicaba
// dentro del ciclo y podia excederlo.
//
// Plataforma: ESP32 (DevKit v1) — WiFi integrado. Servo con ESP32Servo.
// Proyecto de BAJA TENSION: sin 220 V ni agua. Nota electrica: 3,3 V; servos
// desde 5 V con GND comun.
//
// Basado en: Fragmento de codigo del Nivel Avanzado (Paso 6, publicacion de las
// plazas ocupadas) — Estacionamiento Automatizado, Saberes Digitales
// (INET/EDUCAR 2019).
// Modernizaciones / desvios (ver README):
// (1) OBLOQ + ObloqAdafruit sobre SoftwareSerial(8,9) estan discontinuados y,
// ademas, en el original conviven con los servos en pines 0/1 (RX/TX) que
// rompen el USB. Se reemplazan por el WiFi NATIVO del ESP32 + MQTT contra
// io.adafruit.com (libreria "Adafruit MQTT Library"). El IoT solo se
// implementa en ESP32; en UNO es conceptual.
// (2) Publicacion EVENT-DRIVEN (solo al cambiar "lugares"), no en cada vuelta.
// (3) Reconexion MQTT NO bloqueante (reintento cada 5 s), sin frenar barreras.
#include <WiFi.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"
// Feed al que publicamos. Cambiar a "estacionamiento2" para la segunda maqueta.
#define IO_FEED "estacionamiento1"
// Servidor MQTT de Adafruit IO.
const char SERVIDOR_MQTT[] = "io.adafruit.com";
const int PUERTO_MQTT = 1883;
// --- Capacidad del estacionamiento ---
const int CAPACIDAD = 8;
// --- Pines de las barreras (servos) — GPIO del contrato ---
const int PIN_SERVO_ENTRADA = 25;
const int PIN_SERVO_SALIDA = 26;
// --- Pines de los pulsadores (con resistencia de 10K como pulldown) ---
const int PIN_PULSADOR_ENTRADA = 32;
const int PIN_PULSADOR_SALIDA = 33;
// --- Pines del display de 7 segmentos (segmentos a..g) — GPIO del contrato ---
const int PIN_A = 16;
const int PIN_B = 17;
const int PIN_C = 18;
const int PIN_D = 19;
const int PIN_E = 21;
const int PIN_F = 22;
const int PIN_G = 23;
// --- Angulos de la barrera ---
const int ANGULO_ABIERTA = 90;
const int ANGULO_CERRADA = 0;
// Espera (ms) a que el auto cruce antes de bajar la barrera.
const unsigned long TIEMPO_PASO_MS = 3000;
// Estados de la maquina de cada barrera.
const int CERRADA = 0;
const int ABIERTA = 1;
const int CERRANDO = 2;
// Lugares disponibles. Arranca vacio: 8 lugares libres.
int lugares = CAPACIDAD;
// Estado y cronometro de cada barrera.
int estadoEntrada = CERRADA;
int estadoSalida = CERRADA;
unsigned long tLiberadoEntrada = 0;
unsigned long tLiberadoSalida = 0;
int segmentos[10][7] = { { 1, 1, 1, 1, 1, 1, 0 }, // 0
{ 0, 1, 1, 0, 0, 0, 0 }, // 1
{ 1, 1, 0, 1, 1, 0, 1 }, // 2
{ 1, 1, 1, 1, 0, 0, 1 }, // 3
{ 0, 1, 1, 0, 0, 1, 1 }, // 4
{ 1, 0, 1, 1, 0, 1, 1 }, // 5
{ 1, 0, 1, 1, 1, 1, 1 }, // 6
{ 1, 1, 1, 0, 0, 0, 0 }, // 7
{ 1, 1, 1, 1, 1, 1, 1 }, // 8
{ 1, 1, 1, 1, 0, 1, 1 } }; // 9
// --- WiFi + MQTT (Adafruit IO) ---
WiFiClient clienteWifi;
Adafruit_MQTT_Client mqtt(&clienteWifi, SERVIDOR_MQTT, PUERTO_MQTT,
IO_USERNAME, IO_KEY);
Adafruit_MQTT_Publish feedEstacionamiento(&mqtt,
IO_USERNAME "/feeds/" IO_FEED);
// Reintento de conexion MQTT sin bloquear.
const unsigned long INTERVALO_REINTENTO_MQTT_MS = 5000;
unsigned long millisUltimoIntentoMqtt = 0;
// Ultima cantidad de OCUPADAS publicada con exito (-1 fuerza la 1ra publicacion).
int ocupadasPublicada = -1;
Servo servoEntrada;
Servo servoSalida;
void escribir_numero(int num) {
digitalWrite(PIN_A, segmentos[num][0]);
digitalWrite(PIN_B, segmentos[num][1]);
digitalWrite(PIN_C, segmentos[num][2]);
digitalWrite(PIN_D, segmentos[num][3]);
digitalWrite(PIN_E, segmentos[num][4]);
digitalWrite(PIN_F, segmentos[num][5]);
digitalWrite(PIN_G, segmentos[num][6]);
}
// Maquina de estados de UNA barrera (no bloqueante). esEntrada decide si el
// paso de un auto RESTA (entrada) o SUMA (salida) un lugar, y si la barrera
// puede abrirse.
void gestionarBarrera(Servo &servo, int pinPulsador, int &estado,
unsigned long &tLiberado, bool esEntrada) {
switch (estado) {
case CERRADA: {
bool puedeAbrir = esEntrada ? (lugares >= 1) : (lugares < CAPACIDAD);
if (digitalRead(pinPulsador) == HIGH && puedeAbrir) {
servo.write(ANGULO_ABIERTA);
estado = ABIERTA;
}
break;
}
case ABIERTA: {
if (digitalRead(pinPulsador) == LOW) {
tLiberado = millis();
estado = CERRANDO;
}
break;
}
case CERRANDO: {
if (millis() - tLiberado >= TIEMPO_PASO_MS) {
servo.write(ANGULO_CERRADA);
if (esEntrada) {
lugares = lugares - 1;
} else {
lugares = lugares + 1;
}
escribir_numero(lugares);
estado = CERRADA;
}
break;
}
}
}
// Mantiene viva la conexion MQTT sin bloquear: si esta caida, reintenta como
// mucho una vez cada 5 s.
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 la cantidad de plazas OCUPADAS (8 - lugares) SOLO cuando cambia
// (event-driven). Si no hay conexion o el publish falla, no se actualiza el
// registro: se reintenta cuando vuelva la conexion o en el proximo cambio.
void publicarOcupadasSiCambio() {
int ocupadas = CAPACIDAD - lugares;
if (ocupadas == ocupadasPublicada) {
return; // sin cambios: nada que publicar
}
if (!mqtt.connected()) {
return; // sin internet: se publicara cuando reconecte
}
if (feedEstacionamiento.publish((int32_t) ocupadas)) {
ocupadasPublicada = ocupadas;
}
}
void setup() {
Serial.begin(115200);
pinMode(PIN_PULSADOR_ENTRADA, INPUT);
pinMode(PIN_PULSADOR_SALIDA, INPUT);
pinMode(PIN_A, OUTPUT);
pinMode(PIN_B, OUTPUT);
pinMode(PIN_C, OUTPUT);
pinMode(PIN_D, OUTPUT);
pinMode(PIN_E, OUTPUT);
pinMode(PIN_F, OUTPUT);
pinMode(PIN_G, OUTPUT);
servoEntrada.attach(PIN_SERVO_ENTRADA);
servoSalida.attach(PIN_SERVO_SALIDA);
servoEntrada.write(ANGULO_CERRADA);
servoSalida.write(ANGULO_CERRADA);
escribir_numero(lugares);
// Conexion WiFi. Bloquear aca con delay() es aceptable: el ciclo de barreras
// 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) Barreras no bloqueantes: al cerrar, cambian "lugares".
gestionarBarrera(servoEntrada, PIN_PULSADOR_ENTRADA, estadoEntrada,
tLiberadoEntrada, true);
gestionarBarrera(servoSalida, PIN_PULSADOR_SALIDA, estadoSalida,
tLiberadoSalida, false);
// 2) Mantenimiento IoT + publicacion event-driven. publicarOcupadasSiCambio()
// solo envia datos cuando "lugares" cambio, asi respeta el cupo de Adafruit.
mantenerConexionMqtt();
publicarOcupadasSiCambio();
} Podés ver el sistema completo —los autos entrando y saliendo, el display y el panel reflejando cada publicación— en el simulador:
Simulación del comportamiento
La misma máquina de estados del firmware, portada a TypeScript, maneja el
comportamiento del dispositivo. No se ejecuta el binario real: la ejecución
del .hex con avr8js está en el roadmap.
Simulación lista. Presioná «Iniciar» para arrancar la secuencia.
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: el medidor de estacionamiento1 tiene que actualizarse cada vez que un
auto entra o sale (con una pequeña demora de red). Si no llega nada, revisá las credenciales, el
nombre de la red y su contraseña.
Monitorear varios estacionamientos en un mismo panel
El panel de Adafruit IO permite registrar el estado de tantas maquetas como se quiera, lo cual es
ideal para trabajar en equipo: cada grupo publica su estacionamiento y todos ven el estado de todos.
Para sumar el estacionamiento de los primos de Juana repetimos el procedimiento del paso 2, pero
ahora creamos un segundo feed llamado estacionamiento2 y su propio medidor en el panel.
En el código, la única diferencia entre una maqueta y otra es el nombre del feed al que publica:
donde una dice estacionamiento1, la otra dice estacionamiento2. Si las dos publicaran al mismo
feed, en el panel se mezclaría la información de ambos estacionamientos.
Una decisión al entrar
Hoy el panel muestra la ocupación, pero la decisión de mandar autos de un estacionamiento al otro
sigue siendo de las personas. Piensen en grupo: ¿cómo podría el sistema de Juana, al llenarse,
avisar automáticamente (encender un cartel, prender una luz) que conviene ir al de sus primos,
leyendo el feed estacionamiento2 para saber si allá hay lugar? ¿Qué tendría que hacer el programa:
publicar, suscribirse, o las dos cosas?
Verificá
Cargá el código con estacionamiento1 en una maqueta y con estacionamiento2 en otra (o cargá la
misma placa dos veces cambiando solo esa línea): en el panel, cada medidor tiene que moverse de
manera independiente según los autos que entran y salen en cada estacionamiento.
Cierre del nivel
Al terminar este nivel, el estacionamiento de Juana dejó de ser 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 cuántas plazas hay ocupadas, incluso comparando
varios estacionamientos a la vez. Con esto queda completo el recorrido del proyecto. Si el grupo
quiere seguir, en la ficha del proyecto están las extensiones sugeridas: detectar los autos con
sensores ultrasónicos en lugar de pulsadores, señalizar cada plaza o sumar una alarma sonora.
Evidencia de cierre
Al terminar este nivel, esto tiene que estar funcionando:
- Podés explicar qué es IoT y qué dos cosas necesita el proyecto para incorporarlo (un dispositivo con internet y un servidor que aloje los datos).
- El panel de Adafruit IO tiene los feeds estacionamiento1 (y estacionamiento2) con un control que muestra la ocupación.
- El sistema funciona con código no bloqueante basado en millis(): no queda ningún delay() que detenga el programa, condición necesaria para atender internet y las barreras en simultáneo.
- El dispositivo (variante ESP32) publica la cantidad de plazas ocupadas en su feed cada vez que un auto entra o sale, y el panel lo refleja a distancia.