La API de Web Bluetooth permite que los sitios web se comuniquen con dispositivos Bluetooth.
¿Qué pasaría si te dijera que los sitios web podrían comunicarse con dispositivos Bluetooth cercanos de una manera segura y que preserva la privacidad? De esta manera, los monitores de frecuencia cardíaca, las bombillas que cantan y hasta las tortugas podrían interactuar directamente con un sitio web.
Hasta ahora, la capacidad de interactuar con dispositivos Bluetooth solo era posible para las apps específicas de la plataforma. La API de Web Bluetooth tiene como objetivo cambiar esto y también llevarlo a los navegadores web.
Antes de comenzar
En este documento, se supone que tienes conocimientos básicos sobre el funcionamiento de Bluetooth de bajo consumo (BLE) y el Perfil de atributo genérico.
Si bien la especificación de la API de Web Bluetooth aún no está finalizada, los autores de la especificación buscan activamente desarrolladores entusiastas que prueben esta API y proporcionen comentarios sobre la especificación y comentarios sobre la implementación.
Un subconjunto de la API de Web Bluetooth está disponible en ChromeOS, Chrome para Android 6.0, Mac (Chrome 56) y Windows 10 (Chrome 70). Esto significa que deberías poder solicitar y conectarte a dispositivos Bluetooth de bajo consumo cercanos, leer o escribir características de Bluetooth, recibir notificaciones de GATT, saber cuándo se desconecta un dispositivo Bluetooth y hasta leer y escribir descriptores de Bluetooth. Consulta la tabla de compatibilidad de navegadores de MDN para obtener más información.
Para Linux y versiones anteriores de Windows, habilita la marca #experimental-web-platform-features en about://flags.
Disponible para pruebas de origen
Para obtener la mayor cantidad posible de comentarios de los desarrolladores que usan la API de Web Bluetooth en el campo, Chrome agregó esta función en Chrome 53 como una prueba de origen para ChromeOS, Android y Mac.
La prueba finalizó correctamente en enero de 2017.
Requisitos de seguridad
Para comprender las ventajas y desventajas de seguridad, te recomiendo la entrada Modelo de seguridad de Web Bluetooth de Jeffrey Yasskin, ingeniero de software del equipo de Chrome que trabaja en la especificación de la API de Web Bluetooth.
Solo HTTPS
Debido a que esta API experimental es una nueva y potente función que se agregó a la Web, solo está disponible para contextos seguros. Esto significa que deberás tener en cuenta TLS al momento de compilar.
Se requiere un gesto del usuario
Como función de seguridad, el descubrimiento de dispositivos Bluetooth con navigator.bluetooth.requestDevice debe activarse con un gesto del usuario, como un toque o un clic con el mouse. Hablamos de escuchar eventos de pointerup, click y touchend.
button.addEventListener('pointerup', function(event) {
// Call navigator.bluetooth.requestDevice
});
Accede al código
La API de Web Bluetooth depende en gran medida de las promesas de JavaScript. Si no los conoces, consulta este excelente instructivo sobre Promises. Una cosa más: () => {} son funciones de flecha de ECMAScript 2015.
Solicitar dispositivos Bluetooth
Esta versión de la especificación de la API de Web Bluetooth permite que los sitios web que se ejecutan en el rol de Central se conecten a servidores GATT remotos a través de una conexión BLE. Admite la comunicación entre dispositivos que implementan Bluetooth 4.0 o versiones posteriores.
Cuando un sitio web solicita acceso a dispositivos cercanos con navigator.bluetooth.requestDevice, el navegador le muestra al usuario un selector de dispositivos en el que puede elegir uno o cancelar la solicitud.
La función navigator.bluetooth.requestDevice() toma un objeto obligatorio que define filtros. Estos filtros se usan para devolver solo los dispositivos que coinciden con algunos servicios GATT de Bluetooth anunciados o con el nombre del dispositivo.
Filtro de servicios
Por ejemplo, para solicitar dispositivos Bluetooth que anuncien el servicio de batería GATT de Bluetooth, haz lo siguiente:
navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => { /* … */ })
.catch(error => { console.error(error); });
Sin embargo, si tu servicio GATT de Bluetooth no está en la lista de servicios GATT de Bluetooth estandarizados, puedes proporcionar el UUID de Bluetooth completo o una forma abreviada de 16 o 32 bits.
navigator.bluetooth.requestDevice({
filters: [{
services: [0x1234, 0x12345678, '99999999-0000-1000-8000-00805f9b34fb']
}]
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });
Filtro de nombre
También puedes solicitar dispositivos Bluetooth según el nombre del dispositivo que se anuncia con la clave de filtros name o incluso un prefijo de este nombre con la clave de filtros namePrefix. Ten en cuenta que, en este caso, también deberás definir la clave optionalServices para poder acceder a cualquier servicio que no esté incluido en un filtro de servicio. De lo contrario, recibirás un error más adelante cuando intentes acceder a ellos.
navigator.bluetooth.requestDevice({
filters: [{
name: 'Francois robot'
}],
optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });
Filtro de datos del fabricante
También es posible solicitar dispositivos Bluetooth según los datos específicos del fabricante que se anuncian con la clave de filtros manufacturerData. Esta clave es un array de objetos con una clave obligatoria Bluetooth company identifier llamada companyIdentifier. También puedes proporcionar un prefijo de datos que filtre los datos del fabricante de los dispositivos Bluetooth que comiencen con él. Ten en cuenta que también deberás definir la clave optionalServices para poder acceder a los servicios que no se incluyan en un filtro de servicios. De lo contrario, recibirás un error más adelante cuando intentes acceder a ellos.
// Filter Bluetooth devices from Google company with manufacturer data bytes
// that start with [0x01, 0x02].
navigator.bluetooth.requestDevice({
filters: [{
manufacturerData: [{
companyIdentifier: 0x00e0,
dataPrefix: new Uint8Array([0x01, 0x02])
}]
}],
optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });
También se puede usar una máscara con un prefijo de datos para que coincida con algunos patrones en los datos del fabricante. Consulta la explicación sobre los filtros de datos de Bluetooth para obtener más información.
Filtros de exclusión
La opción exclusionFilters en navigator.bluetooth.requestDevice() te permite excluir algunos dispositivos del selector de navegador. Se puede usar para excluir dispositivos que coinciden con un filtro más amplio, pero que no son compatibles.
// Request access to a bluetooth device whose name starts with "Created by".
// The device named "Created by Francois" has been reported as unsupported.
navigator.bluetooth.requestDevice({
filters: [{
namePrefix: "Created by"
}],
exclusionFilters: [{
name: "Created by Francois"
}],
optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });
No hay filtros
Por último, en lugar de filters, puedes usar la tecla acceptAllDevices para mostrar todos los dispositivos Bluetooth cercanos. También deberás definir la clave optionalServices para poder acceder a algunos servicios. De lo contrario, recibirás un error más adelante cuando intentes acceder a ellos.
navigator.bluetooth.requestDevice({
acceptAllDevices: true,
optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });
Conexión a un dispositivo Bluetooth
Entonces, ¿qué haces ahora que tienes un BluetoothDevice? Conectémonos al servidor GATT remoto de Bluetooth que contiene las definiciones de servicio y características.
navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => {
// Human-readable name of the device.
console.log(device.name);
// Attempts to connect to remote GATT Server.
return device.gatt.connect();
})
.then(server => { /* … */ })
.catch(error => { console.error(error); });
Cómo leer una característica de Bluetooth
Aquí nos conectamos al servidor GATT del dispositivo Bluetooth remoto. Ahora queremos obtener un servicio GATT principal y leer una característica que pertenece a este servicio. Por ejemplo, intentemos leer el nivel de carga actual de la batería del dispositivo.
En el ejemplo que se muestra a continuación, battery_level es la característica estandarizada de nivel de batería.
navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => device.gatt.connect())
.then(server => {
// Getting Battery Service…
return server.getPrimaryService('battery_service');
})
.then(service => {
// Getting Battery Level Characteristic…
return service.getCharacteristic('battery_level');
})
.then(characteristic => {
// Reading Battery Level…
return characteristic.readValue();
})
.then(value => {
console.log(`Battery percentage is ${value.getUint8(0)}`);
})
.catch(error => { console.error(error); });
Si usas una característica GATT de Bluetooth personalizada, puedes proporcionar el UUID de Bluetooth completo o una forma abreviada de 16 o 32 bits para service.getCharacteristic.
Ten en cuenta que también puedes agregar un objeto de escucha de eventos characteristicvaluechanged en una característica para controlar la lectura de su valor. Consulta el ejemplo de Lectura del valor de la característica cambiada para ver cómo controlar de forma opcional las próximas notificaciones de GATT también.
…
.then(characteristic => {
// Set up event listener for when characteristic value changes.
characteristic.addEventListener('characteristicvaluechanged',
handleBatteryLevelChanged);
// Reading Battery Level…
return characteristic.readValue();
})
.catch(error => { console.error(error); });
function handleBatteryLevelChanged(event) {
const batteryLevel = event.target.value.getUint8(0);
console.log('Battery percentage is ' + batteryLevel);
}
Cómo escribir en una característica de Bluetooth
Escribir en una característica GATT de Bluetooth es tan fácil como leerla. Esta vez, usemos el punto de control de frecuencia cardíaca para restablecer el valor del campo Energía consumida a 0 en un dispositivo de monitor de frecuencia cardíaca.
Te prometo que no hay magia aquí. Todo se explica en la página de características del punto de control de la frecuencia cardíaca.
navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('heart_rate'))
.then(service => service.getCharacteristic('heart_rate_control_point'))
.then(characteristic => {
// Writing 1 is the signal to reset energy expended.
const resetEnergyExpended = Uint8Array.of(1);
return characteristic.writeValue(resetEnergyExpended);
})
.then(_ => {
console.log('Energy expended has been reset.');
})
.catch(error => { console.error(error); });
Recibir notificaciones de GATT
Ahora, veamos cómo recibir notificaciones cuando cambia la característica Heart Rate Measurement en el dispositivo:
navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('heart_rate'))
.then(service => service.getCharacteristic('heart_rate_measurement'))
.then(characteristic => characteristic.startNotifications())
.then(characteristic => {
characteristic.addEventListener('characteristicvaluechanged',
handleCharacteristicValueChanged);
console.log('Notifications have been started.');
})
.catch(error => { console.error(error); });
function handleCharacteristicValueChanged(event) {
const value = event.target.value;
console.log('Received ' + value);
// TODO: Parse Heart Rate Measurement value.
// See https://github.com/WebBluetoothCG/demos/blob/gh-pages/heart-rate-sensor/heartRateSensor.js
}
El ejemplo de notificaciones te muestra cómo detener las notificaciones con stopNotifications() y quitar correctamente el objeto de escucha de eventos characteristicvaluechanged agregado.
Cómo desconectarse de un dispositivo Bluetooth
Para brindar una mejor experiencia del usuario, es posible que desees detectar eventos de desconexión e invitar al usuario a volver a conectarse:
navigator.bluetooth.requestDevice({ filters: [{ name: 'Francois robot' }] })
.then(device => {
// Set up event listener for when device gets disconnected.
device.addEventListener('gattserverdisconnected', onDisconnected);
// Attempts to connect to remote GATT Server.
return device.gatt.connect();
})
.then(server => { /* … */ })
.catch(error => { console.error(error); });
function onDisconnected(event) {
const device = event.target;
console.log(`Device ${device.name} is disconnected.`);
}
También puedes llamar a device.gatt.disconnect() para desconectar tu app web del dispositivo Bluetooth. Esto activará los objetos de escucha de eventos gattserverdisconnected existentes. Ten en cuenta que NO detendrá la comunicación del dispositivo Bluetooth si otra app ya se está comunicando con él. Consulta el ejemplo de desconexión del dispositivo y el ejemplo de reconexión automática para obtener más información.
Lectura y escritura en descriptores de Bluetooth
Los descriptores GATT de Bluetooth son atributos que describen un valor de característica. Puedes leerlos y escribirlos de forma similar a las características de GATT de Bluetooth.
Veamos, por ejemplo, cómo leer la descripción del usuario del intervalo de medición del termómetro de estado del dispositivo.
En el siguiente ejemplo, health_thermometer es el servicio de termómetro de salud, measurement_interval la característica Intervalo de medición y gatt.characteristic_user_description el descriptor de descripción de usuario de la característica.
navigator.bluetooth.requestDevice({ filters: [{ services: ['health_thermometer'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('health_thermometer'))
.then(service => service.getCharacteristic('measurement_interval'))
.then(characteristic => characteristic.getDescriptor('gatt.characteristic_user_description'))
.then(descriptor => descriptor.readValue())
.then(value => {
const decoder = new TextDecoder('utf-8');
console.log(`User Description: ${decoder.decode(value)}`);
})
.catch(error => { console.error(error); });
Ahora que leímos la descripción del usuario del intervalo de medición del termómetro de salud del dispositivo, veamos cómo actualizarlo y escribir un valor personalizado.
navigator.bluetooth.requestDevice({ filters: [{ services: ['health_thermometer'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('health_thermometer'))
.then(service => service.getCharacteristic('measurement_interval'))
.then(characteristic => characteristic.getDescriptor('gatt.characteristic_user_description'))
.then(descriptor => {
const encoder = new TextEncoder('utf-8');
const userDescription = encoder.encode('Defines the time between measurements.');
return descriptor.writeValue(userDescription);
})
.catch(error => { console.error(error); });
Muestras, demostraciones y codelabs
Todas las muestras de Web Bluetooth que se muestran a continuación se probaron correctamente. Para aprovechar al máximo estos ejemplos, te recomiendo que instales la [app para Android de BLE Peripheral Simulator], que simula un periférico BLE con un servicio de batería, un servicio de frecuencia cardíaca o un servicio de termómetro de salud.
Principiante
- Device Info: Recupera información básica del dispositivo desde un dispositivo BLE.
- Battery Level: Recupera información de la batería de un dispositivo BLE que anuncia información de la batería.
- Reset Energy: Restablece la energía consumida por un dispositivo BLE que anuncia la frecuencia cardíaca.
- Characteristic Properties: Muestra todas las propiedades de una característica específica de un dispositivo BLE.
- Notifications: Inicia y detiene las notificaciones de características de un dispositivo BLE.
- Device Disconnect: Desconecta un dispositivo BLE y recibe una notificación cuando se desconecte después de conectarse a él.
- Get Characteristics: Obtiene todas las características de un servicio anunciado desde un dispositivo BLE.
- Get Descriptors: Obtiene todos los descriptores de características de un servicio anunciado desde un dispositivo BLE.
- Filtro de datos del fabricante: Recupera información básica del dispositivo de un dispositivo BLE que coincide con los datos del fabricante.
- Filtros de exclusión: Recupera información básica del dispositivo de un dispositivo BLE que incluye filtros de exclusión básicos.
Combinación de varias operaciones
- GAP Characteristics: Obtiene todas las características de GAP de un dispositivo BLE.
- Device Information Characteristics: Obtén todas las características de información del dispositivo de un dispositivo BLE.
- Link Loss: Establece la característica de nivel de alerta de un dispositivo BLE (readValue y writeValue).
- Discover Services & Characteristics: Descubre todos los servicios principales accesibles y sus características desde un dispositivo BLE.
- Automatic Reconnect: Vuelve a conectar un dispositivo BLE desconectado con un algoritmo de retirada exponencial.
- Read Characteristic Value Changed: Lee el nivel de batería y recibe notificaciones de cambios desde un dispositivo BLE.
- Read Descriptors: Lee todos los descriptores de características de un servicio desde un dispositivo BLE.
- Write Descriptor: Escribe en el descriptor "Characteristic User Description" en un dispositivo BLE.
También puedes consultar nuestras demostraciones seleccionadas de Web Bluetooth y los codelabs oficiales de Web Bluetooth.
Bibliotecas
- web-bluetooth-utils es un módulo de npm que agrega algunas funciones convenientes a la API.
- Un shim de la API de Web Bluetooth está disponible en noble, el módulo central BLE de Node.js más popular. Esto te permite usar webpack/browserify noble sin necesidad de un servidor WebSocket ni otros complementos.
- angular-web-bluetooth es un módulo para Angular que abstrae todo el código repetitivo necesario para configurar la API de Web Bluetooth.
Herramientas
- Get Started with Web Bluetooth es una app web simple que generará todo el código boilerplate de JavaScript para comenzar a interactuar con un dispositivo Bluetooth. Ingresa un nombre de dispositivo, un servicio o una característica, define sus propiedades y listo.
- Si ya eres desarrollador de Bluetooth, el complemento Web Bluetooth Developer Studio también generará el código JavaScript de Web Bluetooth para tu dispositivo Bluetooth.
Sugerencias
En Chrome, está disponible una página de Bluetooth Internals en about://bluetooth-internals para que puedas inspeccionar todo lo relacionado con los dispositivos Bluetooth cercanos: estado, servicios, características y descriptores.
También te recomiendo que consultes la página oficial Cómo presentar errores de Web Bluetooth, ya que, a veces, depurar Bluetooth puede ser difícil.
¿Qué sigue?
Primero, consulta el estado de implementación del navegador y la plataforma para saber qué partes de la API de Web Bluetooth se están implementando actualmente.
Aunque aún está incompleta, aquí tienes un adelanto de lo que puedes esperar en el futuro cercano:
- La búsqueda de anuncios de BLE cercanos se realizará con
navigator.bluetooth.requestLEScan(). - Un nuevo evento
serviceaddedhará un seguimiento de los servicios GATT de Bluetooth recién descubiertos, mientras que el eventoserviceremovedhará un seguimiento de los que se quitaron. Se activará un nuevo eventoservicechangedcuando se agregue o quite cualquier característica o descriptor de un servicio GATT de Bluetooth.
Cómo mostrar compatibilidad con la API
¿Piensas usar la API de Web Bluetooth? Tu apoyo público ayuda al equipo de Chrome a priorizar funciones y muestra a otros proveedores de navegadores lo importante que es admitirlas.
Envía un tuit a @ChromiumDev con el hashtag #WebBluetooth y cuéntanos dónde y cómo lo usas.
Recursos
- Stack Overflow
- Estado de las funciones de Chrome
- Errores de implementación de Chrome
- Especificación de Web Bluetooth
- Problemas de especificaciones en GitHub
- App de BLE Peripheral Simulator
Agradecimientos
Gracias a Kayce Basques por la revisión.