Komunikacja z urządzeniami Bluetooth przez JavaScript

Interfejs Web Bluetooth API umożliwia witrynom komunikowanie się z urządzeniami Bluetooth.

François Beaufort
François Beaufort

A gdyby tak witryny mogły komunikować się z urządzeniami Bluetooth w pobliżu w sposób bezpieczny i zapewniający ochronę prywatności? Dzięki temu monitory pracy serca, śpiewające żarówki, a nawet żółwie mogą wchodzić w bezpośrednią interakcję ze stroną internetową.

Do tej pory interakcja z urządzeniami Bluetooth była możliwa tylko w przypadku aplikacji na konkretne platformy. Interfejs Web Bluetooth API ma to zmienić i umożliwić korzystanie z Bluetootha w przeglądarkach.

Zanim zaczniemy

Zakładamy, że masz podstawową wiedzę o tym, jak działają Bluetooth Low Energy (BLE) i Generic Attribute Profile.

Chociaż specyfikacja interfejsu Web Bluetooth API nie jest jeszcze ukończona, autorzy specyfikacji aktywnie poszukują deweloperów, którzy chcieliby wypróbować ten interfejs API i przesłać opinię na temat specyfikacji oraz opinię na temat implementacji.

Podzbiór interfejsu Web Bluetooth API jest dostępny w ChromeOS, Chrome na Androida 6.0, macOS (Chrome 56) i Windows 10 (Chrome 70). Oznacza to, że powinna być możliwość wysyłania żądańnawiązywania połączeń z pobliskimi urządzeniami Bluetooth Low Energy, odczytywaniazapisywania charakterystyk Bluetooth, otrzymywania powiadomień GATT, sprawdzania, kiedy urządzenie Bluetooth zostanie odłączone, a nawet odczytywania i zapisywania deskryptorów Bluetooth. Więcej informacji znajdziesz w tabeli Zgodność z przeglądarkami na stronie MDN.

W przypadku systemu Linux i starszych wersji systemu Windows włącz flagę #experimental-web-platform-features w about://flags.

Dostępne w ramach testów pochodzenia

Aby uzyskać jak najwięcej opinii od deweloperów korzystających z interfejsu Web Bluetooth API w praktyce, Chrome dodał tę funkcję w Chrome 53 jako wersję próbną origin na ChromeOS, Androida i Maca.

Okres próbny zakończył się w styczniu 2017 roku.

Wymagania dotyczące bezpieczeństwa

Aby zrozumieć kompromisy w zakresie bezpieczeństwa, polecam post Web Bluetooth Security Model (Model bezpieczeństwa Web Bluetooth) autorstwa Jeffreya Yasskina, inżyniera oprogramowania w zespole Chrome, który pracuje nad specyfikacją interfejsu Web Bluetooth API.

Tylko HTTPS

Ten eksperymentalny interfejs API to nowa, zaawansowana funkcja dodana do internetu, dlatego jest dostępna tylko w bezpiecznych kontekstach. Oznacza to, że musisz pamiętać o TLS.

Wymagany gest użytkownika

Ze względów bezpieczeństwa wykrywanie urządzeń Bluetooth za pomocą navigator.bluetooth.requestDevice musi być wywoływane przez gest użytkownika, taki jak dotknięcie lub kliknięcie myszą. Chodzi o słuchanie zdarzeń pointerup, clicktouchend.

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

Przejdź do kodu

Interfejs Web Bluetooth API w dużej mierze opiera się na obietnicach JavaScript. Jeśli nie znasz tego pojęcia, zapoznaj się z tym samouczkiem dotyczącym obietnic. Jeszcze jedno: () => {} to funkcje strzałkowe ECMAScript 2015.

Żądanie dostępu do urządzeń Bluetooth

Ta wersja specyfikacji interfejsu Web Bluetooth API umożliwia stronom działającym w roli Central łączenie się ze zdalnymi serwerami GATT przez połączenie BLE. Umożliwia komunikację między urządzeniami z Bluetoothem 4.0 lub nowszym.

Gdy witryna poprosi o dostęp do urządzeń w pobliżu za pomocą interfejsu API navigator.bluetooth.requestDevice, przeglądarka wyświetli użytkownikowi selektor urządzeń, w którym może on wybrać jedno urządzenie lub anulować prośbę.

Prośba o potwierdzenie przez użytkownika urządzenia Bluetooth.

Funkcja navigator.bluetooth.requestDevice() przyjmuje obowiązkowy obiekt, który definiuje filtry. Te filtry służą do zwracania tylko urządzeń, które pasują do niektórych reklamowanych usług Bluetooth GATT lub nazwy urządzenia.

Filtr usług

Na przykład aby poprosić o urządzenia Bluetooth reklamujące usługę baterii Bluetooth GATT:

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

Jeśli usługa Bluetooth GATT nie znajduje się na liście standardowych usług Bluetooth GATT, możesz podać pełny identyfikator UUID Bluetooth lub jego skróconą 16- lub 32-bitową formę.

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

Filtr nazwy

Możesz też wysyłać żądania dotyczące urządzeń Bluetooth na podstawie nazwy urządzenia, która jest reklamowana za pomocą klucza name filters, lub nawet prefiksu tej nazwy za pomocą klucza namePrefix filters. Pamiętaj, że w tym przypadku musisz też zdefiniować klucz optionalServices, aby mieć dostęp do usług, które nie są uwzględnione w filtrze usług. W przeciwnym razie podczas próby uzyskania do nich dostępu pojawi się błąd.

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

Filtr danych producenta

Można też wysyłać żądania dotyczące urządzeń Bluetooth na podstawie danych specyficznych dla producenta, które są reklamowane za pomocą klucza manufacturerData filters. Ten klucz to tablica obiektów z obowiązkowym kluczem identyfikatora firmy Bluetooth o nazwie companyIdentifier. Możesz też podać prefiks danych, który filtruje dane producenta z urządzeń Bluetooth, które się nim zaczynają. Pamiętaj, że musisz też zdefiniować klucz optionalServices, aby mieć dostęp do wszystkich usług, które nie są uwzględnione w filtrze usług. Jeśli tego nie zrobisz, później podczas próby uzyskania do nich dostępu pojawi się błąd.

// 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); });

Maski można też używać z prefiksami danych, aby dopasowywać niektóre wzorce w danych producenta. Więcej informacji znajdziesz w wyjaśnieniu filtrów danych Bluetooth.

Filtry wykluczania

Opcja exclusionFiltersnavigator.bluetooth.requestDevice() umożliwia wykluczenie niektórych urządzeń z selektora przeglądarek. Można go używać do wykluczania urządzeń, które pasują do szerszego filtra, ale nie są obsługiwane.

// 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); });

Brak filtrów

Zamiast filters możesz użyć klawisza acceptAllDevices, aby wyświetlić wszystkie pobliskie urządzenia Bluetooth. Aby uzyskać dostęp do niektórych usług, musisz też zdefiniować optionalServicesklucz. W przeciwnym razie podczas próby uzyskania do nich dostępu pojawi się błąd.

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

Nawiązywanie połączenia z urządzeniem Bluetooth

Co teraz zrobisz, gdy masz BluetoothDevice? Połączmy się ze zdalnym serwerem GATT Bluetooth, który zawiera definicje usług i charakterystyk.

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); });

Odczytywanie charakterystyki Bluetooth

W tym miejscu łączymy się z serwerem GATT zdalnego urządzenia Bluetooth. Teraz chcemy uzyskać podstawową usługę GATT i odczytać charakterystykę należącą do tej usługi. Spróbujmy na przykład odczytać aktualny poziom naładowania baterii urządzenia.

W przykładzie poniżej battery_level to standardowy poziom baterii Charakterystyka.

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); });

Jeśli używasz niestandardowej charakterystyki Bluetooth GATT, możesz podać pełny identyfikator UUID Bluetooth lub jego krótką 16- lub 32-bitową formę w service.getCharacteristic.

Pamiętaj, że możesz też dodać characteristicvaluechanged do charakterystyki, aby obsługiwać odczytywanie jej wartości. Zapoznaj się z przykładem Read Characteristic Value Changed Sample, aby dowiedzieć się, jak opcjonalnie obsługiwać nadchodzące powiadomienia 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);
}

Zapisywanie w charakterystyce Bluetooth

Zapisywanie w charakterystyce GATT Bluetooth jest tak samo proste jak odczytywanie. Tym razem użyjemy punktu kontrolnego tętna, aby zresetować wartość pola Wydatkowana energia do 0 na urządzeniu monitorującym tętno.

Obiecuję, że nie ma tu żadnej magii. Wszystko to zostało wyjaśnione na stronie dotyczącej charakterystyki punktu kontroli tętna.

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); });

Otrzymywanie powiadomień GATT

Teraz zobaczmy, jak otrzymywać powiadomienia o zmianach charakterystyki Pomiar tętna na urządzeniu:

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
}

przykładzie powiadomień pokazujemy, jak zatrzymać powiadomienia za pomocą funkcji stopNotifications() i prawidłowo usunąć dodany characteristicvaluechanged detektor zdarzeń.

Rozłączanie urządzenia Bluetooth

Aby zapewnić większą wygodę użytkownikom, możesz nasłuchiwać zdarzeń odłączenia i zachęcać użytkowników do ponownego połączenia:

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.`);
}

Możesz też zadzwonić pod numer device.gatt.disconnect(), aby odłączyć aplikację internetową od urządzenia Bluetooth. Spowoduje to wywołanie istniejących odbiorników zdarzeń gattserverdisconnected. Pamiętaj, że nie zatrzyma to komunikacji urządzenia Bluetooth, jeśli inna aplikacja już się z nim komunikuje. Aby dowiedzieć się więcej, zapoznaj się z przykładowym kodem odłączania urządzeniaprzykładowym kodem automatycznego ponownego łączenia.

Odczytywanie i zapisywanie deskryptorów Bluetooth

Deskryptory Bluetooth GATT to atrybuty opisujące wartość charakterystyki. Możesz je odczytywać i zapisywać w podobny sposób jak charakterystyki Bluetooth GATT.

Zobaczmy na przykład, jak odczytać opis użytkownika dotyczący interwału pomiarowego termometru zdrowia urządzenia.

W przykładzie poniżej health_thermometer to usługa termometru medycznego, measurement_interval to cecha interwału pomiarowego, a gatt.characteristic_user_description to deskryptor opisu cechy przez użytkownika.

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); });

Po przeczytaniu opisu użytkownika dotyczącego interwału pomiarowego termometru zdrowia urządzenia zobaczmy, jak go zaktualizować i zapisać wartość niestandardową.

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); });

Przykłady, wersje demonstracyjne i ćwiczenia z programowania

Wszystkie przykłady Web Bluetooth poniżej zostały przetestowane. Aby w pełni wykorzystać te przykłady, zalecam zainstalowanie [aplikacji na Androida BLE Peripheral Simulator], która symuluje urządzenie peryferyjne BLE z usługą baterii, usługą pomiaru tętna lub usługą termometru medycznego.

Początkujący

  • Informacje o urządzeniu – pobieranie podstawowych informacji o urządzeniu BLE.
  • Battery Level (Poziom baterii) – pobieranie informacji o baterii z urządzenia BLE, które rozgłasza informacje o baterii.
  • Reset Energy (Resetuj energię) – resetuje energię wydatkowaną przez urządzenie BLE, które reklamuje tętno.
  • Właściwości charakterystyczne – wyświetla wszystkie właściwości konkretnej charakterystyki z urządzenia BLE.
  • Powiadomienia – włączanie i wyłączanie powiadomień o charakterystyce z urządzenia BLE.
  • Rozłączenie urządzenia – rozłączanie urządzenia BLE i otrzymywanie powiadomień o rozłączeniu po nawiązaniu z nim połączenia.
  • Get Characteristics (Pobierz charakterystyki) – pobierz wszystkie charakterystyki reklamowanej usługi z urządzenia BLE.
  • Get Descriptors (Pobierz deskryptory) – pobiera wszystkie deskryptory cech reklamowanej usługi z urządzenia BLE.
  • Filtr danych producenta – pobiera podstawowe informacje o urządzeniu z urządzenia BLE, które pasuje do danych producenta.
  • Filtry wykluczające – pobieranie podstawowych informacji o urządzeniu z urządzenia BLE z podstawowymi filtrami wykluczającymi.

Łączenie wielu operacji

  • GAP Characteristics (Charakterystyki GAP) – pobieranie wszystkich charakterystyk GAP urządzenia BLE.
  • Device Information Characteristics (Charakterystyka informacji o urządzeniu) – pobieranie wszystkich charakterystyk informacji o urządzeniu BLE.
  • Link Loss – ustawianie charakterystyki Alert Level urządzenia BLE (readValue i writeValue).
  • Odkrywanie usług i cech – odkrywanie wszystkich dostępnych usług podstawowych i ich cech na urządzeniu BLE.
  • Automatyczne ponowne łączenie – ponowne łączenie z odłączonym urządzeniem BLE za pomocą algorytmu wzrastającego czasu do ponowienia.
  • Odczytanie zmiany wartości charakterystyki – odczytywanie poziomu baterii i otrzymywanie powiadomień o zmianach z urządzenia BLE.
  • Read Descriptors (Odczytaj deskryptory) – odczytuje wszystkie deskryptory charakterystyki usługi z urządzenia BLE.
  • Write Descriptor (Zapisz deskryptor) – zapisuje w deskryptorze „Characteristic User Description” na urządzeniu BLE.

Zapoznaj się też z naszymi wybranymi wersjami demonstracyjnymi Web Bluetoothoficjalnymi ćwiczeniami Web Bluetooth.

Biblioteki

  • web-bluetooth-utils to moduł npm, który dodaje do interfejsu API kilka przydatnych funkcji.
  • noble, najpopularniejszym centralnym module BLE Node.js, dostępny jest shim interfejsu Web Bluetooth API. Dzięki temu możesz używać narzędzia webpack/browserify do pakowania modułu noble bez konieczności korzystania z serwera WebSocket ani innych wtyczek.
  • angular-web-bluetooth to moduł dla Angulara, który usuwa cały kod standardowy potrzebny do skonfigurowania interfejsu Web Bluetooth API.

Narzędzia

  • Get Started with Web Bluetooth to prosta aplikacja internetowa, która wygeneruje cały kod JavaScript potrzebny do rozpoczęcia interakcji z urządzeniem Bluetooth. Wpisz nazwę urządzenia, usługi lub cechy, zdefiniuj jej właściwości i gotowe.
  • Jeśli jesteś już deweloperem Bluetooth, wtyczka Web Bluetooth Developer Studio wygeneruje też kod JavaScript Web Bluetooth dla Twojego urządzenia Bluetooth.

Wskazówki

W Chrome dostępna jest strona Bluetooth Internals (about://bluetooth-internals), na której możesz sprawdzić wszystkie informacje o pobliskich urządzeniach Bluetooth: stan, usługi, charakterystyki i deskryptory.

Zrzut ekranu strony wewnętrznej do debugowania Bluetootha w Chrome
Strona wewnętrzna w Chrome do debugowania urządzeń Bluetooth.

Zalecam też zapoznanie się z oficjalną stroną How to file Web Bluetooth bugs, ponieważ debugowanie Bluetootha może być czasami trudne.

Co dalej?

Najpierw sprawdź stan implementacji w przeglądarce i na platformie, aby dowiedzieć się, które części interfejsu Web Bluetooth API są obecnie wdrażane.

Chociaż nie jest jeszcze kompletna, oto zapowiedź tego, czego możesz się spodziewać w najbliższej przyszłości:

  • Skanowanie w poszukiwaniu reklam BLE w pobliżu będzie się odbywać z użyciem navigator.bluetooth.requestLEScan().
  • Nowe zdarzenie serviceadded będzie śledzić nowo wykryte usługi Bluetooth GATT, a zdarzenie serviceremoved będzie śledzić usunięte usługi. Nowe servicechangedzdarzenie zostanie wywołane, gdy do usługi Bluetooth GATT zostanie dodana lub z niej usunięta dowolna charakterystyka lub deskryptor.

Wyrażanie poparcia dla interfejsu API

Czy zamierzasz używać interfejsu Web Bluetooth API? Twoje publiczne wsparcie pomaga zespołowi Chrome ustalać priorytety funkcji i pokazuje innym dostawcom przeglądarek, jak ważne jest ich obsługiwanie.

Wyślij tweeta do @ChromiumDev z hasztagiem #WebBluetooth i napisz, gdzie i jak korzystasz z tej funkcji.

Zasoby

Podziękowania

Dziękujemy Kayce Basques za sprawdzenie.