Controle uma vela do PLAYBULB com o Web Bluetooth

1. Sobre o que é?

IMG_19700101_023537~2~2.jpg

Neste codelab, você vai aprender a controlar uma vela sem chamas de LED PLAYBULB com apenas JavaScript, graças à API Web Bluetooth. Ao longo do caminho, você também testará recursos do JavaScript ES2015, como Classes, Funções de seta, Mapa e Promessas.

O que você vai aprender

  • Como interagir no JavaScript com um dispositivo Bluetooth por perto
  • Como usar as classes ES2015, as funções de seta, o Map e as promessas.

O que é necessário

2. Jogue primeiro

Confira a versão final do app que você está prestes a criar em https://googlecodelabs.github.io/candle-bluetooth (link em inglês) e teste o dispositivo PLAYBULB Candle Bluetooth que está à sua disposição antes de começar este codelab.

Você também pode me assistir mudando de cor em https://www.youtube.com/watch?v=fBCPA9gIxlU

3. Começar a configuração

Fazer o download do exemplo de código

Você pode obter o exemplo de código para este código fazendo o download do zip aqui:

ou clonando este repositório do Git:

git clone https://github.com/googlecodelabs/candle-bluetooth.git

Se você fez o download da origem como um ZIP, descompactá-lo fornece uma pasta raiz candle-bluetooth-master.

Instalar e verificar o servidor da Web

Você pode usar seu próprio servidor da Web, mas este codelab foi projetado para funcionar bem com o Chrome Web Server. Se você ainda não tem esse app instalado, faça isso pela Chrome Web Store.

Depois de instalar o app do servidor da Web para Chrome, clique no atalho "Apps" na barra de favoritos:

Screen Shot 2016-11-16 at 4.10.42 PM.png

Na janela seguinte, clique no ícone do Web Server:

9f3c21b2cf6cbfb5.png

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

Screen Shot 2016-11-16 at 3.40.47 PM.png

Clique no botão Escolher pasta e selecione a raiz do repositório clonado (ou desarquivado). Isso permitirá que você exiba o trabalho em andamento 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 index.html automaticamente", conforme mostrado abaixo:

Screen Shot 2016-11-16 at 3.40.56 PM.png

Agora acesse seu site no navegador da Web (clicando no URL do servidor da Web destacado) e você verá uma página como esta:

Screen Shot 2016-11-16 at 3.20.22 PM.png

Para conferir a aparência desse app no seu smartphone Android, ative a Depuração remota no Android e configure o encaminhamento de portas. Por padrão, o número da porta é 8887. Depois disso, basta abrir uma nova guia do Chrome para http://localhost:8887 no seu smartphone Android.

A seguir

Neste ponto, o aplicativo da web não tem muita utilidade. Vamos adicionar compatibilidade com Bluetooth!

4. Descubra a vela

Vamos começar escrevendo uma biblioteca que usa uma classe JavaScript ES2015 para o dispositivo PLAYBULB Candle Bluetooth.

Mantenha a calma. A sintaxe da classe não está introduzindo um novo modelo de herança orientado a objetos para JavaScript. Ele simplesmente fornece uma sintaxe muito mais clara para criar objetos e lidar com heranças, como você pode ler abaixo.

Primeiro, vamos definir uma classe PlaybulbCandle em playbulbCandle.js e criar uma instância playbulbCandle que será disponibilizada no arquivo app.js depois.

playbulbCandle.js

(function() {
  'use strict';

  class PlaybulbCandle {
    constructor() {
      this.device = null;
    }
  }

  window.playbulbCandle = new PlaybulbCandle();

})();

Para solicitar acesso a um dispositivo Bluetooth próximo, 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 de serviços de filtros em um novo método connect público da classe PlaybulbCandle.

Também vamos rastrear internamente o objeto BluetoothDevice para que possamos acessá-lo mais tarde, se necessário. Como navigator.bluetooth.requestDevice retorna uma promessa ES2015 JavaScript, faremos 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 "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 aplicativo do servidor da Web) ou simplesmente atualize a página. Clique no botão verde "Conectar". selecione o dispositivo no seletor, abra seu console favorito das Ferramentas para desenvolvedores usando o atalho de teclado Ctrl + Shift + J e observe que o objeto BluetoothDevice foi registrado.

Screen Shot 2016-11-16 at 3.27.12 PM.png

Você pode receber um erro se o Bluetooth estiver desativado e/ou o dispositivo Bluetooth PLAYBULB Candle estiver desativado. Nesse caso, ative-a e prossiga de novo.

Bônus obrigatório

Não sei você, mas já vejo muitas function() {} neste código. Em vez disso, vamos mudar para as funções de seta do JavaScript ES2015 () => {}. Eles são um grande benefício: 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

- Certo... Posso falar com essa vela ou o quê?

- Claro... passe para a próxima etapa

Perguntas frequentes

5. Ler algo

O que fazer agora que há um BluetoothDevice retornado da promessa de navigator.bluetooth.requestDevice? Vamos nos conectar ao servidor GATT remoto Bluetooth que contém o serviço Bluetooth e as definições de características 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

Estamos conectados ao servidor GATT do dispositivo Bluetooth PLAYBULB Candle. Agora, queremos obter 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 simplesmente 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 a app.js chamando playbulbCandle.getDeviceName quando estivermos conectados e exibirmos 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 aplicativo do servidor da Web) ou simplesmente atualize a página. Verifique se a vela PLAYBULB está ativada e clique no botão "Conectar" na página, e você verá o nome do dispositivo abaixo do seletor de cores.

Screen Shot 2016-11-16 at 3.29.21 PM.png

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 com o nível de bateria do dispositivo. Isso significa que podemos usar nomes padrão como battery_service para o UUID do serviço Bluetooth GATT e battery_level para o UUID de característica do Bluetooth GATT.

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 para a chave optionalServices, já que ela não é anunciada 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 quando tivermos o nome do dispositivo e mostrarmos 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 o site no navegador da Web (clicando no URL do servidor da Web destacado no aplicativo do servidor da Web) ou simplesmente atualize a página. Clique no botão "Conectar". na página. Você verá o nome do dispositivo e o nível da bateria.

Screen Shot 2016-11-16 at 3.29.21 PM.png

A seguir

- Como posso mudar a cor dessa lâmpada? É por isso que estou aqui.

- Você está tão perto, prometo...

Perguntas frequentes

6. Mudar a cor

Mudar a cor é tão fácil quanto escrever um conjunto específico de comandos para uma característica Bluetooth (0xFFFC) no serviço GATT principal anunciado como 0xFF02. Por exemplo, mudar a vela do PLAYBULB para vermelho significa escrever uma matriz de números inteiros não assinados de 8 bits iguais a [0x00, 255, 0, 0], em que 0x00 é a saturação branca e 255, 0, 0 são, respectivamente, os valores vermelho, verde e azul .

Vamos usar o 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 atendida 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 no app.js para chamar playbulbCandle.setColor quando a mensagem "Sem efeito" está 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 aplicativo do servidor da Web) ou simplesmente atualize a página. Clique no botão "Conectar". na página e clique no seletor de cores para mudar a cor da vela do PLAYBULB quantas vezes quiser.

Screen Shot 2016-11-16 at 3.31.37 PM.png

Efeitos de vela moar (link em inglês)

Se você já acendeu uma vela antes, sabe que a luz não é estática. Felizmente, há outra característica de Bluetooth (0xFFFB) no serviço GATT principal anunciado como 0xFF02 que permite ao usuário definir alguns efeitos de vela.

Como definir um "efeito de vela" por exemplo, pode ser conseguido escrevendo [0x00, r, g, b, 0x04, 0x00, 0x01, 0x00]. Também é possível definir o "efeito intermitente" 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 no app.js para chamar playbulbCandle.setCandleEffectColor quando o "Efeito vela" botão de opção estiver marcado e playbulbCandle.setFlashingColor quando a mensagem está marcado. Desta vez, switch se você estiver de acordo com 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;
  }
}

Neste ponto, acesse o site no navegador da Web (clicando no URL do servidor da Web destacado no aplicativo do servidor da Web) ou simplesmente atualize a página. Clique no botão "Conectar". na página e teste os efeitos de vela e flash.

Screen Shot 2016-11-16 at 3.33.23 PM.png

A seguir

- Só isso? Três efeitos ruins de vela? É por isso que estou aqui?

- Há mais, mas desta vez você vai ficar por conta própria.

7. Vá além

Aqui estamos nós. Talvez você ache 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. Confira o que você quer fazer para destacar este app.

Adicionar efeitos ausentes

Estes são os dados dos efeitos ausentes:

  • Pulso: [0x00, r, g, b, 0x01, 0x00, 0x09, 0x00] (convém ajustar os valores de r, g, b aqui)
  • Arco-íris: [0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00] (epiléticos podem evitar esse problema)
  • Esmaecimento 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 a mensagem "Sem efeito"

Como você deve ter notado, a expressão "sem efeito" não redefine nenhum efeito em andamento. Isso é pequeno, mas ainda assim. Vamos corrigir isso. No método setColor, é necessário verificar primeiro se um efeito está em andamento usando uma nova variável de classe _isEffectSet e, se for true, desativar o efeito antes de definir uma nova cor com estes dados: [0x00, r, g, b, 0x05, 0x00, 0x01, 0x00].

Gravar o nome do dispositivo

Essa é fácil! Escrever um nome personalizado de dispositivo é tão simples quanto escrever na característica anterior do nome do dispositivo Bluetooth. Recomendamos usar o método TextEncoder encode para receber um Uint8Array contendo o nome do dispositivo.

Depois, adicionaria uma "entrada" eventListener para document.querySelector('#deviceName') e chame playbulbCandle.setDeviceName para simplificar.

Eu pessoalmente nomeei o meu como PLAY💡 CANDLE!

8. Pronto!

O que você aprendeu

  • Como interagir no JavaScript com um dispositivo Bluetooth por perto
  • Como usar as classes ES2015, as funções de seta, o Map e as promessas.

Próximas etapas