You dont have javascript enabled! Please enable it!

Caso práctico: Baliza de presencia BLE con ESP32

Caso práctico: Baliza de presencia BLE con ESP32 — hero

Objetivo y caso de uso

Qué construirás: Una baliza de presencia de sala Bluetooth Low Energy (BLE) independiente que transmite el estado de ocupación, alternado mediante un pulsador físico y mostrado localmente a través de un LED de estado.

Por qué es importante / Casos de uso

  • Gestión de salas de reuniones e instalaciones: Detecta si las salas de conferencias, cabinas telefónicas o baños están ocupados sin complejas redes de sensores cableados.
  • Control de privacidad: Actúa como un letrero digital de «No molestar» para oficinas personales o estudios de grabación, transmitiendo el estado a los teléfonos inteligentes cercanos o concentradores de puerta de enlace BLE.
  • Arquitectura sin conexión de bajo consumo: Utiliza las cargas útiles de los anuncios BLE para la transmisión del estado, permitiendo que infinitos escáneres pasivos lean datos simultáneamente sin el consumo de energía adicional que supone establecer conexiones BLE GATT formales.

Resultado esperado

  • El ESP32 inicializa con éxito un servidor BLE y transmite continuamente cargas útiles de estado sin conexión.
  • Al presionar el botón de hardware se alterna instantáneamente el LED local y se actualiza el paquete de anuncios BLE con una latencia inferior a 100 ms.
  • Los paneles de control remotos o concentradores BLE rastrean con precisión la disponibilidad de la sala simplemente escuchando los anuncios BLE pasivos.

Audiencia: Desarrolladores de IoT, Ingenieros de Edificios Inteligentes; Nivel: Intermedio

Arquitectura/flujo: Pulsador físico → Interrupción GPIO del ESP32 → Actualizar estado → Alternar LED local & Modificar carga útil de anuncio BLE → Transmisión pasiva a escáneres BLE.

Nota educativa de validación

Antes de publicar este caso, el contenido pasó la puerta automática de validación de Prometeo con estado PASS. Para este perfil ESP32 DevKitC, el proyecto se comprobó como proyecto PlatformIO: el validador extrajo platformio.ini y src/main.cpp, creó un proyecto temporal y ejecutó pio run contra platform = espressif32, board = esp32dev y framework = arduino. También revisó la estructura del artículo, que los comandos sean copiables con guiones ASCII, y que no aparezcan stacks no soportados como ESP-IDF directo o placas ESP32 no acotadas.

Evidencia de validación publicada

  • Resultado automático: PASS.
  • Estructura parseada: 3 apartados, 4 tablas y 2 bloques de código detectados antes de publicar.
  • Código comprobado: 1 PlatformIO config + 1 ESP32 source/pio run.
  • Catálogo soportado: el texto se contrastó contra los perfiles de dispositivo validables de Prometeo y los stacks no soportados bloquean la publicación.
  • Hallazgos del informe: sin hallazgos bloqueantes.

Esta validación confirma compatibilidad sintáctica y de herramientas para el código publicado, pero no sustituye la prueba física sobre tu placa ESP32 DevKitC exacta, tu cableado, alimentación y entorno WiFi local.

Nota educativa de seguridad

Este proyecto es un prototipo educativo, no un producto certificado. Antes de encender la configuración, verifique la distribución de pines de la revisión exacta de su placa ULX3S, mantenga las señales de E/S de la FPGA a 3.3 V, nunca conecte 5 V directamente a los pines de E/S, desconecte la alimentación antes de cambiar el cableado y use fuentes externas adecuadas para cargas, motores o servos mientras comparte la tierra solo cuando el cableado lo requiera.

Diagrama de bloques conceptual

Vista de alto nivel: qué entra, qué procesa cada bloque y qué sale del sistema.

Arquitectura funcional

Pulsador físico

Interrupción GPIO del ESP32

Actualizar estado

Alternar LED local & Modificar carga útil…

Transmisión pasiva a escáneres BLE

Flujo conceptual de señales y responsabilidades entre bloques del dispositivo.

Ruta de validación

Código fuente

PlatformIO build

Flash

Monitor serie

Resumen conceptual de las herramientas usadas para comprobar el material publicado.

Requisitos previos

Antes de comenzar este caso práctico, asegúrese de tener lo siguiente listo:
* Una comprensión básica de la programación en C++ y la lógica GPIO (entrada/salida) de microcontroladores.
* Visual Studio Code instalado en su computadora con la extensión PlatformIO IDE habilitada.
* Un teléfono inteligente (Android o iOS) con una aplicación de escaneo BLE instalada. Recomendamos LightBlue o BLE Scanner.
* Los controladores USB adecuados para su placa ESP32 instalados en su sistema operativo host (generalmente controladores CP210x o CH34x, dependiendo del fabricante específico del DevKitC).

Materiales

ComponenteDescripción / Modelo exactoCantidad
Núcleo del microcontroladorESP32 DevKitC + pulsador/entrada de contacto + LED de estado1
ResistenciaResistencia de 330 Ω (ohmios) (para limitar la corriente del LED de estado)1
Placa de pruebas (Breadboard)Placa de pruebas estándar sin soldadura de 400 u 830 puntos1
Cables puenteSurtido de cables puente Dupont macho a macho4-6
Cable USBCable Micro-USB o USB-C (con capacidad de datos, que coincida con su DevKitC)1

(Nota: El «ESP32 DevKitC + pulsador/entrada de contacto + LED de estado» constituye el modelo de dispositivo lógico completo para este prototipo. El pulsador y el LED pueden ser componentes discretos colocados en la placa de pruebas o integrados en una placa portadora personalizada).

Configuración/Conexión

Este proyecto requiere el cableado de un pulsador físico para actuar como nuestra entrada de contacto y un LED externo para actuar como nuestro indicador de estado. Usaremos la resistencia pull-up interna del ESP32 para el pulsador para simplificar el cableado y reducir la cantidad de componentes.

Lógica de cableado

  1. Pulsador: Conecte un terminal del pulsador normalmente abierto al GPIO 4. Conecte el terminal opuesto directamente a uno de los pines GND del ESP32. Cuando se presiona el botón, conecta el GPIO 4 a tierra (Ground), creando una señal LOW. La resistencia pull-up interna del ESP32 mantiene el pin en HIGH cuando no se presiona.
  2. LED de estado: Conecte el ánodo (pata más larga) del LED al GPIO 5. Conecte el cátodo (pata más corta) a un extremo de la resistencia de 330 Ω. Conecte el otro extremo de la resistencia al GND del ESP32.

Tabla de referencia de pines

Terminal del componentePin del ESP32 DevKitCTipo de señalDescripción
Terminal 1 del pulsadorGPIO 4Entrada digitalAlterna el estado de la sala (usa pull-up interno)
Terminal 2 del pulsadorGNDAlimentación (Tierra)Lleva el GPIO 4 a LOW cuando se presiona
Ánodo del LED de estado (+)GPIO 5Salida digitalSe ilumina cuando la sala está «Ocupada»
Cátodo del LED de estado (-)GND (vía 330 Ω)Alimentación (Tierra)Ruta de retorno de corriente

Código validado

Los siguientes archivos de código están estructurados para el entorno PlatformIO. El proyecto requiere dos archivos principales: platformio.ini para la configuración de compilación y src/main.cpp para la lógica de la aplicación.

platformio.ini

Cree o sobrescriba el archivo platformio.ini en la raíz de su proyecto PlatformIO con la siguiente configuración. Esto configura el entorno del ESP32 DevKitC y especifica la velocidad del monitor serie.

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
; Force the use of standard C++11 and optimize for size
build_flags = 
    -std=gnu++11
    -Os

src/main.cpp

Cree o sobrescriba el archivo main.cpp dentro del directorio src. Este código implementa un algoritmo de antirrebote (debounce) no bloqueante para el pulsador y actualiza dinámicamente la carga útil de anuncios BLE sin requerir un reinicio completo del dispositivo.

Vista pública parcial del archivo validado. El código completo se muestra a miembros y en PDF/Print.

/**
 * BLE Room Presence Beacon
 * Device: ESP32 DevKitC + pushbutton/contact input + status LED
 * Framework: Arduino via PlatformIO
 */

#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

// Hardware Pin Definitions
#define BUTTON_PIN 4
#define LED_PIN 5

// State Machine Variables
bool isOccupied = false;
int buttonState = HIGH;
int lastReading = HIGH;

// Non-blocking Debounce Variables
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50; // 50 milliseconds

// BLE Global Pointer
BLEAdvertising *pAdvertising;

/**
 * Updates the BLE Advertisement payload based on the current room state.
 * Connectionless BLE requires us to stop advertising, update the payload,
 * and then restart advertising so scanners see the new data immediately.
 */
void updateBLEAdvertisement() {
    if (pAdvertising != nullptr) {
        pAdvertising->stop();
    }

    BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();

    // Set standard BLE flags. 
    // 0x04 = BR_EDR_NOT_SUPPORTED (Indicates this is a BLE-only device)
    oAdvertisementData.setFlags(0x04); 

    // Dynamically change the advertised device name based on state.
    // This allows scanners to know the room status without connecting.
    if (isOccupied) {
        oAdvertisementData.setName("ROOM_INUSE");
    } else {
        oAdvertisementData.setName("ROOM_AVAIL");
    }

    pAdvertising->setAdvertisementData(oAdvertisementData);
    pAdvertising->start();
}

void setup() {
    // Initialize Serial Monitor for debugging
    Serial.begin(115200);
    while (!Serial) {
        ; // Wait for serial port to connect
    }
// ... continúa para miembros en el código completo validado ...

🔒 Parte del código validado es premium. Con el pase de 7 días o la suscripción mensual podrás consultar el archivo completo validado.

/**
 * BLE Room Presence Beacon
 * Device: ESP32 DevKitC + pushbutton/contact input + status LED
 * Framework: Arduino via PlatformIO
 */

#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

// Hardware Pin Definitions
#define BUTTON_PIN 4
#define LED_PIN 5

// State Machine Variables
bool isOccupied = false;
int buttonState = HIGH;
int lastReading = HIGH;

// Non-blocking Debounce Variables
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50; // 50 milliseconds

// BLE Global Pointer
BLEAdvertising *pAdvertising;

/**
 * Updates the BLE Advertisement payload based on the current room state.
 * Connectionless BLE requires us to stop advertising, update the payload,
 * and then restart advertising so scanners see the new data immediately.
 */
void updateBLEAdvertisement() {
    if (pAdvertising != nullptr) {
        pAdvertising->stop();
    }

    BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();

    // Set standard BLE flags. 
    // 0x04 = BR_EDR_NOT_SUPPORTED (Indicates this is a BLE-only device)
    oAdvertisementData.setFlags(0x04); 

    // Dynamically change the advertised device name based on state.
    // This allows scanners to know the room status without connecting.
    if (isOccupied) {
        oAdvertisementData.setName("ROOM_INUSE");
    } else {
        oAdvertisementData.setName("ROOM_AVAIL");
    }

    pAdvertising->setAdvertisementData(oAdvertisementData);
    pAdvertising->start();
}

void setup() {
    // Initialize Serial Monitor for debugging
    Serial.begin(115200);
    while (!Serial) {
        ; // Wait for serial port to connect
    }
    Serial.println("Initializing BLE Room Presence Beacon...");

    // Configure GPIO Pins
    pinMode(BUTTON_PIN, INPUT_PULLUP);
    pinMode(LED_PIN, OUTPUT);

    // Set initial hardware state
    digitalWrite(LED_PIN, LOW); // LED OFF = Available

    // Initialize the BLE environment with a default name
    BLEDevice::init("ROOM_AVAIL");
    pAdvertising = BLEDevice::getAdvertising();

    // Apply our custom advertisement data and start broadcasting
    updateBLEAdvertisement();

    Serial.println("Initialization Complete. Broadcasting as ROOM_AVAIL.");
}

void loop() {
    // Read the current physical state of the pushbutton
    int reading = digitalRead(BUTTON_PIN);

    // If the switch changed (due to noise or pressing)
    if (reading != lastReading) {
        lastDebounceTime = millis(); // Reset the debouncing timer
    }

    // Whatever the reading is at, it's been there for longer than the debounce delay,
    // so take it as the actual current state.
    if ((millis() - lastDebounceTime) > debounceDelay) {

        // If the button state has truly changed
        if (reading != buttonState) {
            buttonState = reading;

            // Only toggle the room state when the button is actively PRESSED (transition to LOW)
            if (buttonState == LOW) {
                isOccupied = !isOccupied;

                // Update the physical Status LED
                digitalWrite(LED_PIN, isOccupied ? HIGH : LOW);

                // Update the BLE Advertisement Payload
                updateBLEAdvertisement();

                // Print to Serial Monitor for validation
                Serial.print("State toggled! Room is now: ");
                Serial.println(isOccupied ? "OCCUPIED" : "AVAILABLE");
            }
        }
    }

    // Save the reading. Next time through the loop, it'll be the lastReading.
    lastReading = reading;
}

Comandos de compilación/flasheo/ejecución

Utilice la Interfaz de Línea de Comandos (CLI) de PlatformIO para compilar, cargar y monitorear el ESP32. Asegúrese de que su terminal esté abierta en el directorio raíz de su proyecto (donde se encuentra platformio.ini).

AcciónComando
Compilar proyectopio run
Cargar al ESP32pio run --target upload
Abrir monitor seriepio device monitor

Flujo de trabajo de ejecución:
1. Conecte el ESP32 DevKitC a su computadora a través de USB.
2. Ejecute pio run para descargar las dependencias del framework de Espressif y compilar el código fuente en C++. Asegúrese de que la compilación sea exitosa sin errores.
3. Ejecute pio run --target upload para flashear el firmware compilado al microcontrolador.
4. Ejecute pio device monitor para ver la salida serie. Debería ver inmediatamente «Initializing BLE Room Presence Beacon…» seguido de «Initialization Complete.»

Validación paso a paso

Para demostrar que el sistema funciona correctamente, siga estos puntos de control estructurados.

  1. Encendido inicial y verificación del registro serie
    • Acción: Observe la salida de la terminal después de ejecutar pio device monitor.
    • Observación esperada: La terminal imprime «Initialization Complete. Broadcasting as ROOM_AVAIL.»
    • Condición de aprobación: El ESP32 arranca sin pánicos del kernel ni bucles de reinicio, confirmando que la pila BLE se inicializó con éxito.
  2. Alternancia de estado del hardware
    • Acción: Presione el pulsador físico una vez.
    • Observación esperada: El LED de estado se ilumina. El monitor serie imprime «State toggled! Room is now: OCCUPIED».
    • Condición de aprobación: La lógica de antirrebote no bloqueante registra correctamente exactamente un cambio de estado por cada pulsación física, y el LED refleja el booleano isOccupied.
  3. Verificación de anuncio BLE sin conexión (Disponible)
    • Acción: Abra la aplicación de escáner BLE de su teléfono inteligente (por ejemplo, LightBlue). Borre la caché/actualice la lista de escaneo. Asegúrese de que el LED del ESP32 esté APAGADO.
    • Observación esperada: Aparece un dispositivo llamado «ROOM_AVAIL» en la lista del escáner.
    • Condición de aprobación: El teléfono inteligente recibe con éxito los paquetes de anuncios que contienen el nombre predeterminado.
  4. Verificación de actualización dinámica de carga útil (Ocupado)
    • Acción: Presione el pulsador en el ESP32 para que el LED de estado se ENCIENDA. En la aplicación del teléfono inteligente, actualice la lista de escaneo.
    • Observación esperada: El dispositivo llamado «ROOM_AVAIL» desaparece, y aparece un nuevo dispositivo llamado «ROOM_INUSE» (a menudo con la misma dirección MAC).
    • Condición de aprobación: El ESP32 detuvo con éxito el servidor BLE, actualizó la carga útil de anuncios y reinició la transmisión, demostrando la transmisión dinámica de estado sin conexión.

Solución de problemas

SíntomaCausa probableSolución
La carga del firmware falla con «Permission denied» o «COM port not found»Falta el controlador USB o permisos insuficientes del sistema operativo para acceder al puerto serie.Instale los controladores CP210x/CH34x. En Linux, agregue su usuario al grupo dialout usando sudo usermod -a -G dialout $USER.
La pulsación del botón se registra varias veces (doble alternancia)Rebote del interruptor de hardware que excede la ventana de retraso de antirrebote del software.Aumente debounceDelay en main.cpp de 50 a 100 o 150 milisegundos.
El LED de estado nunca se enciendeLa polaridad del LED está invertida o está cableado al pin GPIO incorrecto.Asegúrese de que la pata más larga (ánodo) vaya al GPIO 5 y la pata más corta (cátodo) vaya a GND a través de la resistencia.
La aplicación del teléfono inteligente no ve el cambio de nombreLa aplicación del escáner está almacenando en caché el nombre del dispositivo BLE antiguo basado en la dirección MAC.Fuerce una actualización completa en la aplicación o reinicie la radio Bluetooth del teléfono inteligente para borrar la caché BLE local.

Mejoras

Una vez que el prototipo básico esté funcionando, considere implementar las siguientes mejoras arquitectónicas y de hardware para crear un dispositivo más robusto:

  • Gestión de energía y funcionamiento con batería:
    • Integración de suspensión profunda (Deep Sleep): En lugar de ejecutar el loop() continuamente, configure el ESP32 para que entre en suspensión profunda. Use la fuente de activación ext0 vinculada al pulsador. Al despertar, transmita el nuevo estado durante 5 segundos y luego vuelva a la suspensión. Esto reduce el consumo de energía de ~100 mA a ~10 µA, permitiendo meses de funcionamiento con una batería LiPo. Método de validación: Para verificar esta afirmación de rendimiento, coloque un multímetro digital en serie con la fuente de alimentación del ESP32 para medir el consumo de corriente durante la fase de suspensión profunda; debería observar una caída a aproximadamente 10 µA a 15 µA dependiendo del regulador de voltaje incorporado del DevKitC específico y del puente USB a UART.
    • Tiempo de espera del LED de estado: En lugar de mantener el LED permanentemente iluminado cuando está ocupado, hágalo parpadear brevemente cada 10 segundos o apáguelo por completo después de un minuto para ahorrar energía.
  • Estructura de datos y eficiencia de la carga útil:
    • Datos específicos del fabricante: En lugar de cambiar el nombre del dispositivo (que se almacena fuertemente en caché por iOS y Android), codifique el estado de ocupación como un byte personalizado en el campo de Datos Específicos del Fabricante (Manufacturer Specific Data) del paquete de anuncios. Esto permite a los escáneres analizar el estado exacto sin depender de comparaciones de cadenas y evita por completo los problemas de almacenamiento en caché de nombres a nivel del sistema operativo.

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a Amazon

Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.

Quiz rápido

Pregunta 1: ¿Qué dispositivo principal se utiliza para construir la baliza de presencia de sala?




Pregunta 2: ¿Cómo se alterna el estado de ocupación en la baliza?




Pregunta 3: ¿Qué tecnología inalámbrica utiliza la baliza para transmitir el estado?




Pregunta 4: ¿Cómo se muestra localmente el estado de ocupación en el dispositivo?




Pregunta 5: ¿Cuál es un caso de uso principal para esta baliza?




Pregunta 6: ¿Qué ventaja tiene la arquitectura sin conexión de bajo consumo utilizada?




Pregunta 7: ¿Qué tipo de cargas útiles utiliza la baliza para la transmisión del estado?




Pregunta 8: ¿Por qué esta arquitectura ahorra energía en comparación con otras conexiones BLE?




Pregunta 9: ¿Qué función cumple la baliza en oficinas personales o estudios de grabación?




Pregunta 10: Según el resultado esperado, ¿qué hace el ESP32 al inicializarse?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:
Scroll al inicio