1. Qual é o objetivo do jogo?

Neste codelab, você vai aprender a controlar uma vela sem chama de LED PLAYBULB usando apenas JavaScript graças à API Web Bluetooth. Ao longo do caminho, você também vai usar recursos do JavaScript ES2015, como Classes, Funções de seta, Map e Promises.
O que você vai aprender
- Como interagir com um dispositivo Bluetooth próximo em JavaScript
- Como usar classes ES2015, funções de seta, mapa e promessas
O que é necessário
- Conhecimento básico de desenvolvimento da 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. Tocar primeiro
Confira a versão final do app que você vai criar em https://googlecodelabs.github.io/candle-bluetooth (link em inglês) e teste o dispositivo PLAYBULB Candle Bluetooth 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
Baixe o exemplo de código
Para acessar o exemplo de código, faça o download do ZIP aqui:
ou clonando este repositório git:
git clone https://github.com/googlecodelabs/candle-bluetooth.git
Se você baixou a fonte 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 Escolher pasta e selecione a raiz do repositório clonado (ou descompactado). 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 seu site no navegador da Web (clicando no URL do servidor da Web destacado). Você vai ver uma página parecida com esta:

Se quiser ver como esse app aparece no seu smartphone Android, ative a depuração remota no Android e configure o encaminhamento de portas (o número da porta padrão é 8887). Depois disso, basta abrir uma nova guia do Chrome em http://localhost:8887 no smartphone Android.
A seguir
Neste momento, o web app não faz muita coisa. Vamos começar a adicionar suporte ao Bluetooth.
4. Descobrir 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 está introduzindo um novo modelo de herança orientada a objetos para JavaScript. Ela simplesmente oferece uma sintaxe muito mais clara para criar objetos e lidar com herança, conforme 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 Bluetooth constante conhecido na forma abreviada como 0xFF02, podemos simplesmente definir uma constante e adicioná-la ao parâmetro services 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 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 um recurso de segurança, a descoberta de dispositivos Bluetooth por perto com navigator.bluetooth.requestDevice precisa ser chamada por um gesto do usuário, como um toque ou um clique do mouse. Por isso, vamos chamar o método connect quando o usuário clicar no botão "Connect" (Conectar) 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
Neste ponto, 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 "Conectar", escolha o dispositivo no seletor e abra seu console favorito das Ferramentas para desenvolvedores com o atalho de teclado Ctrl + Shift + J. Observe o objeto BluetoothDevice registrado.

Você pode receber um erro se o Bluetooth estiver desativado e/ou o dispositivo Bluetooth PLAYBULB Candle estiver desligado. Nesse caso, ligue-o e continue.
Bônus obrigatório
Não sei você, mas já vejo muitos function() {} neste código. Vamos mudar para as funções de seta () => {} JavaScript ES2015. Elas são uma mão na roda: 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 conversar com essa vela ou não?
- Claro... vá 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 do controle remoto Bluetooth, que contém as definições de serviço e características 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 receber 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. Isso pode ser feito facilmente adicionando um novo método getDeviceName à classe PlaybulbCandle e usando 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 assim que estivermos conectados e mostrando 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;
}
Neste ponto, 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. Depois, clique no botão "Conectar" na página. O nome do dispositivo vai aparecer abaixo do seletor de cores.

Ler o nível de bateria
Há também uma característica Bluetooth padrão de nível de bateria 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 Bluetooth e battery_level para o UUID da característica GATT 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 a app.js chamando playbulbCandle.getBatteryLevel assim que tivermos o nome do dispositivo e mostrarmos o nível da 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 + '%';
}
Neste ponto, 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 "Conectar" na página. O nome do dispositivo e o nível da bateria vão aparecer.
A seguir
- Como posso mudar a cor dessa lâmpada? É por isso que estou aqui!
- Você está quase lá, prometo...
Perguntas frequentes
6. Mudar a cor
Mudar a cor é tão fácil quanto escrever um conjunto específico de comandos em uma característica Bluetooth (0xFFFC) no serviço GATT principal anunciado como 0xFF02. Por exemplo, para mudar a cor da PLAYBULB Candle para vermelho, você precisa escrever uma matriz de números inteiros sem sinal de 8 bits igual a [0x00, 255, 0, 0], em que 0x00 é a saturação de branco e 255, 0, 0 são os valores de vermelho, verde e azul, respectivamente .
Vamos usar characteristic.writeValue para gravar alguns dados na característica 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 "Nenhum efeito" 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);
}
}
Neste ponto, 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 "Conectar" 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 antes, sabe que a luz não é estática. Felizmente, há outra característica do Bluetooth (0xFFFB) no serviço GATT principal anunciada como 0xFF02 que permite ao usuário definir alguns efeitos de vela.
Para definir um "efeito de vela", por exemplo, escreva [0x00, r, g, b, 0x04, 0x00, 0x01, 0x00]. Você também pode definir o "efeito de flash" 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 "Efeito de vela" estiver marcado e playbulbCandle.setFlashingColor quando o botão de opção "Piscando" estiver marcado. Desta vez, vamos usar switch, se não houver problemas.
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;
}
}
Neste ponto, 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 "Conectar" na página e use os efeitos de vela e de luz piscando.
A seguir
- Só isso? 3 efeitos ruins de vela? É por isso que estou aqui?
- Há mais, mas você vai precisar descobrir por conta própria desta vez.
7. Supere seus limites
E aqui estamos! Você pode achar que está quase no fim, mas o app ainda não acabou. Vamos ver se você entendeu o que copiou e colou durante este codelab. Agora, faça o seguinte para deixar o app ainda melhor.
Adicionar efeitos ausentes
Confira os dados dos efeitos ausentes:
- Pulso:
[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 talvez queiram evitar esse efeito) - Desaparecimento do arco-íris:
[0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x26, 0x00]
Isso 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 é pequeno, mas ainda assim. Vamos corrigir isso. No método setColor, primeiro verifique se um efeito está em andamento usando uma nova variável de classe _isEffectSet e, se true, desative o efeito antes de definir uma nova cor com estes dados: [0x00, r, g, b, 0x05, 0x00, 0x01, 0x00].
Escrever o nome do dispositivo
Essa é fácil! Escrever um nome de dispositivo personalizado é tão simples quanto escrever na característica de nome do dispositivo Bluetooth anterior. Recomendamos usar o método TextEncoder encode para receber um Uint8Array que contenha o nome do dispositivo.
Em seguida, adicionaria uma eventListener "input" a document.querySelector('#deviceName') e chamaria playbulbCandle.setDeviceName para simplificar.
Eu dei o nome de PLAY💡 CANDLE!
8. Pronto!
O que você aprendeu
- Como interagir com um dispositivo Bluetooth próximo em JavaScript
- Como usar classes ES2015, funções de seta, mapa e promessas
Próximas etapas
- Saiba mais sobre a API Web Bluetooth.
- Navegue pelas amostras do Web Bluetooth e demonstrações oficiais.
- Confira o gato mal-humorado voador

