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

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

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

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

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

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

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

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

Подмножество API Web Bluetooth доступно в 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 из Боулдера, США .