— Чайники, весы, игрушки, лампочки, кофемашины… В эти и другие устройства встраивают bluetooth-модули.
— Зачем?
— Чтобы дать пользователю управлять своими устройствами через приложение. К примеру, управлять освещением в комнате.
— Ой, а можно ли собрать какое-то своё простое устройство и управлять им прямо через браузер?
— Да! И эта статья как раз об этом.
Немного теории
Здесь я приведу несколько основных терминов, которые необходимы нам для воплощения задачи (о ней мы поговорим немного позже) в жизнь.
Bluetooth
Стандарт беспроводной радиосвязи, связывающий на коротком расстоянии различные типы устройств. Для управления железками через Web Bluetooth API нам потребуется Bluetooth v4.0.
GATT
The Generic Attributes — постоянно транслируемое дерево возможностей bluetooth-устройства.
Сервисы
Внутри bluetooth-устройства есть сервисы. Сам по себе сервис — это коллекция характеристик и связей с другими сервисами. У каждого сервиса есть свои UID и имя. Зачастую будут попадаться “Unknown services”. Это связано с тем, что количество устройств и вариантов их использования велико.
Характеристики
Внутри каждого сервиса есть характеристики, в которые можно писать, считывать их, а также подписаться на них. Характеристика также имеет свой UID.
Задача
В качестве задачи я выбрал реализацию сайта, который может:
- Зажигать светодиоды разными цветами и гасить их.
- Заставлять светодиоды переливаться разными цветами.
И, как можно понять из постановки задачи, необходимо научиться подключаться и отключаться от bluetooth-устройства.
Компоненты
Для выполнения задачи я выбрал следующий список необходимого:
- Arduino.
- Bluetooth-модуль v4.0 (HM-10 в моём случае).
- Два трёхцветных светодиода.
- Макетная плата.
- Соединительные провода.
- Резисторы.
Данный список не является строгим для реализации. Я уверен, что можно заменить Arduino чем-то другим и выбрать другой bluetooth-модуль. Но в статье будет рассматриваться взаимодействие именно с этими компонентами.
Как это должно работать
Вкратце суть такова: мы подключаемся к bluetooth-модулю и передаём некий код (от 1 до 4 включительно). Если код валиден, то зажигается один из трёх цветов или же светодиоды начинают мигать всеми возможными цветами (красный, зелёный, синий) в течение какого-то времени.
Приготовления
Для начала необходимо собрать рабочую схему и загрузить Arduino-скетч. Ниже я даю схему (рис. 1) и код скетча, которые у меня получились.
Рис. 1 (Схема для сборки)
#include <SoftwareSerial.h>
int green_pin = 2;
int red_pin = 3;
int blue_pin = 4;
int BLINK_STEPS = 3;
int BLINK_DELAY = 100;
SoftwareSerial mySerial(7, 8); // RX, TX
void setup() {
Serial.begin(9600);
mySerial.begin(9600);
pinMode(green_pin, OUTPUT);
pinMode(red_pin, OUTPUT);
pinMode(blue_pin, OUTPUT);
}
int code;
void loop() {
if (mySerial.available()) {
code = mySerial.read();
shutDownAll();
if (code > 0 && code < 5) {
analogWrite(code, 200);
}
if (code == 1) {
blinked();
}
}
}
void shutDownAll() {
analogWrite(green_pin, 0);
analogWrite(red_pin, 0);
analogWrite(blue_pin, 0);
}
void blinked() {
int steps = 0;
while(steps <= BLINK_STEPS) {
analogWrite(green_pin, 200);
delay(BLINK_DELAY);
analogWrite(green_pin, 0);
delay(BLINK_DELAY);
analogWrite(red_pin, 200);
delay(BLINK_DELAY);
analogWrite(red_pin, 0);
delay(BLINK_DELAY);
analogWrite(blue_pin, 200);
delay(BLINK_DELAY);
analogWrite(blue_pin, 0);
delay(BLINK_DELAY);
steps += 1;
}
}
Последнее приготовление
Итак, мы загрузили скетч, подключили схему к питанию. Что дальше? Для работы с Web Bluetooth API нам необходимо знать имя нашего устройства, и к какому сервису мы хотим получить доступ. Для этого можно воспользоваться приложением “nRF Connect”.
Включаем приложение и видим список bluetooth-устройств рядом с нами (рис. 2).
Рис. 2 (Список устройств, которое нашло приложение)
Устройство с именем “CC41-A” меня заинтересовало и не зря.
После подключения к устройству нам становится доступен список его сервисов (рис. 3). Вряд ли мы найдём что-то интересное в “Device information”, так что смело жмём в “Unknown service”.
Рис. 3 (Список сервисов устройства)
На скриншоте ниже (рис. 4) можно заметить самое главное для нас: запись в характеристику и её чтение.
Когда я решал задачу, описанную выше, то попробовал отправить в характеристику значение «2». В результате чего моя пара светодиодов начала гореть зелёным цветом. Почти успех. Теперь надо сделать тоже самое, но не через мобильное приложение, а через браузер.
Рис. 4 (Unknown характеристика)
Вот список данных, которые мы получили из приложения для продолжения выполнения задачи:
- Имя устройства.
- UID сервиса.
- UID характеристики.
Реализация в web
Прежде чем начать писать JavaScript-код, стоит оговорить несколько моментов:
- API является экспериментальным.
- Работает в Chrome и Samsung Internet.
- Необходимо подключение через HTTPS.
- Я не буду приводить примеры HTML и CSS-кода, так как в рамках данной статьи в них нет ничего интересного, но оставлю ссылку на репозиторий и сайт в конце статьи.
JavaScript
Работа с Web Bluetooth API построена на Promise. Ниже я буду приводить поэтапные примеры кода. Полный исходный код можно будет найти в репозитории, на который будет оставлена ссылка.
Для начала нам необходимо подключиться к устройству. Мы запрашиваем устройства и в фильтре передаём имя устройства и UID сервиса, с которыми будем работать. Если не указать сервис заранее, то в дальнейшем с ним нельзя будет взаимодействовать.
navigator.bluetooth.requestDevice({
filters:
[
{ name: MY_BLUETOOTH_NAME },
{ services: [SEND_SERVICE] },
]
})
После того как мы нажмём на кнопку “Connect”, у нас откроется окно (рис. 5), в котором необходимо выбрать устройство и нажать на кнопку подключения.
Рис. 5 (Окно с доступным к подключению устройством)
При подключении возвращается Promise, содержащий “device”, к которому можно подключиться. Окей, давайте запишем его в переменную и создадим соединение.
.then(device => {
myDevice = device;
return device.gatt.connect();
})
После этого нам возвращается Promise, содержащий “server”. Затем мы у “server” запрашиваем “service”, передавая туда UID сервиса (который мы подсмотрели через приложение). Затем нам возвращается Promise, содержащий “service”, у которого мы запрашиваем “characteristic”, передавая её UID (который мы тоже подсмотрели через приложение).
.then(server => server.getPrimaryService(SEND_SERVICE))
.then(service => service.getCharacteristic(SEND_SERVICE_CHARACTERISTIC))
И вот только теперь мы можем уже начинать что-то делать. Например, я запоминаю характеристику в переменную и вешаю обработчики кликов на кнопки. В своих data-атрибутах они содержат код, который будет записан в характеристику при клике.
В обработчиках кликов по кнопкам содержится следующий код:
const code = Number(event.target.dataset.code);
if (code === 1) {
toggleLigthCharacteristic.writeValue(Uint8Array.of(code));
return;
}
toggleLigthCharacteristic.readValue()
.then(currentCode => {
const convertedCode = currentCode.getUint8(0);
toggleLigthCharacteristic.writeValue(
Uint8Array.of(convertedCode === code ? 0 : code)
);
});
В характеристику необходимо передавать массив uint8, поэтому для преобразования кода, который будет передан в неё, необходимо использовать Uint8Array.
По задумке, код 1 заставляет светодиоды мигать тремя цветами и затем гаснуть. Но как погасить светодиод, если в него был передан код 3 и светодиод всё ещё горит? Или включить другой цвет?
Я считываю значение, лежащее в характеристике, преобразую его с помощью getUint8 и, если код совпадает, отправляю любое невалидное значение (например 0). Если же значение валидное, то преобразую его в массив unit8 и записываю в характеристику.
Для окончательного решения поставленной задачи необходимо всего лишь научиться отключаться от устройства. У нас уже есть eventListener на кнопке “Disconnect”, в котором происходит отключение от bluetooth-устройства, снимаются eventListeners, кнопки управления прячутся, а в переменные записывается undefined.
myDevice.gatt.disconnect();
toggleItemsEventListeners('removeEventListener');
toggleButtonsVisible();
toggleLigthCharacteristic = undefined;
myDevice = undefined;
Итог
Мы создали простую web-страницу, с помощью которой можно подключаться к bluetooth-устройству и управлять им. Как видите, это довольно просто. А устройства, которые вы можете собирать и управлять таким образом, ограничены лишь вашей фантазией!