Cliente Bluetooth BLE con ESP32
Material requerido.
Un ESP32 |
Programando un cliente BLE
Hemos visto en las últimas sesiones los conceptos básicos del modelo Bluetooth BLE y hasta hemos hecho pruebas creando un servidor BLE, que publica un valor variable, emulando un sensor cualquiera o sistema de publicación de valores.
después, hemos usado el nrf Connector para comprobar que realmente podíamos conectar a nuestro servidor BLE y hasta veíamos como esos valores publicados en la característica iban variando cada segundo a medida que variaba el valor de la característica. Pero lo interesante (Y mucho más útil), es conseguir hacer ahora un programa de cliente BLE, que nos permita procesar mediante el ESP32 esos valores que vayamos leyendo.
Para eso vamos a escribir un programa clienta BT basado en el ejemplo BLE_Client que viene con el ESP32 y ligeramente retocado. El ejemplo solo lee el valor publicado en la Característica una vez al conectarse y por eso vamos a retocarlo un poco para que haga la lectura cada n segundos, por ejemplo.
Entrando en los detalles del cliente BLE
La idea, es pues, escribir un programa que actuando como un cliente BLE, pueda leer los valores que publica servidor BLE en una característica determinada. Para ello necesitamos los UUID del servicio y la Característica que buscamos.
Podemos usar los UUIDs que pusimos en el programa servidor
4fafc201-1fb5-459e-8fcc-c5c9c331914b beb5483e-36e1-4688-b7f5-ea07361b26a8
Las primeras líneas como siempre
#include "BLEDevice.h" #include "BLEScan.h" // El servicio que nos interesa. static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b"); // La Caracteristica que buscamos. static BLEUUID charUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8");
Algunas variables instrumentales para saber si hemos conectado:
static boolean doConnect = false; static boolean connected = false; static boolean doScan = false;
Si pones la opción doScan a True, cuando el sistema arranque buscará dispositivos bluetooth en los alrededores y listará en la consola unos cuantos:
Y un par de punteros para usar más adelante que contengan la Característica a la que nos conectamos y el dispositivo que la ofrece:
static BLERemoteCharacteristic* pRemoteCharacteristic; static BLEAdvertisedDevice* myDevice;
Vamos a vincular un par de funciones Callback para tener un puntero a la conexión cuando conectemos y otra para saber que nos hemos desconectado cuando finalice la conexión.
class MyClientCallback : public BLEClientCallbacks { void onConnect(BLEClient* pclient) { } void onDisconnect(BLEClient* pclient) { connected = false; Serial.println("onDisconnect"); } };
Tenemos que escribir ahora una función que conecte al servidor para obtener los datos de las características. Sin entrar en muchos detalles para no decir tonterías, (Porque no tengo claro mas de una de estas líneas, pero parecen funcionar) Estas primeras líneas se encargan e la conexión al server externo:
bool connectToServer() { Serial.print("Forming a connection to "); Serial.println(myDevice->getAddress().toString().c_str()); BLEClient* pClient = BLEDevice::createClient(); Serial.println(" - Created client"); pClient->setClientCallbacks(new MyClientCallback()); // Connect to the remove BLE Server. pClient->connect(myDevice); // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private) Serial.println(" - Connected to server");
Una vez conectados, buscamos el Servicio que nos interesa (Con el UUID que proporcionamos arriba):
// Obtain a reference to the service we are after in the remote BLE server. BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
Comprobamos si hemos conectado con el servicio correcto. Comprobamos que el puntero no sea nulo, porque si lo es no seguimos, y nos volvemos devolviendo un valor False como resultado de la función.
if (pRemoteService == nullptr) { Serial.print("Failed to find our service UUID: "); Serial.println(serviceUUID.toString().c_str()); pClient->disconnect(); return false; } Serial.println(" - Found our service");
Si el puntero al servicio es válido, entonces podemos buscar la característica especificada del servicio (Con UUID correspondiente):
if (pRemoteCharacteristic == nullptr) { Serial.print("Failed to find our characteristic UUID: "); Serial.println(charUUID.toString().c_str()); pClient->disconnect(); return false; } Serial.println(" - Found our characteristic");
Si hemos llegado aquí, es que tenemos Servicio y Característica válidos, podemos proceder a leer la Característica:
if(pRemoteCharacteristic->canRead()) getValue(); // Funcion que lee el valor publicado
Aquí es donde retocamos el programa de ejemplo original, para que leamos la característica en una función adicional que sacamos fuera para poderla usar con comodidad llamada getValue()
if(pRemoteCharacteristic->canNotify()) pRemoteCharacteristic->registerForNotify(notifyCallback); connected = true; return true;
Si la característica acepta notificaciones como vimos en la sesión anterior, llamamos a notifyCallback para procesar las notificaciones, y por lo demás devolvemos un true para indicar el éxito en la conexión. La función getValue, simplemente, lee el valor de la característica y lanza un mensaje a la consola con el texto “Client “ más el texto del valor leído:
void getValue() { std::string value = pRemoteCharacteristic->readValue(); Serial.print("Client "); Serial.println(value.c_str()); }
Vamos al setup:
Serial.begin(115200); Serial.println("Starting Arduino BLE Client application..."); BLEDevice::init("");
Arrancamos la consola serie y sobre todo, iniciamos el dispositivo BLE. Fíjate que no le pasamos ningún nombre de dispositivo porque en principio el cliente no publica su nombre.
El siguiente grupo de instrucciones inicia la búsqueda de dispositivos Bluetooth disponibles y engancha nuestra función Callback para ejecutar cada vez que encontremos un nuevo device:
BLEScan* pBLEScan = BLEDevice::getScan(); pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
Fijamos ahora el intervalo y especificamos que queremos active scanning y que busque dispositivos durante 5 segundos (En la última línea) y no estoy muy seguro de que hacen los otros parámetros, pero mira, yo copio lo que mandan los ejemplos por la cuenta que me tiene
pBLEScan->setInterval(1349); pBLEScan->setWindow(499); pBLEScan->setActiveScan(true); pBLEScan->start(5, false);
Ya podemos pasar al loop() que resulta bastante simple:
void loop() { if (connected) getValue(); else if(doScan) BLEDevice::getScan()->start(0 }
Aquí os dejo el programa completo: BLE_client / BLE_server_2
Lo único que hacemos desde aquí es imprimir el valor que llenos de la característica de forma repetitiva (Lo que probablemente no tiene mucho sentido salvo que sean valores que cambien muy rápido) Pero la idea era que disponemos de una función getValue, que lee e imprime el valor cuando la invocamos con lo que ya podemos procesar el valor de la forma que nos resulta más interesante.
Aquí os dejo un mini vídeo con el resultado