1. Sobre o que é?

Neste codelab, você vai aprender a controlar uma vela de LED sem chama PLAYBULB usando apenas JavaScript, graças à API Web Bluetooth. Ao longo do caminho, você também vai usar recursos do JavaScript ES2015, como Classes, Arrow functions, Map e Promises.
O que você vai aprender
- Como interagir com um dispositivo Bluetooth por perto em JavaScript
- Como usar classes ES2015, funções de seta, mapas e promessas
O que é necessário
- Noções básicas de desenvolvimento web
- Conhecimento básico de Bluetooth de baixa energia (BLE) e perfil de atributo genérico (GATT)
- Um editor de texto de sua escolha
- Um Mac, um Chromebook ou um dispositivo Android M com o app navegador Chrome e um cabo micro USB para USB.
2. Assistir primeiro
Confira a versão final do app que você vai criar em https://googlecodelabs.github.io/candle-bluetooth e teste o dispositivo Bluetooth PLAYBULB Candle que você tem à disposição antes de começar este codelab.
Você também pode me ver mudando de cor em https://www.youtube.com/watch?v=fBCPA9gIxlU
3. Começar a configuração
Faça o download do exemplo de código
Para acessar o exemplo de código, faça o download do arquivo ZIP aqui:
ou clonando este repositório git:
git clone https://github.com/googlecodelabs/candle-bluetooth.git
Se você fez o download da origem como um arquivo ZIP, descompacte-o para acessar uma pasta raiz candle-bluetooth-master.
Instalar e verificar o servidor da Web
Embora você possa usar seu próprio servidor da Web, este codelab foi projetado para funcionar bem com o Chrome Web Server. Se você ainda não tiver o app instalado, poderá instalá-lo pela Chrome Web Store.
Depois de instalar o app Web Server for Chrome, clique no atalho "Apps" na barra de favoritos:

Na janela que aparecer, clique no ícone do servidor da Web:

Você verá esta caixa de diálogo, que permite configurar seu servidor da Web local:

Clique no botão choose folder e selecione a raiz do repositório clonado (ou desarquivado). Isso permite que seu trabalho seja detalhado pelo URL destacado na caixa de diálogo do servidor da Web (na seção URLs do servidor da Web).
Em "Opções", marque a caixa ao lado de Mostrar automaticamente index.html, conforme mostrado abaixo:

Agora, acesse o site no navegador da Web (clicando no URL do servidor da Web destacado). Você verá uma página como esta:

Se você quiser ver como esse app fica no seu smartphone Android, ative a depuração remota no Android e configure o encaminhamento de portas (o número da porta por padrão é 8887). Depois disso, basta abrir uma nova guia do Chrome em http://localhost:8887 no smartphone Android.
A seguir
No momento, esse app da Web não faz muita coisa. Vamos começar a adicionar suporte ao Bluetooth.
4. Descubra a vela
Vamos começar escrevendo uma biblioteca que usa uma classe JavaScript ES2015 para o dispositivo Bluetooth PLAYBULB Candle.
Mantenha a calma. A sintaxe de classe não introduz um novo modelo de herança orientada a objetos no JavaScript. Ele simplesmente fornece uma sintaxe muito mais clara para criar objetos e lidar com herança, como você pode ler abaixo.
Primeiro, vamos definir uma classe PlaybulbCandle em playbulbCandle.js e criar uma instância playbulbCandle que vai estar disponível no arquivo app.js mais tarde.
playbulbCandle.js
(function() {
'use strict';
class PlaybulbCandle {
constructor() {
this.device = null;
}
}
window.playbulbCandle = new PlaybulbCandle();
})();
Para solicitar acesso a um dispositivo Bluetooth por perto, precisamos chamar navigator.bluetooth.requestDevice. Como o dispositivo PLAYBULB Candle anuncia continuamente (se ainda não estiver pareado) um UUID de serviço GATT do Bluetooth constante conhecido em sua forma abreviada como 0xFF02, podemos simplesmente definir uma constante e adicioná-la ao parâmetro de serviços de filtros em um novo método público connect da classe PlaybulbCandle.
Também vamos acompanhar internamente o objeto BluetoothDevice para que possamos acessá-lo mais tarde, se necessário. Como navigator.bluetooth.requestDevice retorna uma promessa do JavaScript ES2015, vamos fazer isso no método then.
playbulbCandle.js
(function() {
'use strict';
const CANDLE_SERVICE_UUID = 0xFF02;
class PlaybulbCandle {
constructor() {
this.device = null;
}
connect() {
let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}]};
return navigator.bluetooth.requestDevice(options)
.then(function(device) {
this.device = device;
}.bind(this));
}
}
window.playbulbCandle = new PlaybulbCandle();
})();
Como recurso de segurança, a descoberta de dispositivos Bluetooth por perto com navigator.bluetooth.requestDevice precisa ser chamada por uma interação inicial, como um toque ou clique do mouse. Por isso, vamos chamar o método connect quando o usuário clicar no botão "Connect" no arquivo app.js:
app.js
document.querySelector('#connect').addEventListener('click', function(event) {
document.querySelector('#state').classList.add('connecting');
playbulbCandle.connect()
.then(function() {
console.log(playbulbCandle.device);
document.querySelector('#state').classList.remove('connecting');
document.querySelector('#state').classList.add('connected');
})
.catch(function(error) {
console.error('Argh!', error);
});
});
Executar o app
Agora, acesse o site no navegador da Web (clicando no URL do servidor da Web destacado no app do servidor da Web) ou simplesmente atualize a página atual. Clique no botão verde "Connect", escolha o dispositivo no seletor e abra seu console favorito do DevTools com o atalho de teclado Ctrl + Shift + J. Observe o objeto BluetoothDevice registrado.

Se o Bluetooth e/ou o dispositivo Bluetooth PLAYBULB Candle estiverem desativados, talvez você receba um erro. Nesse caso, ative-os e tente novamente.
Bônus obrigatório
Não sei você, mas eu já vejo muitos function() {} neste código. Vamos mudar para as funções de seta do JavaScript ES2015 () => {}. Elas são uma verdadeira salvação: toda a beleza das funções anônimas, sem a tristeza da vinculação.
playbulbCandle.js
(function() {
'use strict';
const CANDLE_SERVICE_UUID = 0xFF02;
class PlaybulbCandle {
constructor() {
this.device = null;
}
connect() {
let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}]};
return navigator.bluetooth.requestDevice(options)
.then(device => {
this.device = device;
});
}
}
window.playbulbCandle = new PlaybulbCandle();
})();
app.js
document.querySelector('#connect').addEventListener('click', event => {
playbulbCandle.connect()
.then(() => {
console.log(playbulbCandle.device);
document.querySelector('#state').classList.remove('connecting');
document.querySelector('#state').classList.add('connected');
})
.catch(error => {
console.error('Argh!', error);
});
});
A seguir
- OK... posso falar com essa vela ou não?
- Claro... pular para a próxima etapa
Perguntas frequentes
5. Ler algo
Então, o que fazer agora que você tem um BluetoothDevice retornado da promessa de navigator.bluetooth.requestDevice? Vamos nos conectar ao servidor GATT remoto do Bluetooth que contém as definições de serviço e característica do Bluetooth chamando device.gatt.connect():
playbulbCandle.js
class PlaybulbCandle {
constructor() {
this.device = null;
}
connect() {
let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}]};
return navigator.bluetooth.requestDevice(options)
.then(device => {
this.device = device;
return device.gatt.connect();
});
}
}
Ler o nome do dispositivo
Aqui, estamos conectados ao servidor GATT do dispositivo Bluetooth PLAYBULB Candle. Agora, queremos acessar o serviço GATT principal (anunciado como 0xFF02 anteriormente) e ler a característica do nome do dispositivo (0xFFFF) que pertence a esse serviço. Para isso, basta adicionar um novo método getDeviceName à classe PlaybulbCandle e usar device.gatt.getPrimaryService e service.getCharacteristic. O método characteristic.readValue vai retornar um DataView que vamos decodificar com TextDecoder.
playbulbCandle.js
const CANDLE_DEVICE_NAME_UUID = 0xFFFF;
...
getDeviceName() {
return this.device.gatt.getPrimaryService(CANDLE_SERVICE_UUID)
.then(service => service.getCharacteristic(CANDLE_DEVICE_NAME_UUID))
.then(characteristic => characteristic.readValue())
.then(data => {
let decoder = new TextDecoder('utf-8');
return decoder.decode(data);
});
}
Vamos adicionar isso ao app.js chamando playbulbCandle.getDeviceName quando estivermos conectados e mostrar o nome do dispositivo.
app.js
document.querySelector('#connect').addEventListener('click', event => {
playbulbCandle.connect()
.then(() => {
console.log(playbulbCandle.device);
document.querySelector('#state').classList.remove('connecting');
document.querySelector('#state').classList.add('connected');
return playbulbCandle.getDeviceName().then(handleDeviceName);
})
.catch(error => {
console.error('Argh!', error);
});
});
function handleDeviceName(deviceName) {
document.querySelector('#deviceName').value = deviceName;
}
Agora, acesse o site no navegador da Web (clicando no URL do servidor da Web destacado no app do servidor da Web) ou simplesmente atualize a página atual. Verifique se a PLAYBULB Candle está ligada, clique no botão "Conectar" na página e o nome do dispositivo vai aparecer abaixo do seletor de cores.

Ler o nível de bateria
Há também uma característica padrão de nível de bateria do Bluetooth disponível no dispositivo Bluetooth PLAYBULB Candle que contém o nível de bateria do dispositivo. Isso significa que podemos usar nomes padrão, como battery_service para o UUID do serviço GATT do Bluetooth e battery_level para o UUID da característica GATT do Bluetooth.
Vamos adicionar um novo método getBatteryLevel à classe PlaybulbCandle e ler o nível de bateria em porcentagem.
playbulbCandle.js
getBatteryLevel() {
return this.device.gatt.getPrimaryService('battery_service')
.then(service => service.getCharacteristic('battery_level'))
.then(characteristic => characteristic.readValue())
.then(data => data.getUint8(0));
}
Também precisamos atualizar o objeto JavaScript options para incluir o serviço de bateria na chave optionalServices, já que ele não é anunciado pelo dispositivo Bluetooth PLAYBULB Candle, mas ainda é obrigatório para acessá-lo.
playbulbCandle.js
let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}],
optionalServices: ['battery_service']};
return navigator.bluetooth.requestDevice(options)
Como antes, vamos conectar isso ao app.js chamando playbulbCandle.getBatteryLevel quando tivermos o nome do dispositivo e mostrar o nível de bateria.
app.js
document.querySelector('#connect').addEventListener('click', event => {
playbulbCandle.connect()
.then(() => {
console.log(playbulbCandle.device);
document.querySelector('#state').classList.remove('connecting');
document.querySelector('#state').classList.add('connected');
return playbulbCandle.getDeviceName().then(handleDeviceName)
.then(() => playbulbCandle.getBatteryLevel().then(handleBatteryLevel));
})
.catch(error => {
console.error('Argh!', error);
});
});
function handleDeviceName(deviceName) {
document.querySelector('#deviceName').value = deviceName;
}
function handleBatteryLevel(batteryLevel) {
document.querySelector('#batteryLevel').textContent = batteryLevel + '%';
}
Agora, acesse seu site no navegador da Web (clicando no URL do servidor da Web destacado no app do servidor da Web) ou simplesmente atualize a página atual. Clique no botão "Conectar" na página para ver o nome do dispositivo e o nível de bateria.
A seguir
- Como posso mudar a cor desta lâmpada? É por isso que estou aqui!
- Você está quase lá, eu prometo...
Perguntas frequentes
6. Mudar a cor
Para mudar a cor, basta gravar um conjunto específico de comandos em uma característica do Bluetooth (0xFFFC) no serviço GATT principal anunciado como 0xFF02. Por exemplo, para deixar a PLAYBULB Candle vermelha, você precisa escrever uma matriz de números inteiros não assinados de 8 bits igual a [0x00, 255, 0, 0], em que 0x00 é a saturação de branco e 255, 0, 0 são, respectivamente, os valores de vermelho, verde e azul .
Vamos usar characteristic.writeValue para gravar alguns dados na característica do Bluetooth no novo método público setColor da classe PlaybulbCandle. Também vamos retornar os valores reais de vermelho, verde e azul quando a promessa for cumprida para que possamos usá-los em app.js mais tarde:
playbulbCandle.js
const CANDLE_COLOR_UUID = 0xFFFC;
...
setColor(r, g, b) {
let data = new Uint8Array([0x00, r, g, b]);
return this.device.gatt.getPrimaryService(CANDLE_SERVICE_UUID)
.then(service => service.getCharacteristic(CANDLE_COLOR_UUID))
.then(characteristic => characteristic.writeValue(data))
.then(() => [r,g,b]);
}
Vamos atualizar a função changeColor em app.js para chamar playbulbCandle.setColor quando o botão de opção "No Effect" estiver marcado. As variáveis de cor globais r, g, b já estão definidas quando o usuário clica na tela do seletor de cores.
app.js
function changeColor() {
var effect = document.querySelector('[name="effectSwitch"]:checked').id;
if (effect === 'noEffect') {
playbulbCandle.setColor(r, g, b).then(onColorChanged);
}
}
Agora, acesse o site no navegador da Web (clicando no URL do servidor da Web destacado no app do servidor da Web) ou simplesmente atualize a página atual. Clique no botão "Connect" na página e no seletor de cores para mudar a cor da PLAYBULB Candle quantas vezes quiser.
Mais efeitos de vela
Se você já acendeu uma vela, sabe que a luz não é estática. Felizmente, há outra característica do Bluetooth (0xFFFB) no serviço GATT principal anunciado como 0xFF02 que permite ao usuário definir alguns efeitos de vela.
Por exemplo, para definir um "efeito de vela", escreva [0x00, r, g, b, 0x04, 0x00, 0x01, 0x00]. Também é possível definir o "efeito de piscar" com [0x00, r, g, b, 0x00, 0x00, 0x1F, 0x00].
Vamos adicionar os métodos setCandleEffectColor e setFlashingColor à classe PlaybulbCandle.
playbulbCandle.js
const CANDLE_EFFECT_UUID = 0xFFFB;
...
setCandleEffectColor(r, g, b) {
let data = new Uint8Array([0x00, r, g, b, 0x04, 0x00, 0x01, 0x00]);
return this.device.gatt.getPrimaryService(CANDLE_SERVICE_UUID)
.then(service => service.getCharacteristic(CANDLE_EFFECT_UUID))
.then(characteristic => characteristic.writeValue(data))
.then(() => [r,g,b]);
}
setFlashingColor(r, g, b) {
let data = new Uint8Array([0x00, r, g, b, 0x00, 0x00, 0x1F, 0x00]);
return this.device.gatt.getPrimaryService(CANDLE_SERVICE_UUID)
.then(service => service.getCharacteristic(CANDLE_EFFECT_UUID))
.then(characteristic => characteristic.writeValue(data))
.then(() => [r,g,b]);
}
Vamos atualizar a função changeColor em app.js para chamar playbulbCandle.setCandleEffectColor quando o botão de opção "Candle Effect" estiver marcado e playbulbCandle.setFlashingColor quando o botão de opção "Flashing" estiver marcado. Desta vez, vamos usar switch, se estiver tudo bem para você.
app.js
function changeColor() {
var effect = document.querySelector('[name="effectSwitch"]:checked').id;
switch(effect) {
case 'noEffect':
playbulbCandle.setColor(r, g, b).then(onColorChanged);
break;
case 'candleEffect':
playbulbCandle.setCandleEffectColor(r, g, b).then(onColorChanged);
break;
case 'flashing':
playbulbCandle.setFlashingColor(r, g, b).then(onColorChanged);
break;
}
}
Agora, acesse o site no navegador da Web (clicando no URL do servidor da Web destacado no app do servidor da Web) ou simplesmente atualize a página atual. Clique no botão "Connect" na página e brinque com os efeitos de vela e de piscar.
A seguir
- Só isso? 3 efeitos de vela ruins? É por isso que estou aqui?
- Existem mais, mas você terá que se virar por conta própria desta vez.
7. Supere expectativas
E aqui estamos! Você pode achar que está quase no fim, mas o app ainda não acabou. Vamos ver se você realmente entendeu o que copiou e colou durante este codelab. Agora, você vai fazer o seguinte para deixar o app ainda melhor.
Adicionar efeitos ausentes
Confira os dados dos efeitos ausentes:
- Pulsação:
[0x00, r, g, b, 0x01, 0x00, 0x09, 0x00](talvez seja necessário ajustar os valores der, g, b) - Arco-íris:
[0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00](pessoas com epilepsia devem evitar essa opção) - Degradê arco-íris:
[0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x26, 0x00]
Isso basicamente significa adicionar novos métodos setPulseColor, setRainbow e setRainbowFade à classe PlaybulbCandle e chamá-los em changeColor.
Corrigir "sem efeito"
Como você deve ter notado, a opção "sem efeito" não redefine nenhum efeito em andamento. Isso é um detalhe, mas ainda assim é importante. Vamos corrigir isso. No método setColor, primeiro você precisa verificar se um efeito está em andamento usando uma nova variável de classe _isEffectSet. Se true, desative o efeito antes de definir uma nova cor com estes dados: [0x00, r, g, b, 0x05, 0x00, 0x01, 0x00].
Escreva o nome do dispositivo
Essa é fácil! Escrever um nome de dispositivo personalizado é tão simples quanto escrever na característica anterior do nome do dispositivo Bluetooth. Recomendo usar o método TextEncoder encode para receber uma Uint8Array com o nome do dispositivo.
Em seguida, adicionaria uma "entrada" eventListener a document.querySelector('#deviceName') e chamaria playbulbCandle.setDeviceName para simplificar.
Eu chamei o meu de PLAY💡 CANDLE!
8. Pronto!
O que você aprendeu
- Como interagir com um dispositivo Bluetooth por perto em JavaScript
- Como usar classes ES2015, funções de seta, mapas e promessas
Próximas etapas
- Saiba mais sobre a API Web Bluetooth.
- Confira as amostras e as demonstrações oficiais da Web Bluetooth.
- Confira o gato rabugento voador

