Es normal que hoy necesitemos hacer una App conectada a algun dispositivo (ya sea propio o no) mediante bluetooth para determinadas operaciones, y el mundo moderno nos ofrece un protocolo conocido como Bluetooth Low Energy, el cual era una novedad hace unos años y hoy es un standard. La principal ventaja de esta conexión es el ahorro de energía en el dispositivo que lo emite, pudiendo alimentarlo con una pila de reloj durante mucho tiempo; pero al momento de conectarse la falta de información hace que a veces existan problemas al respecto. En nuestros desarrollos de Codize nos ha tocado enfrentarnos a varios desafíos, y debido al aprendizaje (y a lo poco que encontramos en la red al respecto) hoy compartimos errores comunes al usar Capacitor (ya sea Angular o Ionic) y BLE. Advertencia: todo esto fue probado en Android, hay diferencias en como llegan los paquetes e incluso en el objeto que se recibe en iOS por lo tanto los ejemplos deberían adaptarse.
¿Cómo me conecto?
Para conectarse a un dispositivo este debe estar encendido, emitiendo bluetooth y estar cerca del dispositivo con la App (como un teléfono). De más está decir que el bluetooth en el teléfono debe estar activado. Se necesitan, en principio, 3 datos (que pueden estar hardcodeados pero no es recomendable): ID del Dispositivo BLE, UUID del Servicio y UUID de la Característica. La forma más cómoda en principio es iniciando el proceso de escaneo:
this.ble.scan([], 5).subscribe(devices => {
console.log(devices);
}, error => {
alert(error);
},
() => {
console.log('Scan Finished');
});
Este código es sencillo, el número 5 indica la cantidad de dispositivos a escanear (recomiendo mantenerla en un número bajo ya que hay que estar relativamente cerca del dispositivo BLE) y nos va a devolver una lista de dispositivos con varios datos, los importantes son el nombre y el ID. El nombre nos sirve para identificarlo, el fabricante por lo general va a dejar un nombre en el hardware que va a ser claro; pero el ID es lo que deberemos guardar para conectarnos posteriormente. Con el ID podremos generar un connect:
this.ble.connect(device.id).subscribe(periphericalData => {
console.log(periphericalData);
},
periphericalData => {
console.log('Disconnect');
});
Esta conexión permite el emparejamiento parcial entre la App y el dispositivo, si nos devuelve un objeto con la información del mismo quiere decir que nos conectamos (la mayoría de los dispositivos BLE tendrán una forma de indicar eso tambien, ya sea mediante pantallas o LED). En este caso el objeto periphericalData nos va a traer la información de las características, con su UUID del servicio y de la característica (por eso recomiendo no hardcodearlo, ya que asi el sistema queda dinámico). Dentro del objecto periphericalData.characteristics habrá uno (o debería haber uno) que tenga una properties llamada "Notify" (un string). Esa propiedad será a la que nos conectaremos para iniciar el proceso de notificaciones desde el BLE hacia la App. Para eso empleamos la siguiente función:
this.ble.startNotification(id, uuid, char).subscribe(buffer => {});
En este caso id es el ID del dispositivo, uuid es el del Servicio y char el de la Característica (estos últimos deben ser los referentes a Notify, sino dará error). Dentro de la suscripción nos traerá un objeto (llamado buffer en este caso) que es un Array de 8 Bits, la forma clásica de decodificarlo es la siguiente:
String.fromCharCode.apply(null, new Uint8Array(buffer[0])
Aclaro en este caso que es buffer[0] y no buffer ya que en versiones actuales de la librería BLE el objeto llega dentro de otro objeto, y si se lo decodifica sin el índice nos generará un objeto vacío.
Llegan datos pero se cortan a los 20 carácteres
Este es un error propio del BLE. Por defecto el sistema solo manda paquetes de 20 bytes, por lo tanto si nuestra notificación es mayor a eso (generalmente se traduce como 20 carácteres) se verá cortada. En muchos lugares se recomienda arreglarlo desde el hardware, detectando cuando el paquete es mayor y mandando entonces paquetes de 20 en 20 con un sleep de 100 ms; pero no siempre tenemos acceso al hardware. Por suerte puede resolverse en la mayoría de los casos desde el código de la aplicación modificando el tamaño del MTU. Para eso, luego de finalizada la suscripción del connect (es decir, que ya nos conectamos al dispositivo y está emparejado) pero antes de disparar el startNotification, hacemos lo siguiente:
this.ble.requestMtu(device.id, 512).then(() => {});
En este caso no es una suscripción sino una Promise. Recibe dos parámetros, el ID del dispositivo y el tamaño nuevo del MTU, que como máximo puede ser 512 bytes (más que eso da problemas de compatibilidad, asi que ahi si que solo se puede hacer mandando multi-paquetes). Luego dentro del then() podemos llamar a startNotification y la data empezará a llegar completa.