Controla una vela PLAYBULB con Web Bluetooth

1. ¿De qué se trata?

IMG_19700101_023537~2~2.jpg

En este codelab ilustrado, aprenderás a controlar una vela LED sin llamas LED de PLAYBULB solo con JavaScript gracias a la API de Web Bluetooth. También podrás probar funciones de JavaScript ES2015, como clases, funciones de flecha, Map y promesas.

Qué aprenderás

  • Cómo interactuar con un dispositivo Bluetooth cercano en JavaScript
  • Cómo usar clases, funciones de flecha, mapas y promesas de ES2015

Requisitos

2. Reproducir primero

Te recomendamos consultar la versión final de la app que estás por crear en https://googlecodelabs.github.io/candle-bluetooth y jugar con el dispositivo Bluetooth PLAYBULB que tienes a tu disposición antes de sumergirte en este codelab.

También puedes ver cómo cambia de color en https://www.youtube.com/watch?v=fBCPA9gIxlU

3. Prepárate

Descarga el código de muestra

Para obtener el código de muestra de este código, descarga el zip aquí:

Descargar código fuente

o clonando este repositorio de Git:

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

Si descargaste la fuente como un ZIP, cuando lo descomprimas deberías obtener una carpeta raíz candle-bluetooth-master.

Instala y verifica el servidor web

Aunque puedes usar tu propio servidor web, este codelab está diseñado para funcionar bien con Chrome Web Server. Si aún no tienes esa app instalada, puedes instalarla desde Chrome Web Store.

Después de instalar la app Web Server for Chrome, haz clic en el acceso directo de Apps en la barra de favoritos:

Captura de pantalla 2016-11-16 a las 4.10.42 PM.png

En la ventana subsiguiente, haz clic en el ícono de Web Server:

9f3c21b2cf6cbfb5.png

A continuación, verás este diálogo, que te permite configurar tu servidor web local:

Captura de pantalla 2016-11-16 a las 3.40.47 PM.png

Haz clic en el botón Choose folder y selecciona la raíz del repositorio clonado (o desarchivado). Esto te permitirá entregar el trabajo en curso a través de la URL destacada en el diálogo del servidor web (en la sección Web Server URL(s)).

En Opciones, marca la casilla junto a "Automatically show index.html", como se muestra a continuación:

Captura de pantalla 2016-11-16 a las 3.40.56 PM.png

Ahora visita tu sitio en tu navegador web (haciendo clic en la URL destacada de Web Server) y deberías ver una página como la siguiente:

Captura de pantalla 2016-11-16 a las 3.20.22 PM.png

Si quieres saber cómo se ve esta app en tu teléfono Android, deberás habilitar la Depuración remota en Android y configurar la redirección de puertos (el número de puerto predeterminado es 8887). Luego, puedes abrir una nueva pestaña de Chrome en http://localhost:8887 en tu teléfono Android.

Cuál es el próximo paso

En este punto, esta app web no hace mucho. Comencemos a agregar compatibilidad con Bluetooth.

4. Descubre la vela

Empezaremos escribiendo una biblioteca que use una clase JavaScript ES2015 para el dispositivo Bluetooth PLAYBULB Candle.

Mantén la calma. La sintaxis de la clase no presentará un nuevo modelo de herencia orientado a objetos en JavaScript. Simplemente, proporciona una sintaxis mucho más clara para crear objetos y lidiar con la herencia, como puedes leer a continuación.

Primero, definiremos una clase PlaybulbCandle en playbulbCandle.js y crearemos una instancia de playbulbCandle que estará disponible más adelante en el archivo app.js.

playbulbCandle.js

(function() {
  'use strict';

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

  window.playbulbCandle = new PlaybulbCandle();

})();

Para solicitar acceso a un dispositivo Bluetooth cercano, debemos llamar a navigator.bluetooth.requestDevice. Dado que el dispositivo PLAYBULB Candle anuncia de manera continua (si aún no está vinculado) un UUID de servicio GATT de Bluetooth constante conocido como 0xFF02, podemos definir una constante y agregarla al parámetro de servicios de filtros en un nuevo método público connect de la clase PlaybulbCandle.

También realizaremos un seguimiento interno del objeto BluetoothDevice para poder acceder a él más tarde si es necesario. Como navigator.bluetooth.requestDevice muestra una promesa de JavaScript ES2015, lo haremos en el 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 función de seguridad, la detección de dispositivos Bluetooth cercanos con navigator.bluetooth.requestDevice se debe llamar con un gesto del usuario, como un toque o un clic del mouse. Por eso, llamaremos al método connect cuando el usuario haga clic en "Conectar". en el archivo 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);
  });
});

Ejecuta la app

En este punto, visita tu sitio en tu navegador web (haciendo clic en la URL del servidor web destacada en la app del servidor web) o simplemente actualiza la página existente. Haz clic en el botón verde "Conectar". elige el dispositivo del selector y abre tu consola favorita de Herramientas para desarrolladores con la combinación de teclas Ctrl + Mayúsculas + J y observa que se registró el objeto BluetoothDevice.

Captura de pantalla 2016-11-16 a las 3.27.12 PM.png

Es posible que se produzca un error si el Bluetooth está desactivado o si el dispositivo Bluetooth PLAYBULB Candle está desactivado. En ese caso, actívalo y vuelve a continuar.

Bonificación obligatoria

No sé qué te parece, pero ya veo demasiados function() {} en este código. En su lugar, cambiemos a las funciones de flecha de JavaScript ES2015 de () => {}. Son absolutamente salvadores: Toda la belleza de las funciones anónimas, sin la tristeza de las vinculaciones.

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

Cuál es el próximo paso

- Bien... ¿Puedo hablarle a esta vela o qué?

- Por supuesto... continúa con el siguiente paso

Preguntas frecuentes

5. Leer algo

¿Qué haces ahora que se muestra un BluetoothDevice de la promesa de navigator.bluetooth.requestDevice? Conectémonos al GATT Server remoto con Bluetooth que contiene el servicio Bluetooth y las definiciones de características llamando a 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();
      });
    }
  }

Lee el nombre del dispositivo

Aquí estamos conectados al servidor GATT del dispositivo Bluetooth PLAYBULB Candle. Ahora queremos obtener el servicio GATT principal (que antes se anunciaba como 0xFF02) y leer la característica del nombre del dispositivo (0xFFFF) que pertenece a este servicio. Esto se puede lograr fácilmente agregando un nuevo método getDeviceName a la clase PlaybulbCandle y usando device.gatt.getPrimaryService y service.getCharacteristic. El método characteristic.readValue en realidad mostrará un DataView que simplemente decodificaremos con 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);
      });
    }

Agreguemos esto a app.js llamando a playbulbCandle.getDeviceName una vez que nos conectemos y mostremos el nombre del 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;
}

En este punto, visita tu sitio en tu navegador web (haciendo clic en la URL del servidor web destacada en la app del servidor web) o simplemente actualiza la página existente. Asegúrate de que la vela PLAYBULB esté encendida y haz clic en "Conectar" de la página y deberías ver el nombre del dispositivo debajo del selector de color.

Captura de pantalla 2016-11-16 a las 3.29.21 PM.png

Consultar el nivel de batería

También hay una característica estándar de Bluetooth a nivel de batería disponible en el dispositivo PLAYBULB Candle Bluetooth que contiene el nivel de batería del dispositivo. Esto significa que podemos usar nombres estándar como battery_service para el UUID del servicio GATT de Bluetooth y battery_level para el UUID de característica GATT de Bluetooth.

Agreguemos un nuevo método getBatteryLevel a la clase PlaybulbCandle y analicemos el nivel de batería en porcentaje.

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

También necesitamos actualizar el objeto de JavaScript options para incluir el servicio de batería en la clave optionalServices, ya que no lo anuncia el dispositivo Bluetooth PLAYBULB Candle, pero sigue siendo obligatorio para acceder a él.

playbulbCandle.js

      let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}],
                     optionalServices: ['battery_service']};
      return navigator.bluetooth.requestDevice(options)

Como antes, conectaremos esto a app.js llamando a playbulbCandle.getBatteryLevel una vez que tengamos el nombre del dispositivo y mostremos el nivel de batería.

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 + '%';
}

En este punto, visita tu sitio en tu navegador web (haciendo clic en la URL del servidor web destacada en la app del servidor web) o simplemente actualiza la página existente. Haz clic en el botón “Conectar” de la página. Allí deberías ver el nombre del dispositivo y el nivel de batería.

Captura de pantalla 2016-11-16 a las 3.29.21 PM.png

Cuál es el próximo paso

- ¿Cómo puedo cambiar el color de esta bombilla? Por eso estoy aquí.

- Te prometo que estás tan cerca...

Preguntas frecuentes

6. Cambiar el color

Cambiar el color es tan fácil como escribir un conjunto específico de comandos en una característica de Bluetooth (0xFFFC) en el servicio GATT principal anunciado como 0xFF02. Por ejemplo, si cambias la vela PLAYBULB a rojo, estaría escribiendo un array de números enteros sin signo de 8 bits iguales a [0x00, 255, 0, 0], donde 0x00 es la saturación de blancos y 255, 0, 0 son, respectivamente, los valores de rojo, verde y azul .

Usaremos characteristic.writeValue para escribir algunos datos en la característica de Bluetooth en el nuevo método público setColor de la clase PlaybulbCandle. También mostraremos los valores reales de rojo, verde y azul cuando se cumpla la promesa para que podamos usarlos en app.js más adelante:

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

Actualicemos la función changeColor en app.js para llamar a playbulbCandle.setColor cuando se muestre el resultado "Sin efecto" el botón de selección esté marcado. Las variables de color r, g, b globales ya están configuradas cuando el usuario hace clic en el lienzo del selector de color.

app.js

function changeColor() {
  var effect = document.querySelector('[name="effectSwitch"]:checked').id;
  if (effect === 'noEffect') {
    playbulbCandle.setColor(r, g, b).then(onColorChanged);
  }
}

En este punto, visita tu sitio en tu navegador web (haciendo clic en la URL del servidor web destacada en la app del servidor web) o simplemente actualiza la página existente. Haz clic en el botón “Conectar” de la página y haz clic en el selector de color para cambiar el color de la vela de PLAYBULB tantas veces como desees.

Captura de pantalla 2016-11-16 a las 3.31.37 PM.png

Efectos de velas moradas

Si ya has encendido una vela antes, sabrás que la luz no es estática. Por suerte para nosotros, hay otra característica Bluetooth (0xFFFB) en el servicio principal GATT, anunciada como 0xFF02, que le permite al usuario configurar algunos efectos de vela.

Configurar un “efecto de vela” Por ejemplo, se puede lograr escribiendo [0x00, r, g, b, 0x04, 0x00, 0x01, 0x00]. También puedes establecer el "efecto de flash" con [0x00, r, g, b, 0x00, 0x00, 0x1F, 0x00].

Agreguemos los métodos setCandleEffectColor y setFlashingColor a la clase 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]);
    }

Actualicemos la función changeColor en app.js para llamar a playbulbCandle.setCandleEffectColor cuando el "Efecto de vela" el botón de selección está marcado y playbulbCandle.setFlashingColor cuando el ícono "Intermitente" está marcado el botón de selección esté marcado. Esta vez, usaremos switch si te parece bien.

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

En este punto, visita tu sitio en tu navegador web (haciendo clic en la URL del servidor web destacada en la app del servidor web) o simplemente actualiza la página existente. Haz clic en el botón “Conectar” de la página y juega con velas y efectos de flash.

Captura de pantalla 2016-11-16 a las 3.33.23 PM.png

Cuál es el próximo paso

- ¿Eso es todo? ¿3 efectos de velas malos? ¿Es por eso que estoy aquí?

- Hay más, pero trabajarás por tu cuenta esta vez.

7. Haz un esfuerzo adicional

¡Aquí estamos! Tal vez pienses que está por el final, pero la app aún no termina. Veamos si entendiste lo que copiaste y pegaste durante este codelab. Esto es lo que quieres hacer por tu cuenta para que esta app se destaque.

Agregar los efectos que faltan

A continuación, se muestran los datos de los efectos faltantes:

  • Pulso: [0x00, r, g, b, 0x01, 0x00, 0x09, 0x00] (es posible que desees ajustar los valores de r, g, b allí)
  • Arcoíris: [0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00] (Las personas epilépticas quieren evitar este)
  • Atenuación de arcoíris: [0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x26, 0x00]

Básicamente, esto significa agregar nuevos métodos setPulseColor, setRainbow y setRainbowFade a la clase PlaybulbCandle y llamarlos en changeColor.

Cómo corregir la configuración “sin efecto”

Como habrás notado, la expresión "sin efecto" no restablece ningún efecto en curso. Esto es menor, pero igual. Tiene una solución. En el método setColor, primero deberás verificar si hay un efecto en curso a través de una nueva variable de clase _isEffectSet y, si es true, desactivar el efecto antes de configurar un nuevo color con estos datos: [0x00, r, g, b, 0x05, 0x00, 0x01, 0x00].

Escribir nombre del dispositivo

Esto es fácil. Escribir un nombre de dispositivo personalizado es tan simple como escribir en la característica de nombre del dispositivo Bluetooth anterior. Te recomendamos que uses el método TextEncoder encode para obtener un Uint8Array que contenga el nombre del dispositivo.

Luego, agrego una "entrada" eventListener a document.querySelector('#deviceName') y llama a playbulbCandle.setDeviceName para simplificar el proceso.

Personalmente, le llamé a la mía PLAY💡 VENTAJA.

8. Eso es todo.

Qué aprendiste

  • Cómo interactuar con un dispositivo Bluetooth cercano en JavaScript
  • Cómo usar clases, funciones de flecha, mapas y promesas de ES2015

Próximos pasos