Monitorear el estacionamiento con Internet de las Cosas
Recorrido del nivel
Esa barra de arriba te dice en qué paso estás. Tocala para ver el índice completo y saltar a cualquier paso.
Introducción a Internet de las Cosas (IoT)
Internet de las Cosas (en inglés Internet of Things, abreviado IoTIoT (Internet de las Cosas)Interconexión digital de objetos cotidianos con internet. Permite, por ejemplo, enviar los datos de un sensor a una central y visualizarlos desde un dispositivo móvil. Requiere un dispositivo capaz de conectarse a internet y un servidor que reciba y aloje los datos.) es un concepto que refiere a la interconexión digital de objetos cotidianos con internet. Por ejemplo, enviar 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.
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.