1. О чем она?

В этой практической работе вы узнаете, как управлять светодиодной свечой PLAYBULB с помощью JavaScript и Web Bluetooth API. В процессе вы также поработаете с такими функциями JavaScript ES2015, как классы, стрелочные функции, Map и Promises.
Что вы узнаете
- Как взаимодействовать с устройством Bluetooth поблизости с помощью JavaScript
- Как использовать классы ES2015, стрелочные функции, Map и Promises
Что вам понадобится
- Базовые знания в области веб-разработки.
- Базовые знания о Bluetooth с низким энергопотреблением (BLE) и профиле общих атрибутов (GATT).
- Текстовый редактор.
- Компьютер Mac, Chromebook или устройство Android M с приложением Chrome Browser и кабелем USB-Micro USB.
2. Сначала воспроизвести
Прежде чем приступить к этой практической работе, вы можете ознакомиться с финальной версией приложения, которое вы собираетесь создать, на странице https://googlecodelabs.github.io/candle-bluetooth и поэкспериментировать с имеющимся у вас устройством Bluetooth PLAYBULB Candle.
Вы также можете посмотреть, как я меняю цвета, на странице https://www.youtube.com/watch?v=fBCPA9gIxlU.
3. Подготовка
Скачать пример кода
Вы можете скачать образец кода в виде ZIP-архива:
или клонировав этот репозиторий git:
git clone https://github.com/googlecodelabs/candle-bluetooth.git
Если вы скачали исходный код в виде ZIP-архива, распакуйте его. Появится корневая папка candle-bluetooth-master.
Установите и проверьте веб-сервер
Вы можете использовать собственный веб-сервер, но эта практическая работа рассчитана на работу с веб-сервером Chrome. Если у вас нет этого приложения, установите его из интернет-магазина Chrome.
После установки приложения Web Server for Chrome нажмите на ярлык "Приложения" на панели закладок.

В открывшемся окне нажмите на значок веб-сервера.

Появится диалоговое окно, в котором можно настроить локальный веб-сервер.

Нажмите кнопку Choose folder (Выбрать папку) и выберите корневую папку клонированного или разархивированного репозитория. Это позволит вам показывать свою работу в процессе выполнения по URL, выделенному в диалоговом окне веб-сервера (в разделе URL веб-сервера).
В разделе Options (Параметры) установите флажок Automatically show index.html (Автоматически показывать index.html), как показано ниже.

Теперь откройте сайт в браузере, нажав на выделенный URL веб-сервера. Вы должны увидеть страницу, которая выглядит следующим образом:

Если вы хотите посмотреть, как это приложение выглядит на телефоне Android, включите удаленную отладку на Android и настройте переадресацию портов (по умолчанию используется порт 8887). После этого вы сможете просто открыть новую вкладку Chrome и перейти по адресу http://localhost:8887 на телефоне Android.
Далее
На данный момент это веб-приложение не делает многого. Давайте добавим поддержку Bluetooth.
4. Показать свечу
Для начала мы напишем библиотеку, которая использует класс JavaScript ES2015 для устройства Bluetooth PLAYBULB Candle.
Сохраняйте спокойствие. Синтаксис классов не вводит новую объектно-ориентированную модель наследования в JavaScript. Они просто предоставляют более понятный синтаксис для создания объектов и работы с наследованием, как описано ниже.
Сначала определим класс PlaybulbCandle в playbulbCandle.js и создадим экземпляр playbulbCandle, который будет доступен в файле app.js.
playbulbCandle.js
(function() {
'use strict';
class PlaybulbCandle {
constructor() {
this.device = null;
}
}
window.playbulbCandle = new PlaybulbCandle();
})();
Чтобы запросить доступ к устройству Bluetooth поблизости, нужно вызвать navigator.bluetooth.requestDevice. Поскольку устройство PLAYBULB Candle постоянно объявляет (если оно ещё не сопряжено) постоянный UUID сервиса Bluetooth GATT, известный в краткой форме как 0xFF02, мы можем просто определить константу и добавить ее в параметр сервисов фильтров в новом общедоступном методе connect класса PlaybulbCandle.
Мы также будем отслеживать объект BluetoothDevice, чтобы при необходимости получить к нему доступ позже. Поскольку navigator.bluetooth.requestDevice возвращает объект Promise JavaScript ES2015, мы сделаем это в методе 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();
})();
В качестве функции безопасности поиск устройств Bluetooth поблизости с помощью navigator.bluetooth.requestDevice должен запускаться жестом пользователя, например касанием или кликом мыши. Поэтому мы вызовем метод connect, когда пользователь нажмет кнопку "Подключиться" в файле 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);
});
});
Запустите приложение
Теперь откройте сайт в браузере, нажав на URL веб-сервера в приложении, или просто обновите страницу. Нажмите зеленую кнопку Connect (Подключить), выберите устройство и откройте консоль инструментов разработчика, нажав сочетание клавиш Ctrl + Shift + J. Вы увидите объект BluetoothDevice.

Если Bluetooth на телефоне или устройство Bluetooth PLAYBULB Candle отключено, может возникнуть ошибка. В этом случае включите его и повторите попытку.
Mandatory Bonus
Не знаю, как вы, а я уже вижу слишком много function() {} в этом коде. Давайте перейдем на стрелочные функции JavaScript ES2015 (() => {}). Они просто спасают жизнь: все прелести анонимных функций без грусти от привязки.
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);
});
});
Далее
– Ладно. Я могу поговорить с этой свечой или нет?
- Да, перейти к следующему шагу
Часто задаваемые вопросы
5. Читать
Итак, что делать, если вы получили BluetoothDevice из промиса navigator.bluetooth.requestDevice? Подключимся к GATT-серверу пульта Bluetooth, который содержит определения сервисов и характеристик Bluetooth, вызвав 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();
});
}
}
Как узнать название устройства
Здесь мы подключены к серверу GATT устройства Bluetooth PLAYBULB Candle. Теперь нам нужно получить основной сервис GATT (ранее объявленный как 0xFF02) и прочитать характеристику названия устройства (0xFFFF), которая принадлежит этому сервису. Для этого добавьте новый метод getDeviceName в класс PlaybulbCandle и используйте device.gatt.getPrimaryService и service.getCharacteristic. Метод characteristic.readValue возвращает объект DataView, который мы декодируем с помощью 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);
});
}
Добавим это в app.js, вызвав playbulbCandle.getDeviceName после подключения, и покажем название устройства.
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;
}
Теперь откройте сайт в браузере, нажав на URL веб-сервера, выделенный в приложении, или просто обновите страницу. Убедитесь, что свеча PLAYBULB включена, а затем нажмите кнопку "Подключить" на странице. Название устройства должно появиться под палитрой.

Как узнать уровень заряда батареи
В устройстве PLAYBULB Candle также есть стандартная характеристика Bluetooth, связанная с уровнем заряда батареи. Это значит, что мы можем использовать стандартные названия, такие как battery_service для UUID сервиса Bluetooth GATT и battery_level для UUID характеристики Bluetooth GATT.
Добавим новый метод getBatteryLevel в класс PlaybulbCandle и считаем уровень заряда батареи в процентах.
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));
}
Также нужно обновить объект JavaScript options, чтобы включить сервис батареи в ключ optionalServices, поскольку он не объявляется устройством Bluetooth PLAYBULB Candle, но все равно необходим для доступа к нему.
playbulbCandle.js
let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}],
optionalServices: ['battery_service']};
return navigator.bluetooth.requestDevice(options)
Как и раньше, давайте подключим это к app.js, вызвав playbulbCandle.getBatteryLevel, как только у нас будет имя устройства, и отобразим уровень заряда батареи.
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 + '%';
}
Теперь откройте сайт в браузере, нажав на URL веб-сервера, выделенный в приложении, или просто обновите страницу. Нажмите кнопку "Подключить" на странице. Вы увидите название устройства и уровень заряда батареи.
Далее
– Как изменить цвет этой лампочки? Вот почему я здесь!
– Вы уже очень близки к цели, уверяю вас.
Часто задаваемые вопросы
6. Изменить цвет
Чтобы изменить цвет, достаточно записать определенный набор команд в характеристику Bluetooth (0xFFFC) в основном сервисе GATT, который объявляется как 0xFF02. Например, чтобы свеча PLAYBULB Candle загорелась красным, нужно записать массив 8-битных беззнаковых целых чисел, равный [0x00, 255, 0, 0], где 0x00 – насыщенность белого, а 255, 0, 0 – значения красного, зеленого и синего соответственно .
Мы будем использовать characteristic.writeValue, чтобы записать данные в характеристику Bluetooth в новом общедоступном методе setColor класса PlaybulbCandle. Мы также вернем фактические значения красного, зеленого и синего цветов, когда обещание будет выполнено, чтобы мы могли использовать их в app.js позже:
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]);
}
Давайте обновим функцию changeColor в файле app.js, чтобы при выборе переключателя "Без эффекта" вызывалась функция playbulbCandle.setColor. Глобальные переменные цвета r, g, b уже заданы, когда пользователь нажимает на холст палитры.
app.js
function changeColor() {
var effect = document.querySelector('[name="effectSwitch"]:checked').id;
if (effect === 'noEffect') {
playbulbCandle.setColor(r, g, b).then(onColorChanged);
}
}
Теперь откройте сайт в браузере, нажав на URL веб-сервера, выделенный в приложении, или просто обновите страницу. Нажмите кнопку Connect (Подключить) на странице и выберите цвет свечи PLAYBULB Candle. Вы можете менять его сколько угодно раз.
Больше эффектов со свечами
Если вы когда-нибудь зажигали свечу, то знаете, что ее свет не статичен. К счастью, в основном сервисе GATT, который рекламируется как 0xFF02, есть ещё одна характеристика Bluetooth (0xFFFB), которая позволяет пользователю настраивать некоторые эффекты свечи.
Например, чтобы задать эффект свечи, можно написать [0x00, r, g, b, 0x04, 0x00, 0x01, 0x00]. Вы также можете настроить эффект мигания с помощью [0x00, r, g, b, 0x00, 0x00, 0x1F, 0x00].
Добавим методы setCandleEffectColor и setFlashingColor в класс 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]);
}
Обновим функцию changeColor в app.js, чтобы вызывать playbulbCandle.setCandleEffectColor, когда выбран переключатель "Эффект свечи", и playbulbCandle.setFlashingColor, когда выбран переключатель "Мигание". В этот раз мы будем использовать switch, если вы не против.
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;
}
}
Теперь откройте сайт в браузере, нажав на URL веб-сервера, выделенный в приложении, или просто обновите страницу. Нажмите кнопку "Подключить" на странице и поиграйте с эффектами "Свеча" и "Мигание".
Далее
– И это все? 3 плохих эффекта свечи? Is this why I'm here?
- Есть и другие, но на этот раз вы будете сами по себе.
7. Уделяйте зрителям больше внимания
Вот мы и на месте! Вы можете подумать, что это почти конец, но приложение ещё не закончено. Давайте проверим, поняли ли вы, что скопировали и вставили во время этой практической работы. Вот что вы можете сделать самостоятельно, чтобы улучшить приложение.
Добавьте недостающие эффекты
Вот данные об отсутствующих эффектах:
- Pulse:
[0x00, r, g, b, 0x01, 0x00, 0x09, 0x00](возможно, вам потребуется скорректировать значенияr, g, b) - Радуга:
[0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00](не рекомендуется людям с эпилепсией) - Радужное затухание:
[0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x26, 0x00]
Это означает, что в класс PlaybulbCandle нужно добавить новые методы setPulseColor, setRainbow и setRainbowFade и вызвать их в changeColor.
Как исправить ошибку "Нет эффекта"
Как вы могли заметить, вариант "Без эффекта" не сбрасывает текущий эффект. Это незначительная проблема, но все же. Давайте исправим это. В методе setColor сначала нужно проверить, выполняется ли эффект, с помощью новой переменной класса _isEffectSet. Если true, отключите эффект, прежде чем задавать новый цвет с помощью следующих данных: [0x00, r, g, b, 0x05, 0x00, 0x01, 0x00].
Введите название устройства
Это просто! Задать собственное название устройства так же просто, как и в случае с предыдущей характеристикой названия устройства Bluetooth. Рекомендуем использовать метод TextEncoder encode, чтобы получить Uint8Array с названием устройства.
Затем я добавлю "входные данные" eventListener в document.querySelector('#deviceName') и вызову playbulbCandle.setDeviceName, чтобы упростить задачу.
Я назвал свою PLAY💡 CANDLE!
8. Готово!
Что вы узнали
- Как взаимодействовать с устройством Bluetooth поблизости с помощью JavaScript
- Как использовать классы ES2015, стрелочные функции, Map и Promises
Дальнейшие действия
- Подробнее о Web Bluetooth API…
- Ознакомьтесь с официальными примерами и демоверсиями Web Bluetooth.
- Посмотрите на летающего сердитого кота.

