Servidor Bluetooth BLE
Material requerido.
Un ESP32 |
Bluetooth BLE
En la última sesión hablamos de los conceptos precisos para comprender el Bluetooth BLE de baja energía y de cómo el modelo conceptual había cambiado con respecto al más limitado (Y sencillo) Bluetooth Classic.
Cargamos el programa Servidor de ejemplo que nos suministran las librerías del ESP32 BLE, y vimos cómo, con la ayuda de una app para nuestro móvil, podíamos comprobar que publicaba correctamente nuestros datos, los pocos que habíamos manipulado, pero sin entrar en el detalle de funcionamiento del programa servidor.
Tan imperdonable omisión no podía quedar impune, y por ello en esta nueva sesión vamos a entrar en el detalle de las líneas que conforman el programa Servidor BLE ESP32, para que podáis modificarlo en vuestros proyectos personales en caso de que sea necesario. Así que, sin más dilación, vamos al lío
Servidor BLE
Si recordáis la sesión anterior, habíamos comentado que teníamos que dar una serie de pasos para definir un servidor BLE:
Vamos como siempre a empezar por los include necesarios para mover todo, gracias a los que nos ahorramos todos los sucios detalles de bajar al barro.
#include <BLEDevice.h> #include <BLEUtils.h> #include <BLEServer.h>
Las dos primeras includes son precisos cuando vayas a usar BLE con tu ESP32 y el tercero (al igual que con la WIFI), se requiere para montar un servidor BLE. Para arrancar un servidor BLE necesitamos levantar al menos un Servicio y una Característica, cada uno con su propio UUID.
Lo siguiente es definir los UUIDs del Servicio y características por lo que conviene usar el generador aleatorio de claves que vimos en la última sesión Le he pedido que me genere dos claves UUID (Con el generador versión 4) y me ha salido esto:
40bc3f4d-8b63-4edc-a799-f3fd6a1e4417 cf98f21a-2e5b-4341-83ee-e22b6bef0e52
Así que las siguientes líneas van a ser:
#define SERVICE_UUID "40bc3f4d-8b63-4edc-a799-f3fd6a1e4417" #define CHARACTERISTIC_UUID "cf98f21a-2e5b-4341-83ee-e22b6bef0e52"
Donde asignamos estos IDs a nuestros valores y así evitamos tener todos el mismo ID una y otra vez. Vamos con las características básicas del servidor BLE:
BLEDevice::init("Prometec"); BLEServer *pServer = BLEDevice::createServer(); BLEService *pService = pServer->createService(SERVICE_UUID); BLECharacteristic *pCharacteristic = pService->createCharacteristic ( CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE );
He identificado en la primera línea, que publique el servicio con el nombre “Prometec”, pero Aquí puedes poner lo que se te ocurra. La segunda línea crea una instancia de un servidor BLE, y la tercera crea un servicio con el UUID que definimos arriba, y en la 4ª línea, creamos un servicio asociado al servicio pService que acabamos de definir.
Asociamos a este pService, una característica (Con el segundo UUID que hemos creado) y dos propiedades ( READ + WRITE) para que podamos modificar los valores. Lo siguiente es dar valor a esas características y arrancar el servicio en el servidor BLE:
pCharacteristic->setValue("Pues, aqui. Probando el BLE, oyes"); pService->start();
Ahora toca arrancar el advertising (Algo así como encender el cartel de abierto al público) y asignar el servicio al cartel de neon para que se vea
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID);
El resto es gimnasia:
pAdvertising->setScanResponse(true); pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue pAdvertising->setMinPreferred(0x12); BLEDevice::startAdvertising(); Serial.println("Characteristic defined! Now you can read it in your phone!");
La primera inicia la búsqueda de respuestas a nuestro anuncio (Clientes interesados) y como podéis ver los iPhone siempre dando la nota con sus manías. La línea de start es la que físicamente inicia el advertising.
Si ahora usas el nRF Connect veras que las servicios y características usan los UUIDs que les hemos proporcionado:
Mejorando un poco el programa
Como la explicación del programa servidor ha quedado un poco corta (Y me pagan por páginas) podemos hacer alguna modificación que nos resulte mínimamente útil, porque en el ejemplo de arriba, no hemos puesto nada en el loop, sino que todo va en el setup. Cuando queramos publicar un valor de un sensor, por ejemplo, asignándole a las propiedades del servidor, nos vamos a encontrar con que los punteros al servidor servidor BLE no son de acceso público en nuestro programa.
Por eso, y porque el tema de los punteros suelen ser el coco de los estudiantes de C++, vamos a hacer una pequeña modificación, que publique un número aleatorio en las características de nuestro servidor BLE, remedando la idea de un valor que cambia, como la lectura de un sensor, pongamos por caso.
Para ello hay que reescribir este programa de forma que nos permita acceder al servidor desde cualquier punto y de ese modo conseguir que todo esto nos sirva como plantilla para cualquier proyecto bluetooth BLE que podáis necesitar en un momento dado. Vamos con los include:
#include <BLEDevice.h> #include <BLEUtils.h> #include <BLEServer.h> #include <stdio.h> char buffer[32];
Lo de arriba es repetir los include del ejemplo anterior, pero he añadido el stdio.h, porque vamos a publicar un texto más un valor en valor de las características y para eso necesito la función sprinf() y un buffer de caracteres donde escribir el texto.
#define SERVICE_UUID "40bc3f4d-8b63-4edc-a799-f3fd6a1e4417" #define CHARACTERISTIC_UUID "cf98f21a-2e5b-4341-83ee-e22b6bef0e52”
BLEServer *pServer ; BLEService *pService; BLECharacteristic *pCharacteristic ;
Definimos los mismos UUIDs de antes y después, vamos a declarar el servicio y la característica de nuestro servidor BLE como punteros globales (Lo sé, es una chapuza, pero sencilla) para que podamos acceder a ellos desde nuestras funciones fuera del setup, que se reduce un poco y pasa a ser así:
void setup() { Serial.begin(115200); Serial.println("Starting BLE work!"); BLEDevice::init("Prometec"); pServer = BLEDevice::createServer(); pService = pServer->createService(SERVICE_UUID); pCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE ); pService->start(); BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID); pAdvertising->setScanResponse(true); pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue pAdvertising->setMinPreferred(0x12); BLEDevice::startAdvertising(); Serial.println("Characteristic defined! Now you can read it in your phone!"); }
El cambio solo ha sido que como hemos declarado arriba pServer, pService y pCharacteristic (Sin asignarlas) ahora podemos usarlas en cualquier sitio del programa y las asignamos dentro del setup().
El loop va a ser bastante sencillo:
void loop() { int r = random(0,1000) * 6 ; sprintf(buffer, "Prometec: %d",r); Serial.println(buffer); pCharacteristic->setValue(buffer); delay(2000); }
Calculamos los valores con la cadena “Prometec: ” más un número aleatorio entre 0 y 6.000, (Que calculamos cada dos segundos) y s elo asignamos a la pCharacteristic del servidor y para eso necesitaba el sprintf(). Como veras, puedes usar este método como de uso general, para llenar los valores que publica el servidor BLE tanto para Textos como para números, en el caso de lecturas de sensores y similares.
Aquí va el programa completo: BLE_server_2