¿Cómo utilizar el Bluetooth BLE de la UNIT Pulsar ESP32-C6?

¿Cómo utilizar el Bluetooth BLE de la UNIT Pulsar ESP32-C6

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:

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:

  • Agrega las placas de ESP32 de Espressif a URLs adicionales de Arduino IDE
    • Abre el IDE y ve a Archivo > Preferencias.
    • En el campo “Gestor de URLs adicionales de tarjetas”, agrega la siguiente dirección:
    • 👉 https://espressif.github.io/arduino-esp32/package_esp32_index.json
    • Si tienes otras tarjetas de ESP32 deberás eliminar solo dejar las tarjetas ESP32 de Espressif.
  • 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:

Ble con UNIT Pulsar ESP32 C6 Diagrama de conexión
ComponentePin del componentePin de la UNIT Pulsar ESP32-C6Tipo
LEDÁnodo (+)D3: GPIO 9Salida
LEDCátodo (-)GND (con resistencia 220Ω)
HC-SR04PVCC3.3VAlimentación
HC-SR04PGNDGNDTierra
HC-SR04PTRIGD6: GPIO 20Salida
HC-SR04PECHOD7: GPIO 21Entrada

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:

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.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *