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

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


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

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


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


  1. API является экспериментальным.
  2. Работает в Chrome и Samsung Internet.
  3. Необходимо подключение через HTTPS.
  4. Я не буду приводить примеры 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-устройству и управлять им. Как видите, это довольно просто. А устройства, которые вы можете собирать и управлять таким образом, ограничены лишь вашей фантазией!


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


Комментарии (4)


  1. Vladimir_Feschenko
    09.07.2018 17:00

    Почему для разных компонент RGB LED одинаковые номиналы гасящих резисторов?


  1. u-235
    09.07.2018 18:04

    Работает не во всех браузерах

    Работает только в хроме.


    1. Binjo Автор
      09.07.2018 18:08

      Спасибо.


  1. 0xf0a00
    10.07.2018 10:07
    +1

    тык
    image