Saltar al contenido
Volver a la ficha del proyecto

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

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 Barreras automáticas y conteo de plazas Nivel inicial
  3. 03 Mostrar las plazas libres en un display de 7 segmentos Nivel intermedio
  4. 04 Monitorear el estacionamiento con Internet de las Cosas Nivel avanzado

Materiales y equipamiento

Insumos

Imagen Insumo Cantidad Plataforma
Foto de Servomotor 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
Foto de Resistencia 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
Foto de Resistencia 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
Foto de LED 5 mm 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
Foto de LED 5 mm LED verde de 5 mm Indicador de «hay lugar disponible». En el nivel intermedio lo reemplaza el display de 7 segmentos. 1 Todas
Foto de Display de 7 segmentos 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
Foto de Cables dupont macho-macho Cable dupont macho-hembra 20 Todas
Foto de Cables dupont hembra-hembra Cable dupont hembra-hembra 20 Todas
Foto de Protoboard Protoboard 1 Todas
Foto de Arduino UNO R3 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
Foto de Cable USB tipo B Cable USB tipo B 1 Arduino UNO R3
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. 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
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). 1 ESP32 DevKit
Foto de Cable micro USB 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

Arduino UNO Conexionado en protoboard · Estacionamiento: barreras de entrada/salida por servo, pulsadores y display de 7 segmentos.

Deslizá horizontalmente para ver el plano completo

Arduino UNO
Servo barrera entrada (SG90)
SG90
VCCseñalGND
Servo barrera salida (SG90)
SG90
VCCseñalGND
Pulsador entrada
señalGND
Pulsador salida
señalGND
Display 7 segmentos
abcdefgGND
ESP32 DevKit v1 Conexionado en protoboard · Estacionamiento: barreras de entrada/salida por servo, pulsadores y display de 7 segmentos.

Deslizá horizontalmente para ver el plano completo

ESP32 DevKit v1
Servo barrera entrada (SG90)
SG90
VCCseñalGND
Servo barrera salida (SG90)
SG90
VCCseñalGND
Pulsador entrada
señalGND
Pulsador salida
señalGND
Display 7 segmentos
abcdefgGND
Arduino UNOConexionado en protoboard
Conexiones · Arduino UNO. Convención de colores: alimentación VCC = rojo, GND = negro; señal del servo de entrada = azul, del servo de salida = naranja; pulsador de entrada = amarillo, pulsador de salida = violeta; los siete segmentos (a–g) del display = verde, cada uno con su resistencia de 220 Ω. En el ESP32 los servos se alimentan desde una fuente externa de 5 V (no desde la placa), con GND compartido.
Desde Hacia Color de cable
Arduino UNO · 5VBus + de la protoboardrojo
Arduino UNO · GNDBus − de la protoboardnegro
Servo barrera entrada · VCCBus + de la protoboardrojo
Servo barrera entrada · GNDBus − de la protoboardnegro
Servo barrera entrada · señalArduino UNO · 4azul
Servo barrera salida · VCCBus + de la protoboardrojo
Servo barrera salida · GNDBus − de la protoboardnegro
Servo barrera salida · señalArduino UNO · 8naranja
Pulsador entradaArduino UNO · 2amarillo
Pulsador entrada (otro terminal)Bus − de la protoboardnegro
Pulsador salidaArduino UNO · 3violeta
Pulsador salida (otro terminal)Bus − de la protoboardnegro
Display 7 segmentos · segmento a (R 220 Ω)Arduino UNO · 12verde
Display 7 segmentos · segmento b (R 220 Ω)Arduino UNO · 13verde
Display 7 segmentos · segmento c (R 220 Ω)Arduino UNO · 5verde
Display 7 segmentos · segmento d (R 220 Ω)Arduino UNO · 6verde
Display 7 segmentos · segmento e (R 220 Ω)Arduino UNO · 7verde
Display 7 segmentos · segmento f (R 220 Ω)Arduino UNO · 11verde
Display 7 segmentos · segmento g (R 220 Ω)Arduino UNO · 10verde
Display 7 segmentos · comúnBus − 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 de entrada = azul, del servo de salida = naranja; pulsador de entrada = amarillo, pulsador de salida = violeta; los siete segmentos (a–g) del display = verde, cada uno con su resistencia de 220 Ω. En el ESP32 los servos se alimentan desde una fuente externa de 5 V (no desde la placa), con GND compartido.
Desde Hacia Color de cable
Fuente externa 5 VBus + de la protoboardrojo
Fuente externa 5 V · GNDBus − de la protoboardnegro
ESP32 DevKit v1 · GNDBus − de la protoboardnegro
Servo barrera entrada · VCCBus + de la protoboardrojo
Servo barrera entrada · GNDBus − de la protoboardnegro
Servo barrera entrada · señalESP32 DevKit v1 · GPIO25azul
Servo barrera salida · VCCBus + de la protoboardrojo
Servo barrera salida · GNDBus − de la protoboardnegro
Servo barrera salida · señalESP32 DevKit v1 · GPIO26naranja
Pulsador entradaESP32 DevKit v1 · GPIO32amarillo
Pulsador entrada (otro terminal)Bus − de la protoboardnegro
Pulsador salidaESP32 DevKit v1 · GPIO33violeta
Pulsador salida (otro terminal)Bus − de la protoboardnegro
Display 7 segmentos · segmento a (R 220 Ω)ESP32 DevKit v1 · GPIO16verde
Display 7 segmentos · segmento b (R 220 Ω)ESP32 DevKit v1 · GPIO17verde
Display 7 segmentos · segmento c (R 220 Ω)ESP32 DevKit v1 · GPIO18verde
Display 7 segmentos · segmento d (R 220 Ω)ESP32 DevKit v1 · GPIO19verde
Display 7 segmentos · segmento e (R 220 Ω)ESP32 DevKit v1 · GPIO21verde
Display 7 segmentos · segmento f (R 220 Ω)ESP32 DevKit v1 · GPIO22verde
Display 7 segmentos · segmento g (R 220 Ω)ESP32 DevKit v1 · GPIO23verde
Display 7 segmentos · comúnBus − de la protoboardnegro
Nivel inicial

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 servomotor: 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 .

Servomotor SG-5010 de color negro con su brazo blanco encastrado en el eje y los tres cables de conexión (marrón, rojo y naranja) saliendo del cuerpo.
El servomotor SG-5010 que mueve la barrera (figura original del proyecto): el brazo encastrado en el eje hace de barrera; los tres cables son alimentación (VCC, GND) y señal.

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 pulsador 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 LED: 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 resistencia de 220 Ω en serie que limita la corriente.

Esquema de conexión del nivel inicial: placa Arduino UNO con un LED verde (HAY LUGAR) al pin 9 y un LED rojo (NO HAY LUGAR) al pin 8, dos pulsadores (ENTRADA y SALIDA) y dos servomotores (ENTRADA y SALIDA) cuyos cables de señal llegan a la placa; la alimentación se reparte por las líneas de la protoboard.
Circuito completo del nivel inicial (figura original del proyecto): dos servos para las barreras, dos pulsadores y los LED verde (hay lugar) y rojo (completo). En esta reedición los servos NO van a los pines 0 y 1 como muestra el original, sino a pines libres (4 y 8).

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
barrera-pulsadores.ino Arduino UNO
// 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);
  }
}
barrera-pulsadores.ino ESP32
// 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);
  }
}
Estacionamiento del nivel inicial: los pulsadores de entrada y salida mueven las barreras y actualizan la variable lugares (arranca en 8); el LED verde indica que hay lugar y el rojo que está completo. La barrera de entrada solo se abre si quedan plazas.

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

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.

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.
Nivel intermedio

Mostrar las plazas libres en un display de 7 segmentos

Conectar el display de 7 segmentos

El display de 7 segmentos 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.

Esquema de conexión del display de 7 segmentos: cada uno de los siete segmentos va, a través de una resistencia de 220 ohm en la protoboard, a un pin digital del Arduino UNO (13, 12, 11, 10, 7, 6, 5); el pin común del display va a GND.
Conexión del display de 7 segmentos (figura original del proyecto): cada segmento llega a una salida digital con su resistencia de 220 Ω y el común va a GND. El diagrama indica solo las conexiones que se agregan para manejar el display.

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.

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
contador-display.ino Arduino UNO
// 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);
  }
}
contador-display.ino ESP32
// 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);
  }
}
Estacionamiento con display: las dos barreras y el conteo funcionan igual que en el nivel inicial, pero ahora el display de 7 segmentos muestra el número exacto de plazas libres cada vez que un auto entra o sale, en lugar de los LED.

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.

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.
Nivel avanzado

Monitorear el estacionamiento 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 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:

  1. Un dispositivo capaz de conectarse a internet.
  2. 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 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 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 IO, 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 feed 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.

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 bloqueante: 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-no-bloqueante.ino Arduino UNO
// 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-no-bloqueante.ino ESP32
// 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);
}
El mismo estacionamiento del nivel intermedio, ahora sin delay(): el programa consulta millis() para los tiempos de las barreras, dejando el procesador libre para, en el próximo paso, atender internet.

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-iot.ino ESP32
// 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();
}
El estacionamiento IoT completo: control no bloqueante + WiFi del ESP32 + publicación de las plazas ocupadas en el feed estacionamiento1 de Adafruit IO cada vez que un auto entra o sale.

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.

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.

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.