Actualizado: 22 de marzo de 2023
Segunda actualización: Julio de 2023
Primeros pasos con ESP32 Bluetooth Low Energy (BLE) en Arduino IDE – Bluetooth de Bajo Consumo de Energía
El ESP32 cuenta con con Bluetooth Clásico y Bluetooth Low Energy (BLE)
Esta publicación es una introducción a BLE con el ESP32.
En esta sección: ¿Qué es BLE y para qué se puede usar?
También algunos ejemplos con el ESP32 usando Arduino IDE.
¿Qué es Bluetooth de baja energía?
Bluetooth Low Energy, BLE para abreviar, es una variante de ahorro de energía de Bluetooth. La aplicación principal de BLE es la transmisión a corta distancia de pequeñas cantidades de datos (bajo ancho de banda).
A diferencia de Bluetooth Clásico, que siempre está activado, BLE permanece en modo de suspensión constantemente, excepto cuando se inicia una conexión.
Comparado con Bluetooth clásico, Bluetooth Low Energy está diseñado para proporcionar un bajo consumo de energía, manteniendo un rango de alcance de comunicación similar.
Esto hace que consuma muy poca energía. BLE consume aproximadamente 100 veces menos energía que Bluetooth (según el caso de uso).
Además, BLE admite no solo la comunicación punto a punto, sino también el modo de transmisión y la red de malla.
Servidor y cliente BLE
Con Bluetooth Low Energy, hay dos tipos de dispositivos: el servidor y el cliente.
El ESP32 puede actuar como cliente o como servidor. El servidor anuncia su existencia, por lo que otros dispositivos pueden encontrarlo y contiene datos que el cliente puede leer. El cliente escanea los dispositivos cercanos y, cuando encuentra el servidor que está buscando, establece una conexión y escucha los datos entrantes. Esto se llama comunicación punto a punto.
Hay otros modos de comunicación posibles, como el modo de transmisión y la red de malla.
GATT
GATT significa Atributos Genéricos y define una estructura de datos jerárquica que está expuesta a los dispositivos BLE conectados. Esto significa que GATT define la forma en que dos dispositivos BLE envían y reciben mensajes estándar. Comprender esta jerarquía es importante porque facilitará la comprensión de cómo usar BLE con el ESP32.
Perfil: colección estándar de servicios para un caso de uso específico;
Servicio: recopilación de información relacionada, como lecturas de sensores, nivel de batería, frecuencia cardíaca, etc.;
Característica: es donde se guardan los datos reales en la jerarquía (valor);
Descriptor: metadatos sobre los datos;
Propiedades: describe cómo se puede interactuar con el valor característico. Por ejemplo: leer, escribir, notificar, difundir, indicar, etc.
En nuestro ejemplo, crearemos un servicio con dos características.
Uno para la temperatura y otro para la humedad.
Las lecturas reales de temperatura y humedad se guardan en el valor bajo sus características. Cada característica tiene la propiedad de notificación, de modo que notifique al cliente cada vez que cambien los valores.
UUI
Cada servicio, característica y descriptor tiene un UUID (Universally Unique Identifier). Un UUID es un número único de 128 bits (16 bytes).
Por ejemplo:
55072829-bc9e-4c53-938a-74a6d4c78776
Hay UUID abreviados para todos los tipos, servicios y perfiles especificados en el SIG (Bluetooth Special Interest Group).
Si su aplicación necesita su propio UUID, puede generarlo utilizando este sitio web generador de UUID.
En resumen, el UUID se utiliza para identificar información de manera única. Por ejemplo, puede identificar un servicio particular proporcionado por un dispositivo Bluetooth.
Conectando el ESP32
Este ejemplo funcionaría para cualquier placa de desarrollo actual con ESP-32. En este caco se utilizaron las siguientes placas:
Sketch para Servidor BLE
#define LED_BUILTIN 2
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#define temperatureCelsius
#define BLE_server "ESP32_Server"
#define SERVICE_UUID "huecat23-6d3c-4a17-a71f-ece2e6075f9b"
BLECharacteristic dhtTemperatureCelsiusCharacteristics("huecat23-c85e-4596-9bd9-015e2eaa4888", BLECharacteristic::PROPERTY_NOTIFY);
BLEDescriptor dhtTemperatureCelsiusDescriptor(BLEUUID((uint16_t)0x2902));
BLECharacteristic dhtHumidityCharacteristics("huecat23-4a2f-4cf3-96e6-38b79be71a61", BLECharacteristic::PROPERTY_NOTIFY);
BLEDescriptor dhtHumidityDescriptor(BLEUUID((uint16_t)0x2903));
bool device_connected = false;
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
device_connected = true;
};
void onDisconnect(BLEServer* pServer) {
device_connected = false;
}
};
void setup() {
Serial.begin(115200);
BLEDevice::init(BLE_server);
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
BLEService *dhtService = pServer->createService(SERVICE_UUID);
dhtService->addCharacteristic(&dhtTemperatureCelsiusCharacteristics);
dhtTemperatureCelsiusDescriptor.setValue("DHT Temperature (Celsius)");
dhtTemperatureCelsiusCharacteristics.addDescriptor(new BLE2902());
dhtService->addCharacteristic(&dhtHumidityCharacteristics);
dhtHumidityDescriptor.setValue("DHT humidity");
dhtHumidityCharacteristics.addDescriptor(new BLE2902());
dhtService->start();
pServer->getAdvertising()->start();
Serial.println("Esperando a Cliente Bluetooth");
}
void loop() {
//Timer cada 5 segundos: envía la info por bluetooth
static unsigned long enviaDato;
if (millis() > enviaDato + 5000) {
enviaDato = millis();
/////////////////////////////////
if (device_connected) {
//Datos de prueba
float temp = 25;
float hum = 88;
static char temperature_celsius[7];
dtostrf(temp, 6, 2, temperature_celsius);
dhtTemperatureCelsiusCharacteristics.setValue(temperature_celsius);
dhtTemperatureCelsiusCharacteristics.notify();
Serial.print("Temperatura: ");
Serial.print(temp);
Serial.print(" *C");
static char humidity[7];
dtostrf(hum, 6, 2, humidity);
dhtHumidityCharacteristics.setValue(humidity);
dhtHumidityCharacteristics.notify();
Serial.print(" Humedad: ");
Serial.print(hum);
Serial.println(" %");
}
}
//Parpadeo del LED
static unsigned long parpadeo;
if (millis() > parpadeo + 80) {
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
parpadeo = millis();
}
}
Sketch para el Cliente BLE
#include "BLEDevice.h"
#include "heltec.h"
#include "Arduino.h"
SSD1306Wire display(0x3c, SDA_OLED, SCL_OLED, RST_OLED); //128_64
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define temperatureCelsius
#define BLE_server "ESP32_Server"
// Generador de UUIDs:
// https://www.uuidgenerator.net/
//***********************************
//BLE ID UNICO PARA SERVICIO
static BLEUUID dhtServiceUUID("huecat23-6d3c-4a17-a71f-ece2e6075f9b");
//***********************************
//BLE ID UNICO PARA CARACTERÍSTICA (TEMPERATURA)
static BLEUUID temperatureCharacteristicUUID("huecat23-c85e-4596-9bd9-015e2eaa4888");
//BLE ID UNICO PARA CARACTERÍSTICA (HUMEDAD)
static BLEUUID humidityCharacteristicUUID("huecat23-4a2f-4cf3-96e6-38b79be71a61");
/*
//***********************************
//BLE ID UNICO PARA SERVICIO
static BLEUUID dhtServiceUUID("d29181ad-6d3c-4a17-a71f-ece2e6075f9b");
//***********************************
//BLE ID UNICO PARA CARACTERÍSTICA (TEMPERATURA)
static BLEUUID temperatureCharacteristicUUID("ea4963df-c85e-4596-9bd9-015e2eaa4888");
//BLE ID UNICO PARA CARACTERÍSTICA (HUMEDAD)
static BLEUUID humidityCharacteristicUUID("b61decbb-4a2f-4cf3-96e6-38b79be71a61");
*/
static boolean bleConectado = false;
static boolean connected = false;
static BLEAddress *pServerAddress;
static BLERemoteCharacteristic* temperatureCharacteristic;
static BLERemoteCharacteristic* humidityCharacteristic;
const uint8_t notificationOn[] = {0x1, 0x0};
const uint8_t notificationOff[] = {0x0, 0x0};
bool conectarAServidor(BLEAddress pAddress) {
BLEClient* pClient = BLEDevice::createClient();
pClient->connect(pAddress);
Serial.println("Conectado al Servidor");
BLERemoteService* pRemoteService = pClient->getService(dhtServiceUUID);
if (pRemoteService == nullptr) {
Serial.print("Failed to find our service UUID: ");
Serial.println(dhtServiceUUID.toString().c_str());
return (false);
}
temperatureCharacteristic = pRemoteService->getCharacteristic(temperatureCharacteristicUUID);
humidityCharacteristic = pRemoteService->getCharacteristic(humidityCharacteristicUUID);
if (temperatureCharacteristic == nullptr || humidityCharacteristic == nullptr) {
Serial.print("Failed to find our characteristic UUID");
return false;
}
Serial.println(" Characteristics Found!");
temperatureCharacteristic->registerForNotify(temperatureNotifyCallback);
humidityCharacteristic->registerForNotify(humidityNotifyCallback);
}
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
if (advertisedDevice.getName() == BLE_server) {
advertisedDevice.getScan()->stop();
pServerAddress = new BLEAddress(advertisedDevice.getAddress());
bleConectado = true;
Serial.println("Dispositivo encontrado. Conectando...");
}
}
};
static void temperatureNotifyCallback(BLERemoteCharacteristic*pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify)
{
String informarTemperatura = "";
informarTemperatura = "Temperatura: ";
informarTemperatura += (char*)pData;
informarTemperatura += " *C";
imprimirConAltura(informarTemperatura, 20); ////// IMPRIME OLED Y SERIAL //////
}
static void humidityNotifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) {
String informarHumedad = "Humedad: ";
informarHumedad += (char*)pData;
informarHumedad += " %";
//imprimirConAltura(informarHumedad, 28); ////// IMPRIME OLED Y SERIAL //////
Serial.println(informarHumedad);
}
void setup()
{
Serial.begin(115200);
Serial.println("\nESP-32--B-BLE-CLIENTE");
///////////////////////////////////////// PARA PANTALLA OLED
initHeltecEsp32(); //Inicializaciones correspondientes a modelo Pantalla OLED elegido.
imprimirConAltura("Comenzando conexión BLE ( Cliente)", 0); ////// IMPRIME OLED Y SERIAL //////
BLEDevice::init("");
BLEScan* pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true);
pBLEScan->start(30);
}
void loop() {
if (bleConectado == true) {
if (conectarAServidor(*pServerAddress)) {
String conectando = "Conectado servidor BLE.";
imprimirConAltura(conectando, 8); ////// IMPRIME OLED Y SERIAL //////
temperatureCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true);
humidityCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true);
connected = true;
} else {
String conectando_error = "Falló conexión Servidor BLE";
imprimirConAltura(conectando_error, 8); ////// IMPRIME OLED Y SERIAL //////
}
bleConectado = false;
}
delay(1000);
}
//Basado en ejemplo Heltec OLED_rotate.ino
void VextON(void)
{
pinMode(Vext, OUTPUT);
digitalWrite(Vext, LOW);
}
void VextOFF(void) //Vext default OFF
{
pinMode(Vext, OUTPUT);
digitalWrite(Vext, HIGH);
}
void initHeltecEsp32() {
VextON();
delay(100);
display.init();
display.clear();
display.display();
display.setContrast(1);
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.clear();
display.display();
display.screenRotate(ANGLE_0_DEGREE);
display.setFont(ArialMT_Plain_16);
display.drawString(64, 32 - 16 / 2, "HUE CAT"); //ROTATE_0
display.display();
delay(400);
display.clear();
display.display();
display.screenRotate(ANGLE_90_DEGREE);
display.setFont(ArialMT_Plain_10);
display.drawString(32, 64 - 10 / 2, "HUE CAT"); //ROTATE_90
display.display();
delay(400);
display.clear();
display.display();
display.screenRotate(ANGLE_180_DEGREE);
display.setFont(ArialMT_Plain_16);
display.drawString(64, 32 - 16 / 2, "HUE CAT"); //ROTATE_180
display.display();
delay(400);
display.clear();
display.display();
display.screenRotate(ANGLE_270_DEGREE);
display.setFont(ArialMT_Plain_10);
display.drawString(32, 64 - 10 / 2, "HUE CAT"); //ROTATE_270
display.display();
delay(400);
display.clear();
display.display();
display.screenRotate(ANGLE_0_DEGREE);
display.setFont(ArialMT_Plain_16);
display.drawString(64, 32 - 16 / 2, "HUE CAT"); //ROTATE_0
display.display();
delay(500);
display.clear();
display.display();
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.setFont(ArialMT_Plain_10);
}
void imprimirConAltura(String stringToPrint, int alturaAImprimir) {
//Imprime en monitor serie y display
Serial.print("OLED->" + stringToPrint);
display.drawString(0, alturaAImprimir, stringToPrint);
display.display();
}
Puedes subir un código para pantalla convencional16x02 o 16×04?
Hola Santiago, solo debes prescindir de la librería de HELTEC y utilizar la librería que viene con Arduino IDE «LCD»
No tenéis lo mismo pero con Wifi?
Hola Ramón! Sí aquí está! https://huecat.es/conectar-dos-esp32-por-wi-fi-sin-router-de-por-medio/