En este tutorial aprenderás a utilizar el Bluetooth BLE de la UNIT Pulsar ESP32-C6 para crear un sistema de comunicación inalámbrica con una interfaz web BLE, desde la cual podrás encender y apagar un LED y también visualizar la distancia medida por un sensor ultrasónico HC-SR04P.
Además, conocerás cómo programar la UNIT Pulsar ESP32-C6 desde Arduino IDE, cómo funciona la comunicación BLE y qué aspectos debes considerar para que posteriormente puedas agregar más sensores o actuadores a tu proyecto.
INTRODUCCIÓN
El Bluetooth Low Energy (BLE) es una tecnología de comunicación inalámbrica diseñada para intercambiar datos consumiendo muy poca energía. A diferencia del Bluetooth clásico, BLE está enfocado en aplicaciones donde se requiere enviar datos de forma eficiente, rápida y con bajo consumo, por lo que es ideal para proyectos de IoT, monitoreo, automatización y control inalámbrico.
En este tutorial utilizaremos el BLE integrado de la UNIT Pulsar ESP32-C6 para crear un dispositivo capaz de comunicarse con una interfaz web BLE desde el navegador de un celular o computadora. A través de esta interfaz será posible conectarse al Bluetooth BLE que genera la UNIT Pulsar, encender o apagar un LED y visualizar la distancia detectada por el sensor ultrasónico HC-SR04P. También se explicará la lógica general del código y la estructura básica para que puedas expandir el proyecto con más entradas y salidas, como relevadores, servomotores, sensores de temperatura, humedad o más variables de monitoreo.
Antes de comenzar, es importante considerar que para utilizar BLE desde una interfaz web se recomienda usar un navegador compatible con Web Bluetooth, como Google Chrome o Microsoft Edge, y realizar las pruebas en un dispositivo que permita este tipo de conexión.
MATERIALES
¡Manos a la obra! Antes de empezar, asegúrate de tener a la mano los siguientes materiales:
- UNIT Pulsar ESP32-C6
- Protoboard Deslizable 840 Puntos UNIT
- HC-SR04P Sensor Ultrasonico 3-5V
- Led Difuso de 5mm
- Resistencia 220 ohms 1/4W
- Cable USB Tipo C 3A
- Alambre para Protoboard 1 Metro Negro
- Alambre para Protoboard 1 Metro Rojo
- Mini Pinza de Corte Diagonal de 4.5″ (125 mm)
- Mini Pinza de Punta y Corte de 4.5” (125 mm)
DESARROLLO
¡Listo! Ahora sí, comencemos con el tutorial. Sigue los pasos que se indican a continuación.
1. Configuración de entorno de programación
Para comenzar, utilizaremos el Arduino IDE como entorno de desarrollo. Asegúrate de tener lo siguiente listo:
- Instala el Arduino IDE
- Descárgalo desde el sitio oficial:
- 👉 https://www.arduino.cc/en/software
- ⚠️(Si ya lo tienes instalado, puedes omitir este paso).
- Agrega las placas de ESP32 de Espressif a URLs adicionales de Arduino IDE

- Instala las placas ESP32 de Espressif desde el Gestor de placas de Arduino IDE
- Después de agregar la URL a preferencias dirígete a la pestaña Herramientas > Placa > Gestor de placas, busca “ESP32 de Espressif” e instala las placas.
- Espera a que se termine de instalar todas las tarjetas el proceso puede tardar.

DIAGRAMA DE CONEXIÓN
Ya instaladas las tarjetas de ESP32 de Espressif en Arduino IDE procedemos a realizar las siguientes conexiones entre la UNIT Pulsar ESP32 C6, Led, Ultrasónico HC-SR04P y protoboard:

| Componente | Pin del componente | Pin de la UNIT Pulsar ESP32-C6 | Tipo |
|---|---|---|---|
| LED | Ánodo (+) | D3: GPIO 9 | Salida |
| LED | Cátodo (-) | GND (con resistencia 220Ω) | – |
| HC-SR04P | VCC | 3.3V | Alimentación |
| HC-SR04P | GND | GND | Tierra |
| HC-SR04P | TRIG | D6: GPIO 20 | Salida |
| HC-SR04P | ECHO | D7: GPIO 21 | Entrada |
La UNIT Pulsar ESP32-C6 será la encargada de controlar el LED y leer la señal del sensor ultrasónico HC-SR04P. El LED se conecta a un pin digital de salida mediante una resistencia de 220 Ω, mientras que el sensor ultrasónico se alimenta con VCC y GND, y sus pines TRIG y ECHO se conectan a dos pines digitales de la tarjeta para generar el pulso de medición y recibir el eco. Una vez realizadas estas conexiones, la tarjeta podrá medir la distancia y enviar la información a la interfaz web por medio de Bluetooth BLE, además de recibir comandos para cambiar el estado del LED.
PROGRAMACIÓN
Una vez realizadas las conexiones, podemos continuar con la programación del proyecto. Es importante entender que este sistema se compone de dos partes principales que trabajan en conjunto.
Por un lado, se encuentra el programa desarrollado en Arduino IDE, el cual se cargará en la UNIT Pulsar ESP32-C6. Este programa permitirá que la tarjeta funcione como un servidor Bluetooth BLE, generando la señal Bluetooth y gestionando la comunicación para enviar y recibir información, lo que hará posible controlar el estado del LED y obtener la medición del sensor ultrasónico.
Por otro lado, se desarrollará una interfaz web BLE utilizando HTML, CSS y JavaScript, la cual se ejecutará desde el navegador. Esta interfaz será la encargada de buscar, conectarse al dispositivo BLE y permitir la interacción con la tarjeta, enviando comandos (como encender o apagar el LED) y recibiendo datos (como la distancia medida), mostrándolos de forma visual al usuario.
De esta manera, ambas partes trabajan juntas para crear un sistema inalámbrico completo de control y monitoreo en tiempo real. Así que sigue los siguientes pasos de programación:
Programar la UNIT Pulsar ESP32-C6
Este código permite que la UNIT Pulsar ESP32-C6 funcione como un dispositivo Bluetooth BLE, al que una interfaz web o un celular pueden conectarse para enviar y recibir datos de forma inalámbrica. Con él se puede encender, apagar o cambiar el estado de un LED, además de medir la distancia con un sensor ultrasónico HC-SR04P y enviar esa información en tiempo real, sirviendo como base para agregar más sensores o actuadores y controlarlos fácilmente.
Para cargar el código siguiente ten las siguientes consideraciones:
- No necesitas instalar librerías adicionales para Bluetooth BLE, ya que al instalar las tarjetas de ESP32 de Espressif estas ya incluyen todo lo necesario.
- Antes de compilar el código, selecciona la tarjeta en Arduino IDE: Herramientas > Placa > ESP32 > ESP32C6 Dev Module.
- Conecta la UNIT Pulsar ESP32-C6 a tu computadora mediante USB.
- Selecciona el Puerto COM correspondiente en Arduino IDE Compila el código dando clic en el botón Verificar (✔️).
- Si aparece un error, revisa que tengas correctamente instaladas las tarjetas ESP32 de Espressif
- Asegúrate de no tener librerías BLE externas instaladas, ya que pueden generar conflictos.
- Si el código compila correctamente, haz clic en Subir (⬆️) para cargarlo a la tarjeta.
- Espera a que finalice la carga y la tarjeta estará lista para funcionar como dispositivo Bluetooth BLE 🚀.
// Librería principal para trabajar con Bluetooth Low Energy (BLE) en ESP32
#include <BLEDevice.h>
// Librería para crear el servidor BLE, es decir, el dispositivo ESP32 que será visible para el celular o la web BLE
#include <BLEServer.h>
// Librería con utilidades BLE, por ejemplo manejo interno de características y servicios
#include <BLEUtils.h>
// Librería para usar descriptores BLE, necesaria para que las notificaciones funcionen correctamente en muchos clientes BLE
#include <BLE2902.h>
// ======================
// Pines
// ======================
// Pin donde está conectado el LED que se quiere controlar
#define LED_PIN 9
// Pin TRIG del sensor ultrasónico HC-SR04P, se usa para enviar el pulso de disparo
#define TRIG_PIN 20
// Pin ECHO del sensor ultrasónico HC-SR04P, se usa para recibir el eco y medir la distancia
#define ECHO_PIN 21
// ======================
// BLE UUIDs
// ======================
// UUID del servicio BLE principal
// Un servicio BLE es como una carpeta que agrupa funciones relacionadas
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
// UUID de la característica RX
// RX se usa aquí para RECIBIR datos desde la web o desde el celular hacia el ESP32
#define CHARACTERISTIC_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" // Write desde la web
// UUID de la característica TX
// TX se usa aquí para ENVIAR datos desde el ESP32 hacia la web o celular
#define CHARACTERISTIC_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" // Notify hacia la web
// Puntero al servidor BLE
// Se deja inicialmente en nullptr porque todavía no ha sido creado
BLEServer* pServer = nullptr;
// Puntero a la característica TX, que servirá para enviar mensajes por BLE
BLECharacteristic* pTxCharacteristic = nullptr;
// Variable que indica si actualmente hay un cliente BLE conectado
bool deviceConnected = false;
// Variable auxiliar para detectar cambios de estado de conexión
// Sirve para saber cuándo un cliente se conectó o desconectó
bool oldDeviceConnected = false;
// Variable que guarda el estado actual del LED
// false = apagado, true = encendido
bool ledState = false;
// Variable para guardar el tiempo de la última vez que se envió la distancia por BLE
unsigned long lastDistanceSend = 0;
// Intervalo en milisegundos entre cada envío de distancia
// En este caso, se enviará aproximadamente cada 300 ms
const unsigned long DISTANCE_INTERVAL_MS = 300;
// ======================
// Prototipos
// ======================
// Prototipo de la función que procesa los comandos recibidos por BLE
void processCommand(String cmd);
// Prototipo de la función que envía mensajes o estados por BLE
void sendStatus(String msg);
// Prototipo de la función que mide y devuelve la distancia en centímetros
float readDistanceCM();
// ======================
// Callbacks BLE servidor
// ======================
// Se crea una clase personalizada que hereda de BLEServerCallbacks
// Esto permite ejecutar acciones automáticas cuando alguien se conecta o desconecta
class MyServerCallbacks : public BLEServerCallbacks {
// Esta función se ejecuta automáticamente cuando un cliente BLE se conecta
void onConnect(BLEServer* pServer) override {
// Se actualiza la variable para indicar que sí hay conexión
deviceConnected = true;
// Mensaje en el monitor serial para depuración
Serial.println("Cliente BLE conectado");
// Se envía un mensaje por BLE a la web o app para avisar que ya hay conexión
sendStatus("connected");
}
// Esta función se ejecuta automáticamente cuando un cliente BLE se desconecta
void onDisconnect(BLEServer* pServer) override {
// Se actualiza la variable para indicar que ya no hay conexión
deviceConnected = false;
// Mensaje en el monitor serial para depuración
Serial.println("Cliente BLE desconectado");
}
};
// ======================
// Callback BLE escritura
// ======================
// Se crea otra clase personalizada para detectar cuando el cliente escribe datos en la característica RX
class MyCallbacks : public BLECharacteristicCallbacks {
// Esta función se ejecuta automáticamente cuando la web o app manda un comando al ESP32
void onWrite(BLECharacteristic* pCharacteristic) override {
// Se lee el valor recibido y se convierte a String para poder procesarlo fácilmente
String cmd = pCharacteristic->getValue().c_str();
// Elimina espacios o saltos de línea al inicio y final del comando
cmd.trim();
// Solo se procesa si el comando no está vacío
if (cmd.length() > 0) {
// Se imprime el comando recibido en el monitor serial
Serial.print("Comando BLE recibido: ");
Serial.println(cmd);
// Se manda el comando a la función encargada de interpretarlo
processCommand(cmd);
}
}
};
// ======================
// Setup
// ======================
// La función setup se ejecuta una sola vez al encender o reiniciar la tarjeta
void setup() {
// Inicia la comunicación serial a 115200 baudios para ver mensajes en el monitor serial
Serial.begin(115200);
// Pequeña pausa para dar tiempo a que arranque correctamente el sistema
delay(500);
// Imprime una línea vacía en el monitor serial
Serial.println();
// Imprime encabezado informativo
Serial.println("======================================");
Serial.println("UNIT Pulsar ESP32-C6 BLE + HC-SR04P");
Serial.println("======================================");
// Configura el pin del LED como salida
pinMode(LED_PIN, OUTPUT);
// Apaga el LED al iniciar
digitalWrite(LED_PIN, LOW);
// Guarda el estado inicial del LED como apagado
ledState = false;
// Configura el pin TRIG del ultrasónico como salida
pinMode(TRIG_PIN, OUTPUT);
// Configura el pin ECHO del ultrasónico como entrada
pinMode(ECHO_PIN, INPUT);
// Asegura que el pin TRIG inicie en nivel bajo
digitalWrite(TRIG_PIN, LOW);
// ======================
// Configuración BLE
// ======================
// Inicializa el dispositivo BLE y le pone el nombre visible
// Este nombre es el que aparecerá cuando se busque el dispositivo desde el celular o web BLE
BLEDevice::init("UNIT PULSAR BLE");
// Crea el servidor BLE
pServer = BLEDevice::createServer();
// Asigna los callbacks de conexión y desconexión al servidor
pServer->setCallbacks(new MyServerCallbacks());
// Crea el servicio BLE principal usando el UUID definido anteriormente
BLEService* pService = pServer->createService(SERVICE_UUID);
// ======================
// Característica TX
// ======================
// Crea la característica TX, la cual se usará para enviar datos al cliente BLE
pTxCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_TX, // UUID de la característica TX
BLECharacteristic::PROPERTY_NOTIFY // Propiedad notify para enviar notificaciones
);
// Agrega el descriptor BLE2902 para habilitar correctamente las notificaciones
pTxCharacteristic->addDescriptor(new BLE2902());
// ======================
// Característica RX
// ======================
// Crea la característica RX, la cual se usará para recibir datos del cliente BLE
BLECharacteristic* pRxCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_RX, // UUID de la característica RX
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR // Permite escribir con y sin respuesta
);
// Asigna el callback que se ejecutará cuando el cliente escriba un comando
pRxCharacteristic->setCallbacks(new MyCallbacks());
// Inicia el servicio BLE para que quede activo
pService->start();
// ======================
// Advertising BLE
// ======================
// Obtiene el objeto encargado de anunciar el dispositivo BLE
BLEAdvertising* pAdvertising = BLEDevice::getAdvertising();
// Agrega el UUID del servicio al anuncio BLE
// Esto ayuda a que el cliente identifique qué servicio ofrece el dispositivo
pAdvertising->addServiceUUID(SERVICE_UUID);
// Activa respuesta de escaneo para mejorar compatibilidad con algunos clientes
pAdvertising->setScanResponse(true);
// Inicia el advertising, es decir, empieza a anunciarse para que otros dispositivos puedan encontrarlo
pAdvertising->start();
// Mensajes informativos en el monitor serial
Serial.println("BLE listo");
Serial.println("Nombre BLE: UNIT PULSAR BLE");
Serial.println("Esperando cliente...");
}
// ======================
// Loop
// ======================
// La función loop se ejecuta repetidamente mientras la tarjeta esté encendida
void loop() {
// ==========================================
// Enviar distancia periódicamente por BLE
// ==========================================
// Si hay un cliente conectado y ya pasó el tiempo definido entre mediciones
if (deviceConnected && millis() - lastDistanceSend >= DISTANCE_INTERVAL_MS) {
// Lee la distancia actual en centímetros
float distance = readDistanceCM();
// Variable donde se guardará el mensaje a enviar
String msg;
// Si la distancia es menor que 0 significa error o fuera de rango
if (distance < 0) {
msg = "distance:out"; // Mensaje para indicar que no se pudo medir correctamente
} else {
msg = "distance:" + String(distance, 1); // Convierte la distancia a texto con 1 decimal
}
// Envía el mensaje por BLE
sendStatus(msg);
// Actualiza el tiempo del último envío
lastDistanceSend = millis();
}
// ==========================================
// Reanudar advertising si se desconecta
// ==========================================
// Si actualmente NO hay dispositivo conectado, pero antes sí había uno
if (!deviceConnected && oldDeviceConnected) {
// Pequeña pausa para estabilizar el cambio de estado
delay(300);
// Vuelve a activar el advertising para que el ESP32 pueda ser encontrado otra vez
pServer->startAdvertising();
// Mensaje en serial indicando que se reinició el advertising
Serial.println("Reiniciando advertising BLE...");
// Actualiza la variable de estado anterior
oldDeviceConnected = deviceConnected;
}
// Si actualmente sí hay conexión, pero antes no la había
if (deviceConnected && !oldDeviceConnected) {
// Se actualiza la variable anterior para reflejar el nuevo estado
oldDeviceConnected = deviceConnected;
}
}
// ======================
// Procesar comandos
// ======================
// Esta función recibe un comando de texto y decide qué acción realizar
void processCommand(String cmd) {
// Si el comando recibido es "led-on"
if (cmd == "led-on") {
// Enciende el LED
digitalWrite(LED_PIN, HIGH);
// Guarda el nuevo estado del LED
ledState = true;
// Envía el nuevo estado por BLE
sendStatus("led:on");
// Mensaje informativo en serial
Serial.println("LED encendido");
// Si el comando recibido es "led-off"
} else if (cmd == "led-off") {
// Apaga el LED
digitalWrite(LED_PIN, LOW);
// Guarda el nuevo estado del LED
ledState = false;
// Envía el nuevo estado por BLE
sendStatus("led:off");
// Mensaje informativo en serial
Serial.println("LED apagado");
// Si el comando recibido es "toggle-led"
} else if (cmd == "toggle-led") {
// Cambia el estado actual del LED: si estaba apagado lo enciende, si estaba encendido lo apaga
ledState = !ledState;
// Aplica el nuevo estado al pin del LED
digitalWrite(LED_PIN, ledState ? HIGH : LOW);
// Envía el estado actual por BLE
sendStatus(ledState ? "led:on" : "led:off");
// Imprime en serial el estado actual
Serial.println(ledState ? "LED encendido" : "LED apagado");
// Si el comando recibido es "ping"
} else if (cmd == "ping") {
// Responde con "pong" para comprobar que la comunicación funciona
sendStatus("pong");
// Si el comando no coincide con ninguno conocido
} else {
// Envía un mensaje indicando que el comando no es válido
sendStatus("cmd:unknown");
}
}
// ======================
// Leer distancia HC-SR04P
// ======================
// Esta función mide la distancia con el sensor ultrasónico y devuelve el valor en centímetros
float readDistanceCM() {
// Coloca el pin TRIG en bajo para asegurar un inicio limpio
digitalWrite(TRIG_PIN, LOW);
// Espera 3 microsegundos
delayMicroseconds(3);
// Activa el pulso de disparo del sensor ultrasónico
digitalWrite(TRIG_PIN, HIGH);
// Mantiene el pulso durante 10 microsegundos, como requiere el HC-SR04P
delayMicroseconds(10);
// Vuelve a bajar el pin TRIG para finalizar el pulso
digitalWrite(TRIG_PIN, LOW);
// Mide cuánto tiempo permanece el pin ECHO en nivel alto
// Ese tiempo corresponde al viaje del sonido de ida y vuelta
// El timeout es de 30000 microsegundos = 30 ms
unsigned long duration = pulseIn(ECHO_PIN, HIGH, 30000UL);
// Si duration vale 0 significa que no se detectó eco dentro del tiempo esperado
if (duration == 0) {
return -1; // Devuelve -1 para indicar fuera de rango o error de lectura
}
// Convierte la duración del eco a distancia en centímetros
// 0.0343 cm/us es la velocidad del sonido aproximada
// Se divide entre 2 porque el sonido va y regresa
float distance = duration * 0.0343f / 2.0f;
// Filtrado básico para descartar lecturas demasiado pequeñas o demasiado grandes
if (distance < 2.0 || distance > 450.0) {
return -1; // Devuelve -1 si está fuera del rango válido
}
// Si todo fue correcto, devuelve la distancia calculada
return distance;
}
// ======================
// Enviar notificación BLE
// ======================
// Esta función envía un mensaje al cliente BLE usando la característica TX
void sendStatus(String msg) {
// Solo envía si hay un cliente conectado y la característica TX fue creada correctamente
if (deviceConnected && pTxCharacteristic != nullptr) {
// Coloca el mensaje dentro de la característica TX
pTxCharacteristic->setValue(msg.c_str());
// Envía una notificación BLE al cliente
pTxCharacteristic->notify();
}
}
Si después quieres controlar, por ejemplo, un relevador o un buzzer:
1.- Declaras otro pin:
#define BUZZER_PIN 5
2.- Lo configuras en setup():
pinMode(BUZZER_PIN, OUTPUT); digitalWrite(BUZZER_PIN, LOW);
3.- Agregas comandos en processCommand():
else if (cmd == "buzzer-on") {
digitalWrite(BUZZER_PIN, HIGH);
sendStatus("buzzer:on");
}
else if (cmd == "buzzer-off") {
digitalWrite(BUZZER_PIN, LOW);
sendStatus("buzzer:off");
}
Si quieres agregar otro sensor, por ejemplo uno digital o analógico:
- Declaras su pin.
- Lo configuras en
setup(). - Creas una función nueva para leerlo.
- En
loop()envías el dato por BLE con otro formato, por ejemplo:
sendStatus("temp:25.4");
sendStatus("button:pressed");
sendStatus("ldr:780");
Programación de la interfaz web BLE
La interfaz web BLE es una página desarrollada con HTML, CSS y JavaScript que permite conectarse a la UNIT Pulsar ESP32-C6 directamente desde el navegador, sin necesidad de instalar aplicaciones móviles.
Su objetivo es brindar una forma visual, sencilla e intuitiva de interactuar con la tarjeta, permitiendo enviar comandos y visualizar datos en tiempo real.

¿Qué puedes hacer con esta interfaz?
Para este tutorial, la interfaz web fue diseñada para realizar las siguientes funciones:
- 🔍 Buscar la UNIT Pulsar ESP32-C6 mediante Bluetooth
- 🔗 Establecer conexión con el dispositivo BLE
- 💡 Enviar comandos para encender y apagar el LED
- 📏 Mostrar en pantalla la distancia medida por el sensor ultrasónico
Descarga de la interfaz
Para facilitar la implementación, puedes utilizar la interfaz ya desarrollada:
- Archivo: Interfaz-web-BLE-Pulsar.html
Cómo usar la interfaz paso a paso
1. Descargar y preparar el archivo
- Descarga el archivo comprimido (.ZIP)
- Descomprímelo en tu computadora
- Ubica el archivo .HTML
2. Abrir la interfaz
- Abre el archivo con un navegador compatible como:
- Google Chrome
- Microsoft Edge
También puedes:
- Enviar el archivo a tu celular (por USB, correo o nube)
- Abrirlo directamente desde el navegador del teléfono
3. Conectarse al Bluetooth BLE
- Presiona el botón “Conectar” en la interfaz
- Selecciona la UNIT Pulsar ESP32-C6 en la lista de dispositivos
- Acepta el permiso de conexión del navegador
4. Controlar y visualizar datos
Una vez conectado podrás:
- Encender o apagar el LED desde los botones
- Visualizar la distancia medida en tiempo real
⚠️ Consideraciones importantes
- Utiliza navegadores compatibles con Web Bluetooth (Chrome o Edge)
- Es necesario aceptar los permisos de conexión
- Mantén activado el Bluetooth en tu dispositivo
- Asegúrate de que la ESP32-C6 esté encendida y ejecutando el código
En el siguiente video tienes la referencia para realizar los pasos anteriores:
Personalizar la interfaz web
Si deseas personalizar la interfaz web o agregar más sensores y actuadores a tu proyecto con la UNIT Pulsar ESP32-C6, puedes utilizar el siguiente código como base.
Este código está desarrollado en HTML, CSS y JavaScript, por lo que puedes abrirlo y editarlo fácilmente en cualquier editor de tu preferencia, como Visual Studio Code o incluso en un editor de texto simple.
A partir de esta base podrás:
- Agregar nuevos botones para controlar más dispositivos
- Mostrar datos adicionales de otros sensores
- Modificar el diseño o la interfaz visual
- Adaptar la lógica de comunicación BLE según tus necesidades
Solo necesitas copiar el código, abrirlo en tu editor, seguir las recomendaciones y realizar las modificaciones que requiera tu proyecto.
<!DOCTYPE html>
<!-- Indica al navegador que este archivo usa HTML5 -->
<html lang="es">
<!-- Inicio del documento HTML y se especifica que el idioma es español -->
<head>
<!-- Sección de configuración del documento: título, codificación, estilos, etc. -->
<meta charset="UTF-8" />
<!-- Permite usar caracteres especiales como acentos, eñes y símbolos correctamente -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<!-- Hace que la página se adapte bien a celulares y tablets -->
<!-- initial-scale=1.0 muestra la página con zoom normal -->
<!-- maximum-scale=1.0 evita zoom extra -->
<!-- user-scalable=no evita que el usuario haga zoom manual -->
<title>Control por BLE</title>
<!-- Título que aparece en la pestaña del navegador -->
<style>
/* Inicio de los estilos CSS de la página */
* {
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
/* Aplica a todos los elementos */
/* box-sizing: border-box hace que padding y border no aumenten el tamaño total del elemento */
/* -webkit-tap-highlight-color: transparent elimina el brillo azul al tocar en móviles */
body {
font-family: Arial, sans-serif;
text-align: center;
background: #f5f5f5;
margin: 0;
padding: 20px;
}
/* Estilo general del cuerpo de la página */
/* font-family define el tipo de letra */
/* text-align: center centra el texto */
/* background pone un fondo gris claro */
/* margin: 0 elimina márgenes externos del navegador */
/* padding: 20px deja espacio interior */
h1 {
margin: 10px 0 6px;
font-size: 28px;
}
/* Estilo del título principal */
/* margin controla espacio arriba, derecha/izquierda y abajo */
/* font-size define el tamaño del texto */
#status {
font-weight: bold;
margin-bottom: 8px;
font-size: 18px;
}
/* Estilo del texto de estado de conexión */
/* font-weight: bold lo pone en negritas */
/* margin-bottom deja espacio debajo */
/* font-size define tamaño */
#info {
font-size: 14px;
color: #666;
margin-bottom: 20px;
min-height: 20px;
}
/* Estilo del texto informativo */
/* color: #666 lo pone gris */
/* min-height evita que el espacio cambie de tamaño cuando cambia el texto */
.topbar {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 16px;
flex-wrap: wrap;
}
/* Contenedor de los botones superiores */
/* display: flex acomoda elementos en fila */
/* justify-content: center los centra horizontalmente */
/* gap deja espacio entre botones */
/* margin-bottom deja espacio debajo */
/* flex-wrap: wrap permite que bajen de línea si no caben */
button.action {
min-width: 140px;
height: 46px;
font-size: 16px;
border: none;
border-radius: 10px;
background: #333;
color: white;
padding: 0 14px;
}
/* Estilo de los botones Conectar y Desconectar */
/* min-width da un ancho mínimo */
/* height define altura */
/* font-size define tamaño del texto */
/* border: none quita borde */
/* border-radius redondea esquinas */
/* background define color de fondo */
/* color define color del texto */
/* padding agrega espacio interno horizontal */
.panel {
max-width: 420px;
margin: 0 auto;
background: white;
border-radius: 18px;
padding: 24px 20px;
box-shadow: 0 8px 24px rgba(0,0,0,0.08);
}
/* Panel principal blanco que contiene la lectura de distancia y control del LED */
/* max-width limita su ancho máximo */
/* margin: 0 auto lo centra */
/* background: white fondo blanco */
/* border-radius redondea el panel */
/* padding da espacio interno */
/* box-shadow agrega sombra */
.distance-box {
margin: 18px 0 24px;
padding: 18px;
border-radius: 16px;
background: #eef4ff;
}
/* Caja donde se muestra la distancia */
/* margin da espacio afuera */
/* padding da espacio adentro */
/* border-radius redondea esquinas */
/* background pone fondo azul claro */
.distance-title {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
/* Estilo del título "Lecturas de distancia" */
.distance-value {
font-size: 40px;
font-weight: bold;
color: #111;
}
/* Estilo del valor numérico grande de la distancia */
.distance-unit {
font-size: 18px;
color: #666;
}
/* Estilo de la unidad "cm" */
.led-section {
margin-top: 10px;
}
/* Espacio superior para la sección del LED */
.led-label {
font-size: 15px;
margin-bottom: 12px;
color: #444;
}
/* Estilo del texto "Control del LED" */
.led-btn {
width: 140px;
height: 140px;
font-size: 30px;
border: none;
border-radius: 22px;
background: #eb6f0f;
color: white;
box-shadow: 0 6px 14px rgba(0,0,0,0.14);
transition: transform 0.08s ease, opacity 0.2s ease, background-color 0.2s ease;
}
/* Estilo del botón grande del LED */
/* width y height lo hacen cuadrado */
/* font-size aumenta el emoji o texto */
/* border-radius redondea esquinas */
/* background es el color principal del botón */
/* color pone el texto/emoji en blanco */
/* box-shadow agrega sombra */
/* transition suaviza cambios visuales */
.led-btn:active,
.action:active {
transform: scale(0.98);
}
/* Cuando se presiona un botón, se hace ligeramente más pequeño para dar efecto visual */
.led-btn.on {
background: #2e7d32;
}
/* Cuando el LED está encendido, el botón cambia a color verde */
.led-state {
margin-top: 14px;
font-size: 16px;
font-weight: bold;
}
/* Estilo del texto que dice "LED encendido" o "LED apagado" */
.small {
margin-top: 16px;
color: #777;
font-size: 13px;
max-width: 320px;
margin-left: auto;
margin-right: auto;
}
/* Texto pequeño de recomendación al final */
/* max-width limita el ancho */
/* margin-left y margin-right auto lo centran */
</style>
</head>
<body>
<!-- Inicio del contenido visible de la página -->
<h1>UNIT PULSAR ESP32 C6</h1>
<!-- Título principal de la interfaz -->
<h3>Interfaz Web Bluetooth BLE</h3>
<!-- Subtítulo de la interfaz -->
<div id="status">Desconectado</div>
<!-- Texto que indica el estado actual de conexión BLE -->
<div id="info">Presiona “Conectar BLE” para enlazar la tarjeta</div>
<!-- Texto informativo para mostrar instrucciones o mensajes al usuario -->
<div class="topbar">
<!-- Contenedor de botones superiores -->
<button class="action" id="connectBtn">Conectar BLE</button>
<!-- Botón para iniciar la conexión BLE con la tarjeta -->
<button class="action" id="disconnectBtn">Desconectar</button>
<!-- Botón para desconectar la conexión BLE -->
</div>
<div class="panel">
<!-- Panel principal -->
<div class="distance-box">
<!-- Caja que contiene la lectura de distancia -->
<div class="distance-title">Lecturas de distancia</div>
<!-- Título de la sección de distancia -->
<div>
<!-- Contenedor del valor y unidad -->
<span class="distance-value" id="distanceValue">--.-</span>
<!-- Aquí se mostrará el valor de la distancia que llega por BLE -->
<span class="distance-unit">cm</span>
<!-- Unidad de la medición -->
</div>
</div>
<div class="led-section">
<!-- Sección de control del LED -->
<div class="led-label">Control del LED</div>
<!-- Título de la sección del LED -->
<button class="led-btn" id="ledBtn">💡</button>
<!-- Botón grande para encender o apagar el LED -->
<div class="led-state" id="ledStateText">LED apagado</div>
<!-- Texto que muestra el estado actual del LED -->
</div>
</div>
<div class="small">
Recomendado: Chrome o Edge en Android con Web Bluetooth habilitado.
</div>
<!-- Nota final recomendando navegadores compatibles -->
<script>
// Inicio del código JavaScript que controla la lógica de la interfaz
const SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
// UUID del servicio BLE principal
// Debe coincidir exactamente con el UUID configurado en el ESP32
const RX_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e";
// UUID de la característica RX
// Se usa para ENVIAR comandos desde la web hacia el ESP32
const TX_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e";
// UUID de la característica TX
// Se usa para RECIBIR datos o estados desde el ESP32 hacia la web
let device = null;
// Guardará el dispositivo BLE seleccionado por el usuario
let server = null;
// Guardará el servidor GATT del dispositivo BLE
let service = null;
// Guardará el servicio BLE principal
let rxCharacteristic = null;
// Guardará la característica BLE usada para enviar comandos al ESP32
let txCharacteristic = null;
// Guardará la característica BLE usada para recibir notificaciones del ESP32
let ledIsOn = false;
// Variable que almacena el estado visual del LED en la interfaz
// false = apagado, true = encendido
function setStatus(text, color = "black") {
// Función para cambiar el texto del estado principal y su color
const el = document.getElementById("status");
// Busca en el HTML el elemento con id "status"
el.textContent = text;
// Cambia el texto visible de ese elemento
el.style.color = color;
// Cambia el color del texto
}
function setInfo(text) {
// Función para cambiar el texto informativo secundario
document.getElementById("info").textContent = text;
// Busca el elemento con id "info" y reemplaza su texto
}
function isBleConnected() {
// Función que verifica si realmente hay conexión BLE activa
return !!(device && device.gatt && device.gatt.connected && rxCharacteristic);
// Devuelve true solo si:
// - existe un dispositivo
// - existe acceso GATT
// - está conectado
// - existe la característica RX
// El doble !! convierte el resultado en true o false
}
function updateLedVisual() {
// Función para actualizar el aspecto visual del botón y texto del LED
const btn = document.getElementById("ledBtn");
// Busca el botón del LED
const stateText = document.getElementById("ledStateText");
// Busca el texto que muestra el estado del LED
if (ledIsOn) {
// Si el LED está encendido
btn.classList.add("on");
// Agrega la clase "on" al botón, cambiando su color a verde
stateText.textContent = "LED encendido";
// Cambia el texto a LED encendido
} else {
// Si el LED está apagado
btn.classList.remove("on");
// Quita la clase "on", devolviendo el color original
stateText.textContent = "LED apagado";
// Cambia el texto a LED apagado
}
}
function setDistanceValue(value) {
// Función para actualizar el valor visible de la distancia
document.getElementById("distanceValue").textContent = value;
// Reemplaza el texto del valor mostrado en pantalla
}
async function connectBLE() {
// Función asíncrona para buscar y conectar la tarjeta BLE
try {
// try permite intentar la conexión y detectar errores si algo falla
setStatus("Buscando tarjeta...", "#ef6c00");
// Muestra al usuario que se está buscando la tarjeta
setInfo("Acepta la conexión BLE en el teléfono");
// Muestra instrucción para el usuario
device = await navigator.bluetooth.requestDevice({
// Abre el cuadro del navegador para seleccionar un dispositivo BLE
filters: [
{ namePrefix: "UNIT PULSAR" },
// Filtra dispositivos cuyo nombre empiece con "UNIT PULSAR"
{ services: [SERVICE_UUID] }
// También filtra por el servicio BLE definido
],
optionalServices: [SERVICE_UUID]
// Indica servicios adicionales que se quiere usar después de conectar
});
device.addEventListener("gattserverdisconnected", onDisconnected);
// Si el dispositivo se desconecta, ejecuta automáticamente la función onDisconnected
setStatus("Conectando...", "#ef6c00");
// Cambia el estado visual a conectando
server = await device.gatt.connect();
// Se conecta al servidor GATT del dispositivo BLE
service = await server.getPrimaryService(SERVICE_UUID);
// Obtiene el servicio BLE principal usando su UUID
rxCharacteristic = await service.getCharacteristic(RX_UUID);
// Obtiene la característica RX para escribir comandos
txCharacteristic = await service.getCharacteristic(TX_UUID);
// Obtiene la característica TX para recibir datos
try {
// Segundo try para activar notificaciones BLE
await txCharacteristic.startNotifications();
// Habilita las notificaciones en la característica TX
txCharacteristic.addEventListener("characteristicvaluechanged", handleNotification);
// Cada vez que llegue un dato desde el ESP32 se ejecutará handleNotification
} catch (e) {
// Si fallan las notificaciones, entra aquí
console.log("No se activaron notificaciones:", e);
// Muestra el error en consola
}
setStatus("Conectado", "green");
// Muestra estado de conexión exitosa
setInfo("UNIT Pulsar enlazada correctamente");
// Muestra mensaje informativo de éxito
} catch (error) {
// Si algo falla en todo el proceso de conexión entra aquí
console.error(error);
// Imprime el error en consola para depuración
setStatus("Error de conexión", "red");
// Muestra estado de error en rojo
setInfo("No se pudo enlazar la tarjeta");
// Muestra mensaje informativo de error
}
}
function onDisconnected() {
// Función que se ejecuta cuando la conexión BLE se pierde o se desconecta
rxCharacteristic = null;
// Limpia la característica RX
txCharacteristic = null;
// Limpia la característica TX
service = null;
// Limpia la referencia al servicio
server = null;
// Limpia la referencia al servidor
setStatus("Desconectado", "red");
// Actualiza el texto de estado a desconectado
setInfo("Se perdió la conexión BLE");
// Informa al usuario que se perdió la conexión
setDistanceValue("--.-");
// Reinicia el valor mostrado de la distancia
}
async function disconnectBLE() {
// Función para desconectar manualmente el BLE
try {
// Se intenta hacer la desconexión segura
if (device && device.gatt && device.gatt.connected) {
// Solo se desconecta si realmente existe conexión activa
device.gatt.disconnect();
// Desconecta el dispositivo BLE
}
} catch (error) {
// Si ocurre un error al desconectar
console.error(error);
// Lo muestra en consola
}
}
function handleNotification(event) {
// Función que procesa cada mensaje recibido desde el ESP32 por BLE
const value = new TextDecoder().decode(event.target.value);
// Convierte los datos binarios recibidos en texto legible
console.log("BLE RX:", value);
// Muestra en consola el dato recibido
if (value === "connected") {
// Si el ESP32 manda el mensaje "connected"
setInfo("Control enlazado con la tarjeta");
// Se actualiza el mensaje informativo
} else if (value === "pong") {
// Si el ESP32 responde "pong"
// keep alive
// No se hace nada visible, solo sirve para comprobar que la conexión sigue viva
} else if (value === "led:on") {
// Si el ESP32 avisa que el LED está encendido
ledIsOn = true;
// Actualiza la variable interna
updateLedVisual();
// Refresca el aspecto visual del LED
} else if (value === "led:off") {
// Si el ESP32 avisa que el LED está apagado
ledIsOn = false;
// Actualiza la variable interna
updateLedVisual();
// Refresca el aspecto visual
} else if (value.startsWith("distance:")) {
// Si el mensaje empieza con "distance:" significa que llegó una lectura del sensor
const raw = value.split(":")[1];
// Divide el texto por ":" y toma la parte después de distance
if (raw === "out") {
// Si el valor recibido es "out" significa fuera de rango
setDistanceValue("---");
// Muestra guiones en vez de una lectura numérica
setInfo("Objeto fuera de rango");
// Informa al usuario que no hay lectura válida
} else {
// Si sí llegó un valor numérico
setDistanceValue(raw);
// Muestra ese valor en pantalla
setInfo("Lectura de distancia actualizada");
// Muestra mensaje informativo
}
} else {
// Si llega cualquier otro mensaje no contemplado
setInfo("Estado: " + value);
// Lo muestra como texto general
}
}
async function sendCommand(cmd) {
// Función asíncrona para enviar comandos de texto al ESP32
try {
// Intenta enviar el comando
if (!isBleConnected()) return;
// Si no hay conexión BLE activa, se sale de la función sin hacer nada
const data = new TextEncoder().encode(cmd);
// Convierte el texto del comando a formato binario para enviarlo por BLE
await rxCharacteristic.writeValue(data);
// Escribe el comando en la característica RX del ESP32
} catch (error) {
// Si ocurre un error al enviar
console.error("Error enviando comando:", error);
// Lo muestra en consola
setStatus("Error al enviar", "red");
// Cambia el estado visual a error
}
}
async function handleLedToggle() {
// Función que se ejecuta cuando el usuario presiona el botón del LED
if (!isBleConnected()) {
// Si no hay conexión BLE activa
setInfo("Primero conecta la tarjeta por BLE");
// Informa al usuario que primero debe conectarse
return;
// Sale de la función sin intentar enviar el comando
}
await sendCommand("toggle-led");
// Envía al ESP32 el comando para cambiar el estado del LED
}
document.getElementById("connectBtn").addEventListener("click", connectBLE);
// Cuando se haga clic en el botón Conectar BLE, se ejecuta connectBLE
document.getElementById("disconnectBtn").addEventListener("click", disconnectBLE);
// Cuando se haga clic en el botón Desconectar, se ejecuta disconnectBLE
document.getElementById("ledBtn").addEventListener("click", handleLedToggle);
// Cuando se haga clic en el botón del LED, se ejecuta handleLedToggle
setInterval(() => {
// Ejecuta una función cada cierto tiempo automáticamente
if (isBleConnected()) {
// Solo si hay conexión BLE activa
sendCommand("ping");
// Envía el comando "ping" al ESP32 para mantener viva o comprobar la conexión
}
}, 3000);
// Esto ocurre cada 3000 milisegundos, es decir, cada 3 segundos
updateLedVisual();
// Al cargar la página, actualiza el estado visual inicial del LED
</script>
</body>
</html>
Si quieres agregar otro botón para controlar otro actuador
Por ejemplo, un buzzer o relevador:
1.- Agregas un nuevo botón en HTML:
<button class="action" id="buzzerBtn">Buzzer</button>
2.- En JavaScript agregas el evento:
document.getElementById("buzzerBtn").addEventListener("click", async () => {
await sendCommand("toggle-buzzer");
});
3.- En el código de la UNIT Pulsa ESP32 C6 agregas el comando "toggle-buzzer".
Si quieres mostrar otro sensor en pantalla
Por ejemplo, temperatura:
1.- Agregas un espacio en HTML:
<div id="tempValue">--.- °C</div>
2.- En handleNotification() detectas mensajes como:
else if (value.startsWith("temp:")) {
const temp = value.split(":")[1];
document.getElementById("tempValue").textContent = temp + " °C";
}
3.- En la UNIT Pulsa ESP32 C6 mandas mensajes con formato:
sendStatus("temp:24.5");
CONCLUCIONES
El Bluetooth BLE de la UNIT Pulsar ESP32-C6 permite desarrollar proyectos de control y monitoreo inalámbrico de forma práctica, moderna y eficiente. En este tutorial se mostró cómo utilizar esta tecnología para crear una conexión con una interfaz web BLE, desde la cual fue posible encender y apagar un LED y visualizar la distancia medida por un sensor ultrasónico HC-SR04P.
Además de aprender a configurar el entorno en Arduino IDE, instalar las tarjetas de Espressif, comprender la lógica del código y desarrollar la interfaz web, también se estableció una base sólida para crear proyectos más avanzados. Con esta misma estructura, cualquier usuario podrá agregar más sensores y actuadores, ampliando las capacidades del sistema y aprovechando al máximo el potencial de la UNIT Pulsar ESP32-C6 en aplicaciones de automatización, monitoreo y control inalámbrico.

