Связь с устройствами Bluetooth через JavaScript

API Web Bluetooth позволяет веб-сайтам взаимодействовать с устройствами Bluetooth.

Франсуа Бофор
François Beaufort

Что, если бы я сказал вам, что веб-сайты могут безопасно и с сохранением конфиденциальности взаимодействовать с находящимися поблизости устройствами Bluetooth? Таким образом, пульсометры, поющие лампочки и даже черепахи смогут напрямую взаимодействовать с веб-сайтом.

До сих пор возможность взаимодействия с устройствами Bluetooth была возможна только для приложений, специфичных для конкретной платформы. API Web Bluetooth призван изменить эту ситуацию и перенести его и в веб-браузеры.

Прежде чем мы начнем

В этом документе предполагается, что у вас есть базовые знания о том, как работают Bluetooth Low Energy (BLE) и общий профиль атрибутов .

Несмотря на то, что спецификация Web Bluetooth API еще не доработана, авторы спецификации активно ищут разработчиков-энтузиастов, которые могли бы опробовать этот API и оставить отзыв о спецификации и отзывах о реализации .

Подмножество Web Bluetooth API доступно в ChromeOS, Chrome для Android 6.0, Mac (Chrome 56) и Windows 10 (Chrome 70). Это означает, что вы должны иметь возможность запрашивать и подключаться к ближайшим устройствам Bluetooth Low Energy, считывать / записывать характерис��ики Bluetooth, получать уведомления GATT , знать, когда устройство Bluetooth отключается , и даже читать и записывать дескрипторы Bluetooth . Дополнительную информацию см. в таблице совместимости браузеров MDN.

Для Linux и более ранних версий Windows включите флаг #experimental-web-platform-features в about://flags .

Доступно для исходных пробных версий

Чтобы получить как можно больше отзывов от разработчиков, использующих Web Bluetooth API на местах, Chrome ранее добавил эту функцию в Chrome 53 в качестве исходной пробной версии для ChromeOS, Android и Mac.

Судебный процесс успешно завершился в январе 2017 года.

Требования безопасности

Чтобы понять компромиссы в области безопасности, я рекомендую статью о модели безопасности Web Bluetooth от Джеффри Яскина, инженера-программиста из команды Chrome, ��а��отаю��е��о над спецификацией API Web Bluetooth.

только HTTPS

Поскольку этот экспериментальный API представляет собой новую мощную функцию, добавленную в Интернет, он доступен только для защищенных контекстов . Это означает, что вам нужно будет строить с учетом TLS .

Требуется жест пользователя

В целях безопасности обнаружение устройств Bluetooth с помощью navigator.bluetooth.requestDevice должно запускаться жестом пользователя , например прикосновением или щелчком мыши. Мы говорим о прослушивании событий pointerup , click и touchend .

button.addEventListener('pointerup', function(event) {
  // Call navigator.bluetooth.requestDevice
});

Вникнуть в код

API Web Bluetooth в значительной степени опирается на обещания JavaScript. Если вы с ними не знакомы, ознакомьтесь с этим замечательным руководством по Promises . Еще одна вещь: () => {} — это стрелочные функции ECMAScript 2015.

Запросить устройства Bluetooth

Эта версия спецификации Web Bluetooth API позволяет веб-сайтам, работающим в центральной роли, подключаться к удаленным серверам GATT через соединение BLE. Он поддерживает связь между устройствами, поддерживающими Bluetooth 4.0 или более поздней версии.

Когда веб-сайт запрашивает доступ к близлежащим устройствам с помощью navigator.bluetooth.requestDevice , браузер предлагает пользователю выбрать устройство, где он может выбрать одно устройство или отменить ��апрос.

Подсказка пользователя устройства Bluetooth.

Функция navigator.bluetooth.requestDevice() принимает обязательный объект, определяющий фильтры. Эти фильтры используются для возврата только устройств, которые соответствуют некоторым рекламируемым службам Bluetooth GATT и/или имени устройства.

Фильтр услуг

Например, чтобы запросить устройства Bluetooth, рекламирующие службу аккумуляторов Bluetooth GATT :

navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => { /* … */ })
.catch(error => { console.error(error); });

Если вашей службы Bluetooth GATT нет в списке стандартизированных служб Bluetooth GATT , вы можете предоставить либо полный UUID Bluetooth, либо короткую 16- или 32-битную форму.

navigator.bluetooth.requestDevice({
  filters: [{
    services: [0x1234, 0x12345678, '99999999-0000-1000-8000-00805f9b34fb']
  }]
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

Фильтр имен

Вы также можете запросить устройства Bluetooth на основе имени устройства, рекламируемого с помощью ключа фильтров name , или даже префикса этого имени с помощью ключа фильтров namePrefix . Обратите внимание, что в этом случае вам также необходимо будет определить ключ optionalServices , чтобы иметь возможность доступа к любым службам, не включенным в фильтр служб. Если вы этого не сделаете, позже при попытке доступа к ним вы получите сообщение об ошибке.

navigator.bluetooth.requestDevice({
  filters: [{
    name: 'Francois robot'
  }],
  optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

Фильтр данных производителя

Также можно запросить устройства Bluetooth на основе конкретных данных производителя, которые объявляются с помощью ключа фильтров manufacturerData . Этот ключ представляет собой массив объектов с обязательным ключом идентификатора компании Bluetooth с именем companyIdentifier . Вы также можете указать префикс данных, который фильтрует данные производителя с устройств Bluetooth, которые начинаются с него. Обратите внимание, что вам также потребуется определить ключ optionalServices , чтобы иметь возможность доступа к любым службам, не включенным в фильтр служб. Если вы этого не сделаете, позже при попытке доступа к ним вы получите сообщение об ошибке.

// Filter Bluetooth devices from Google company with manufacturer data bytes
// that start with [0x01, 0x02].
navigator.bluetooth.requestDevice({
  filters: [{
    manufacturerData: [{
      companyIdentifier: 0x00e0,
      dataPrefix: new Uint8Array([0x01, 0x02])
    }]
  }],
  optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

Маску также можно использовать с префиксом данных для соответствия некоторым шаблонам в данных производителя. Чтобы узнать больше, ознакомьтесь с объяснением фильтров данных Bluetooth .

Фильтры исключения

Параметр exclusionFilters в navigator.bluetooth.requestDevice() позволяет исключить некоторые устройства из средства выбора браузера. Его можно использовать для исключения устройств, соответствующих более широкому фильтру, но не поддерживаемых.

// Request access to a bluetooth device whose name starts with "Created by".
// The device named "Created by Francois" has been reported as unsupported.
navigator.bluetooth.requestDevice({
  filters: [{
    namePrefix: "Created by"
  }],
  exclusionFilters: [{
    name: "Created by Francois"
  }],
  optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

Без фильтров

Наконец, вместо filters вы можете использовать клавишу acceptAllDevices , чтобы отобразить все ближайшие устройства Bluetooth. Вам также потребуется определить ключ optionalServices , чтобы иметь доступ к некоторым сервисам. Если вы этого не сделаете, позже при попытке доступа к ним вы получите сообщение об ошибке.

navigator.bluetooth.requestDevice({
  acceptAllDevices: true,
  optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

Подключитесь к устройству Bluetooth

Итак, что же делать теперь, когда у вас есть BluetoothDevice ? Давайте подключимся к удаленному серверу GATT Bluetooth, который содержит определения служб и характеристик.

navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => {
  // Human-readable name of the device.
  console.log(device.name);

  // Attempts to connect to remote GATT Server.
  return device.gatt.connect();
})
.then(server => { /* … */ })
.catch(error => { console.error(error); });

Чтение характеристики Bluetooth

Здесь мы подключаемся к GATT-серверу удаленного устройства Bluetooth. Теперь мы хотим по��уч��ть ��е��в��чн��ю услугу GATT и прочитать характеристику, принадлежащую этой службе. Попробуем, например, прочитать текущий уровень заряда аккумулятора устройства.

В приведенном ниже примере battery_level — это стандартизированная характеристика уровня заряда батареи .

navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => device.gatt.connect())
.then(server => {
  // Getting Battery Service…
  return server.getPrimaryService('battery_service');
})
.then(service => {
  // Getting Battery Level Characteristic…
  return service.getCharacteristic('battery_level');
})
.then(characteristic => {
  // Reading Battery Level…
  return characteristic.readValue();
})
.then(value => {
  console.log(`Battery percentage is ${value.getUint8(0)}`);
})
.catch(error => { console.error(error); });

Если вы используете пользовательскую характеристику Bluetooth GATT, вы можете предоставить для service.getCharacteristic либо полный UUID Bluetooth, либо короткую 16- или 32-битную форму.

Обратите внимание, что вы также можете добавить прослушиватель событий characteristicvaluechanged » для характеристики, чтобы обрабатывать чтение ее значения. Ознакомьтесь с примером чтения измененного значения характеристики , чтобы узнать, как дополнительно обрабатывать предстоящие уведомления GATT.

…
.then(characteristic => {
  // Set up event listener for when characteristic value changes.
  characteristic.addEventListener('characteristicvaluechanged',
                                  handleBatteryLevelChanged);
  // Reading Battery Level…
  return characteristic.readValue();
})
.catch(error => { console.error(error); });

function handleBatteryLevelChanged(event) {
  const batteryLevel = event.target.value.getUint8(0);
  console.log('Battery percentage is ' + batteryLevel);
}

Запись в характеристику Bluetooth

Записать характеристику Bluetooth GATT так же просто, как и прочитать ее. На этот раз давайте воспользуемся точкой контроля сердечного ритма, чтобы сбросить значение поля «Затраченная энергия» на 0 на устройстве мониторинга сердечного ритма.

Обещаю, здесь нет никакой магии. Все это объясняется на странице «Характеристика контрольной точки сердечного ритма» .

navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('heart_rate'))
.then(service => service.getCharacteristic('heart_rate_control_point'))
.then(characteristic => {
  // Writing 1 is the signal to reset energy expended.
  const resetEnergyExpended = Uint8Array.of(1);
  return characteristic.writeValue(resetEnergyExpended);
})
.then(_ => {
  console.log('Energy expended has been reset.');
})
.catch(error => { console.error(error); });

Получать уведомления ГАТТ

Теперь давайте посмотрим, как получать уведомления об изменении характеристики измерения сердечного ритма на устройстве:

navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('heart_rate'))
.then(service => service.getCharacteristic('heart_rate_measurement'))
.then(characteristic => characteristic.startNotifications())
.then(characteristic => {
  characteristic.addEventListener('characteristicvaluechanged',
                                  handleCharacteristicValueChanged);
  console.log('Notifications have been started.');
})
.catch(error => { console.error(error); });

function handleCharacteristicValueChanged(event) {
  const value = event.target.value;
  console.log('Received ' + value);
  // TODO: Parse Heart Rate Measurement value.
  // See https://github.com/WebBluetoothCG/demos/blob/gh-pages/heart-rate-sensor/heartRateSensor.js
}

В примере уведомлений показано, как остановить уведомления с помощью stopNotifications() и правильно удалить добавленный прослушиватель событий characteristicvaluechanged .

Отключиться от устройства Bluetooth

Чтобы обеспечить лучшее взаимодействие с пользователем, вы можете прослушивать события отключения и предлагать пользователю повторно подключиться:

navigator.bluetooth.requestDevice({ filters: [{ name: 'Francois robot' }] })
.then(device => {
  // Set up event listener for when device gets disconnected.
  device.addEventListener('gattserverdisconnected', onDisconnected);

  // Attempts to connect to remote GATT Server.
  return device.gatt.connect();
})
.then(server => { /* … */ })
.catch(error => { console.error(error); });

function onDisconnected(event) {
  const device = event.target;
  console.log(`Device ${device.name} is disconnected.`);
}

Вы также можете вызвать device.gatt.disconnect() , чтобы отключить веб-приложение от устройства Bluetooth. Это запустит существующие прослушиватели событий gattserverdisconnected . Обратите внимание, что связь с устройством Bluetooth НЕ прекратится, если другое приложение уже обменивается данными с устройством Bluetooth. Чтобы узнать больше, ознакомьтесь с примером отключения устройства и примером автоматического повторного подключения .

Чтение и запись в дескрипторы Bluetooth

Дескрипторы Bluetooth GATT — это атрибуты, описывающие характеристическое значение. Вы можете читать и записывать их аналогично характеристикам Bluetooth GATT.

Давайте посмотрим, например, как прочитать пользовательское описание интервала измерения термометра здоровья устройства.

В приведенном ниже примере health_thermometer — это сервис Health Thermometer , measurement_interval характеристика интервала измерения , а gatt.characteristic_user_description дескриптор описания характеристики пользователя .

navigator.bluetooth.requestDevice({ filters: [{ services: ['health_thermometer'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('health_thermometer'))
.then(service => service.getCharacteristic('measurement_interval'))
.then(characteristic => characteristic.getDescriptor('gatt.characteristic_user_description'))
.then(descriptor => descriptor.readValue())
.then(value => {
  const decoder = new TextDecoder('utf-8');
  console.log(`User Description: ${decoder.decode(value)}`);
})
.catch(error => { console.error(error); });

Теперь, когда мы прочитали пользовательское описание интервала измерения термометра здоровья устройства, давайте посмотрим, как его обновить и записать собственное значение.

navigator.bluetooth.requestDevice({ filters: [{ services: ['health_thermometer'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('health_thermometer'))
.then(service => service.getCharacteristic('measurement_interval'))
.then(characteristic => characteristic.getDescriptor('gatt.characteristic_user_description'))
.then(descriptor => {
  const encoder = new TextEncoder('utf-8');
  const userDescription = encoder.encode('Defines the time between measurements.');
  return descriptor.writeValue(userDescription);
})
.catch(error => { console.error(error); });

Примеры, демо и кодлабы

Все приведенные ниже образцы Web Bluetooth прошли успешное тестирование. Чтобы в полной мере насладиться этими образцами, я рекомендую вам установить [приложение BLE Peripheral Simulator для Android], которое имитирует периферийное устройство BLE с помощью службы батареи, службы измерения сердечного ритма или службы термометра здоровья.

Новичок

Объединение нескольких операций

Ознакомьтесь также с нашими тщательно подобранными демонстрациями Web Bluetooth и официальными лабораториями Web Bluetooth Codelabs .

Библиотеки

  • web-bluetooth-utils — это модуль npm, который добавляет в API некоторые удобные функции.
  • Оболочка Web Bluetooth API доступна в Node.js самом популярном центральном модуле BLE Node.js. Это позволяет вам использовать веб-пакеты/браузеры без необходимости использования сервера WebSocket или других плагинов.
  • angular-web-bluetooth — это модуль для Angular , который абстрагирует весь шаблон, необходимый для настройки веб-API Bluetooth.

Инструменты

  • Начало работы с веб-интерфейсом Bluetooth — это простое веб-приложение, которое сгенерирует весь шаблонный код JavaScript для начала взаимодействия с устройством Bluetooth. Введите имя устройства, услугу, характеристику, определите его свойства, и все готово.
  • Если вы уже являетесь разработчиком Bluetooth, плагин Web Bluetooth Developer Studio также сгенерирует JavaScript-код Web Bluetooth для вашего устройства Bluetooth.

Советы

Страница Bluetooth Internals доступна в Chrome по about://bluetooth-internals , чтобы вы могли проверить все о ближайших устройствах Bluetooth: статус, службы, характеристики и дескрипторы.

Скриншот внутренней страницы для отладки Bluetooth в Chrome
Внутренняя страница в Chrome для отладки устройств Bluetooth.

Я также рекомендую посетить официальную страницу «Как зарегистрировать ошибки Web Bluetooth», так как отладка Bluetooth иногда может быть затруднена.

Что дальше

Сначала проверьте статус реализации браузера и платформы , чтобы узнать, какие части Web Bluetooth API реализуются в данный момент.

Хотя он еще не завершен, вот краткий обзор того, чего ожидать в ближайшем будущем:

  • Сканирование близлежащих рекламных объявлений BLE будет выполняться с помощью navigator.bluetooth.requestLEScan() .
  • Новое событие serviceadded будет отслеживать вновь обнаруженные службы Bluetooth GATT, а событие serviceremoved будет отслеживать удаленные. Новое событие servicechanged сработает, когда какая-либо характеристика и/или дескриптор будет добавлена ​​или удалена из службы Bluetooth GATT.

Показать поддержку API

Планируете ли вы использовать Web Bluetooth API? Ваша публичная поддержка помогает команде Chrome расставлять приоритеты в функциях и показывает другим поставщикам браузеров, насколько важно их поддерживать.

Отправьте твит @ChromiumDev , используя хэштег #WebBluetooth , и сообщите нам, где и как вы его используете.

Ресурсы

Благодарности

Спасибо Кейси Басков за рецензию на эту статью. Изображение героя от SparkFun Electronics из Боулдера, США .