Управляем светодиодами с помощью Web Bluetooth API и Arduino

yn_ujbyh92tyfs0kwjztiq96dl0.jpeg


— Чайники, весы, игрушки, лампочки, кофемашины… В эти и другие устройства встраивают 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) и код скетча, которые у меня получились.


yjxxhb2khesa-j8qfodl7tu5f4s.png

Рис. 1 (Схема для сборки)

#include 

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).


rsofxgsudyj7lp3zjd7jridz5qc.jpeg

Рис. 2 (Список устройств, которое нашло приложение)

Устройство с именем «CC41-A» меня заинтересовало и не зря.

После подключения к устройству нам становится доступен список его сервисов (рис. 3). Вряд ли мы найдём что-то интересное в «Device information», так что смело жмём в «Unknown service».


xtqy5cgl8wvkvpsmzdklwa32a1g.jpeg

Рис. 3 (Список сервисов устройства)

На скриншоте ниже (рис. 4) можно заметить самое главное для нас: запись в характеристику и её чтение.

Когда я решал задачу, описанную выше, то попробовал отправить в характеристику значение »2». В результате чего моя пара светодиодов начала гореть зелёным цветом. Почти успех. Теперь надо сделать тоже самое, но не через мобильное приложение, а через браузер.


momrhoy4vm-vehhvpg6h9pomrse.jpeg

Рис. 4 (Unknown характеристика)

Вот список данных, которые мы получили из приложения для продолжения выполнения задачи:


  1. Имя устройства.
  2. UID сервиса.
  3. UID характеристики.


Реализация в web

Прежде чем начать писать JavaScript-код, стоит оговорить несколько моментов:


  1. API является экспериментальным.
  2. Необходимо подключение через HTTPS.
  3. Работает не во всех браузерах. Я запускал в Chrome 67 без флагов.
  4. Я не буду приводить примеры HTML и CSS-кода, так как в рамках данной статьи в них нет ничего интересного, но оставлю ссылку на репозиторий и сайт в конце статьи.


JavaScript

Работа с Web Bluetooth API построена на Promise. Ниже я буду приводить поэтапные примеры кода. Полный исходный код можно будет найти в репозитории, на который будет оставлена ссылка.

Для начала нам необходимо подключиться к устройству. Мы запрашиваем устройства и в фильтре передаём имя устройства и UID сервиса, с которыми будем работать. Если не указать сервис заранее, то в дальнейшем с ним нельзя будет взаимодействовать.

navigator.bluetooth.requestDevice({
    filters:
      [
        { name: MY_BLUETOOTH_NAME },
        { services: [SEND_SERVICE] },
      ]
  })

После того как мы нажмём на кнопку «Connect», у нас откроется окно (рис. 5), в котором необходимо выбрать устройство и нажать на кнопку подключения.


quwszczbtzekphhkccgqzebbyx0.png

Рис. 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-устройству и управлять им. Как видите, это довольно просто. А устройства, которые вы можете собирать и управлять таким образом, ограничены лишь вашей фантазией!


Полезные ссылки


© Habrahabr.ru