Como se comunicar com dispositivos Bluetooth por JavaScript

A API Web Bluetooth permite que sites se comuniquem com dispositivos Bluetooth.

François Beaufort
François Beaufort

E se eu disser que sites podem se comunicar com dispositivos Bluetooth por perto? de forma segura e preservando a privacidade? Dessa forma, os monitores de frequência cardíaca, cantando lâmpadas e até tartarugas poderiam interagir diretamente com um site.

Até agora, a capacidade de interagir com dispositivos Bluetooth era possível apenas para aplicativos específicos da plataforma. O objetivo da API Web Bluetooth é mudar isso e também o leva para navegadores da Web.

Antes de começar

Este documento pressupõe que você tenha algum conhecimento básico sobre o uso do Bluetooth Low Energia (BLE) e o Perfil de atributo genérico funcionam.

Embora a especificação da API Web Bluetooth ainda não esteja finalizada, ela os autores estão procurando ativamente desenvolvedores interessados para testar essa API e envie feedback sobre as especificações e sobre a implementação.

Um subconjunto da API Web Bluetooth está disponível no ChromeOS e no Chrome para Android 6.0, Mac (Chrome 56) e Windows 10 (Chrome 70). Isso significa que você deve ser capaz para solicitar e se conectar a dispositivos Bluetooth de baixa energia perto de você, ler/gravar características do Bluetooth, receber notificações GATT, saber quando um dispositivo Bluetooth é desconectado, e até mesmo ler e gravar no Descritores Bluetooth. Consulte a tabela Compatibilidade do navegador da MDN para saber mais informações imprecisas ou inadequadas.

Para Linux e versões anteriores do Windows, ative a Sinalização #experimental-web-platform-features em about://flags.

Disponível para testes de origem

Para obter o máximo de feedback possível de desenvolvedores que usam a Web API Bluetooth em campo, o Chrome já adicionou esse recurso ao Chrome 53 como um teste de origem para ChromeOS, Android e Mac.

O teste terminou em janeiro de 2017.

Requisitos de segurança

Para entender as desvantagens de segurança, recomendo o curso Web Bluetooth Security Model feita por Jeffrey Yasskin, engenheiro de software da equipe do Chrome, trabalhando com a especificação da API Web Bluetooth.

Somente HTTPS

Como essa API experimental é um novo e poderoso recurso adicionado à Web, ela é disponibilizado apenas para contextos seguros. Isso significa que você precisará criar com TLS em mente.

Gesto do usuário necessário

Como um recurso de segurança, descobrir dispositivos Bluetooth com navigator.bluetooth.requestDevice precisa ser acionado por um gesto do usuário, como como um toque ou um clique do mouse. Estamos falando sobre ouvir pointerup, click e touchend.

button.addEventListener('pointerup', function(event) {
  // Call navigator.bluetooth.requestDevice
});

Entrar no código

A API Web Bluetooth depende muito de promessas JavaScript. Se você não estiver se familiarizar com eles, confira este ótimo tutorial de promessas. Mais uma coisa, () => {} são funções de seta do ECMAScript 2015.

Solicitar dispositivos Bluetooth

Essa versão da especificação da API Web Bluetooth permite que sites, em execução no o papel Central, para se conectar aos servidores GATT remotos por uma conexão BLE. Ela suporta a comunicação entre dispositivos que implementam Bluetooth 4.0 ou posterior.

Quando um site solicita acesso a dispositivos próximos usando navigator.bluetooth.requestDevice, o navegador solicita ao usuário um dispositivo onde poderá escolher um dispositivo ou cancelar a solicitação.

Solicitação do usuário de dispositivo Bluetooth.

A função navigator.bluetooth.requestDevice() usa um objeto obrigatório que define filtros. Esses filtros são usados para retornar somente dispositivos que correspondem a alguns os serviços GATT anunciados e/ou o nome do dispositivo.

Filtro de serviços

Por exemplo, para solicitar dispositivos Bluetooth que anunciam o Bluetooth GATT Serviço da bateria:

navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => { /* … */ })
.catch(error => { console.error(error); });

Se o seu Bluetooth GATT Service não estiver na lista de dispositivos Bluetooth padronizados serviços GATT, é possível fornecer o UUID completo do Bluetooth ou um Formato de 16 ou 32 bits.

navigator.bluetooth.requestDevice({
  filters: [{
    services: [0x1234, 0x12345678, '99999999-0000-1000-8000-00805f9b34fb']
  }]
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

Filtro de nome

Você também pode solicitar dispositivos Bluetooth com base no nome do dispositivo anunciado usando a chave de filtros name ou até mesmo um prefixo desse nome com a namePrefix chave de filtros. Nesse caso, você também vai precisar definir o optionalServices para acessar qualquer serviço não incluído em uma filtro de serviço. Caso contrário, você vai receber uma mensagem de erro mais tarde ao tentar acessar para resolvê-los com rapidez.

navigator.bluetooth.requestDevice({
  filters: [{
    name: 'Francois robot'
  }],
  optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

Filtro de dados do fabricante

Também é possível solicitar dispositivos Bluetooth com base no fabricante dados específicos anunciados com a chave de filtros manufacturerData. Esta chave é uma matriz de objetos com uma chave de identificador de empresa Bluetooth obrigatória chamada companyIdentifier Você também pode fornecer um prefixo de dados que filtra dados do fabricante de dispositivos Bluetooth que começam com ele. Observe que você também precisa definir a chave optionalServices para poder acessar qualquer serviço não incluídos em um filtro de serviço. Caso contrário, você receberá um erro mais tarde, quando tentando acessá-las.

// 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); });

Uma máscara também pode ser usada com um prefixo de dados para corresponder a alguns padrões em dados do fabricante. Confira a explicação sobre filtros de dados Bluetooth para saber mais mais.

Filtros de exclusão

A opção exclusionFilters em navigator.bluetooth.requestDevice() permite exclua alguns dispositivos do seletor de navegador. Ele pode ser usado para excluir dispositivos que correspondem a um filtro mais amplo, mas não são compatíveis.

// 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); });

Sem filtros

Por fim, em vez de filters, é possível usar a tecla acceptAllDevices para mostrar todos dispositivos Bluetooth por perto. Você também vai precisar definir o optionalServices para acessar alguns serviços. Caso contrário, uma mensagem de erro será exibida. ao tentar acessá-las.

navigator.bluetooth.requestDevice({
  acceptAllDevices: true,
  optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

Conectar-se a um dispositivo Bluetooth

O que fazer agora que você tem um BluetoothDevice? Vamos nos conectar Servidor GATT remoto Bluetooth que mantém o serviço e as características e definições.

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); });

Ler uma característica do Bluetooth

Aqui, conectamos ao servidor GATT do dispositivo Bluetooth remoto. Agora vamos desejar obter um serviço GATT primário e ler uma característica que pertence ao esse serviço. Vamos tentar, por exemplo, ler o nível de carga atual do da bateria do dispositivo.

No exemplo a seguir, battery_level é o nível de bateria padronizado Característica.

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); });

Se você usa uma característica GATT personalizada do Bluetooth, pode fornecer o um UUID Bluetooth completo ou uma forma curta de 16 ou 32 bits para service.getCharacteristic:

Também é possível adicionar um listener de eventos characteristicvaluechanged em um para processar a leitura do valor dele. Confira a Característica de leitura Exemplo de valor alterado para ver como lidar opcionalmente com os próximos GATTs as notificações.


.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);
}

Gravar em uma característica do Bluetooth

Gravar em uma característica do Bluetooth GATT é tão fácil quanto ler. Desta vez, usaremos o Ponto de Controle de Frequência Cardíaca para redefinir o valor de Energia gasta como 0 em um dispositivo de monitoramento de frequência cardíaca.

Prometo não ter mágica aqui. Tudo isso está explicado no tópico Controle de frequência cardíaca página "Característica do ponto".

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); });

Receber notificações GATT

Agora, vejamos como ser notificados quando a Medição de frequência cardíaca mudanças características no 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
}

O Exemplo de notificações mostra como parar as notificações com stopNotifications() e remova corretamente o characteristicvaluechanged adicionado. listener de eventos.

Desconectar-se de um dispositivo Bluetooth

Para oferecer uma melhor experiência ao usuário, você pode detectar eventos de desconexão e convide o usuário a se reconectar:

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.`);
}

Você também pode chamar device.gatt.disconnect() para desconectar seu app da Web da dispositivo Bluetooth. Isso vai acionar o evento gattserverdisconnected atual os listeners. Observe que ele NÃO interromperá a comunicação do dispositivo Bluetooth se outro app já está se comunicando com o dispositivo Bluetooth. Acesse a página Dispositivos Desconectar o exemplo e Exemplo de reconexão automática para saber mais.

Ler e gravar descritores Bluetooth

Os descritores Bluetooth GATT são atributos que descrevem um valor de característica. Eles podem ser lidos e gravados de maneira semelhante ao Bluetooth GATT e as características determinantes.

Vamos ver, por exemplo, como ler a descrição do usuário da medição do termômetro de integridade do dispositivo.

No exemplo abaixo, health_thermometer é o serviço Termômetro de saúde. measurement_interval, a característica do intervalo de medição, e gatt.characteristic_user_description a Descrição característica do usuário descritor.

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); });

Agora que lemos a descrição do usuário sobre o intervalo de medição do termômetro de integridade do dispositivo, vamos aprender a atualizá-lo e criar um .

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); });

Amostras, demonstrações e codelabs

Todos os exemplos de Bluetooth da Web abaixo foram testados. Para aproveitar o máximo possível, recomendamos que você instale o [BLE Peripheral Simulator, app Android] que simula um periférico de BLE com um serviço de bateria, uma frequência cardíaca serviço ou um serviço de termômetro de saúde.

Iniciante

Combinar várias operações

Confira também nossas demonstrações selecionadas do Web Bluetooth e os codelabs oficiais do Web Bluetooth.

Bibliotecas

  • O web-bluetooth-utils é um módulo npm que adiciona algumas funções de conveniência ao a API.
  • Um paliativo da API Web Bluetooth está disponível no noble, o BLE mais usado do Node.js. no módulo central. Isso permite usar o webpack/browserify noble sem a necessidade de para um servidor WebSocket ou outros plug-ins.
  • O angular-web-bluetooth é um módulo para o Angular que abstrai todos os necessário para configurar a API Web Bluetooth.

Ferramentas

  • O Get Started with Web Bluetooth é um app da Web simples que gera todos código boilerplate de JavaScript para começar a interagir com um dispositivo Bluetooth. Digite um nome de dispositivo, um serviço, uma característica, defina as propriedades e está tudo pronto.
  • Se você já é um desenvolvedor Bluetooth, o Web Bluetooth Developer Studio Plug-in também gera o código JavaScript do Web Bluetooth para o seu dispositivo Bluetooth.

Dicas

Uma página Bluetooth Internals está disponível no Chrome em about://bluetooth-internals para inspecionar tudo nas proximidades Dispositivos Bluetooth: status, serviços, características e descritores.

Captura de tela da página interna para depurar o Bluetooth no Chrome
Página interna no Chrome sobre depuração de dispositivos Bluetooth.

Também recomendo consultar o guia oficial Como registrar bugs do Web Bluetooth (em inglês). porque a depuração do Bluetooth pode ser difícil.

A seguir

Verifique o status de implementação do navegador e da plataforma primeiro para saber quais partes da API Web Bluetooth estão em fase de implementação.

Embora ele ainda não esteja completo, aqui está uma prévia do que esperar futuro:

  • Procura de anúncios BLE nas proximidades vai acontecer com navigator.bluetooth.requestLEScan().
  • Um novo evento serviceadded vai rastrear serviços GATT recém-descobertos do Bluetooth enquanto o evento serviceremoved vai acompanhar os removidos. Um novo dispositivo servicechanged será acionado quando uma característica e/ou descritor for adicionado ou removido de um serviço Bluetooth GATT.

Mostrar suporte à API

Você pretende usar a API Web Bluetooth? Seu apoio público ajuda a equipe do Chrome priorizar recursos e mostrar a outros fornecedores de navegador como é fundamental oferecer suporte a eles.

Envie um tweet para @ChromiumDev usando a hashtag #WebBluetooth e informe onde e como você o utiliza.

Recursos

Agradecimentos

Agradecemos a Kayce Basques por revisar este artigo. Imagem principal da SparkFun Electronics de Boulder, EUA.