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 co, jeśli powiem Ci, że witryny mogą komunikować się z bliskimi urządzeniami Bluetooth w sposób bezpieczny i nie naruszający prywatności? Dzięki temu monitory tętna, śpiewające żarówki, a nawet żółwie mogłyby wchodzić w interakcję bezpośrednio ze stroną internetową.

Do tej pory możliwość interakcji z urządzeniami Bluetooth była dostępna tylko w przypadku aplikacji przeznaczonych dla konkretnej platformy. Interfejs Web Bluetooth API ma na celu zmianę tego stanu rzeczy i wprowadza tę technologię również do przeglądarek internetowych.

Zanim zaczniemy

W tym dokumencie zakładamy, że masz podstawową wiedzę o tym, jak działa Bluetooth Low Energy (BLE) i Generic Attribute Profile (GAP).

Mimo że specyfikacja interfejsu Web Bluetooth API nie została jeszcze sfinalizowana, autorzy specyfikacji aktywnie poszukują entuzjastycznych deweloperów, którzy wypróbują ten interfejs API i prześlą opinie na temat specyfikacji oraz opinie na temat implementacji.

Podzbiór interfejsu API Bluetooth w internecie jest dostępny w ChromeOS, Chrome na Androida w wersji 6.0, na Macu (Chrome 56) i w Windows 10 (Chrome 70). Oznacza to, że powinna mieć możliwość wysyłanianawiązywania połączeń z bliskimi urządzeniami Bluetooth Low Energy, odczytywania/zapisywania właściwości Bluetooth, odbierania powiadomień GATT, rozpoznawania rozłączenia urządzenia Bluetooth, a nawet odczytywania i zapisywania w opisach Bluetooth. Więcej informacji znajdziesz w tabeli Zgodność przeglądarek w MDN.

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

Dostępne w wersjach próbnych origin

Aby uzyskać jak najwięcej informacji zwrotnych od deweloperów korzystających z interfejsu Web Bluetooth API w praktyce, wcześniej dodaliśmy tę funkcję w Chrome 53 jako test wersji źródłowej na potrzeby ChromeOS, Androida i Maca.

W styczniu 2017 r. zakończyliśmy testowanie.

Wymagania dotyczące bezpieczeństwa

Aby zrozumieć kompromisy związane z bezpieczeństwem, przeczytaj wpis Model zabezpieczeń Bluetooth w internecie autorstwa Jeffreya Yasskina, inżyniera oprogramowania z zespołu Chrome, który pracuje nad specyfikacją interfejsu Web Bluetooth API.

Tylko HTTPS

Ten eksperymentalny interfejs API to potężna nowa funkcja dodana do sieci, dlatego jest dostępna tylko w bezpiecznych kontekstach. Oznacza to, że musisz skompilować aplikację z uwzględnieniem protokołu TLS.

Wymagane działanie użytkownika

Ze względów bezpieczeństwa wykrywanie urządzeń Bluetooth za pomocą navigator.bluetooth.requestDevice musi być aktywowane przez gesty użytkownika, takie jak dotknięcie lub kliknięcie myszką. Mowa o odbieraniu zdarzeń pointerup, clicktouchend.

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

Poznaj kod

Interfejs Web Bluetooth API w dużej mierze korzysta z obietnic w JavaScript. Jeśli nie znasz tych funkcji, zapoznaj się z tym świetnym samouczkiem. Kolejną rzeczą, która nie jest obsługiwana, są funkcje strzałki ECMAScript 2015.() => {}

Prośba o urządzenia Bluetooth

Ta wersja specyfikacji interfejsu Web Bluetooth API umożliwia stronom internetowym działającym w roli centralnej nawiązywanie połączeń z odległymi serwerami GATT przez połączenie BLE. Obsługuje komunikację między urządzeniami z Bluetooth 4.0 lub nowszym.

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

Komunikat dla 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

Aby na przykład zażądać urządzeń Bluetooth reklamujących usługę Bluetooth GATT Battery Service:

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ą wersję 16- lub 32-bitową.

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

Filtr według nazwy

Możesz też żądać urządzeń Bluetooth na podstawie nazwy reklamowanego urządzenia, korzystając z klucza filtrów name, lub nawet z prefiksu tej nazwy, korzystając z klucza filtrów namePrefix. Pamiętaj, że w tym przypadku musisz też zdefiniować klucz optionalServices, aby uzyskać dostęp do usług nieobjętych filtrem usług. Jeśli tego nie zrobisz, później pojawi się błąd podczas próby uzyskania do nich dostępu.

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

Filtr danych o producencie

Można też wysyłać żądania dotyczące urządzeń Bluetooth na podstawie danych o poszczególnych producentach, które są reklamowane za pomocą klucza filtrów manufacturerData. Ten klucz jest tablicą obiektów z obowiązkowym kluczem identyfikatora firmy Bluetooth o nazwie companyIdentifier. Możesz też podać prefiks danych, który odfiltruje dane producenta z urządzeń Bluetooth, których nazwy zaczynają się od tego prefiksu. Pamiętaj, że aby uzyskać dostęp do usług, które nie są uwzględnione w filtrze usług, musisz też zdefiniować klucz optionalServices. Jeśli tego nie zrobisz, później, gdy spróbujesz uzyskać do nich dostęp, 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 prefiksem danych, aby dopasowywać niektóre wzorce w danych producenta. Aby dowiedzieć się więcej, zapoznaj się z opisem filtrów danych Bluetooth.

Filtry wykluczania

Opcja exclusionFilters w sekcji navigator.bluetooth.requestDevice() umożliwia wykluczenie niektórych urządzeń z selektora przeglądarek. Można go użyć do wykluczenia 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

Na koniec, zamiast filters, możesz użyć klawisza acceptAllDevices, aby wyświetlić wszystkie urządzenia Bluetooth w pobliżu. Aby uzyskać dostęp do niektórych usług, musisz też określić klucz optionalServices. Jeśli tego nie zrobisz, później, gdy spróbujesz uzyskać do nich dostęp, 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 zrobić, gdy masz BluetoothDevice? Połącz się z odległym 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 właściwości Bluetooth

Tutaj łączymy się z serwerem GATT zdalnego urządzenia Bluetooth. Teraz chcemy uzyskać usługę GATT Primary i odczytać jej cechę. Spróbujmy na przykład odczytać bieżący poziom naładowania baterii urządzenia.

W przykładzie poniżej battery_level to ujednolicony parametr Poziom baterii.

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 cechy GATT Bluetooth, możesz podać pełny identyfikator UUID Bluetooth lub jego 16- lub 32-bitową formę krótką.service.getCharacteristic

Pamiętaj, że możesz też dodać do cechy odbiornika zdarzenia characteristicvaluechanged, aby obsługiwać odczyt jego wartości. Zapoznaj się z przykładem odczytu wartości właściwości, 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 danych w charakterystyce GATT Bluetooth jest tak samo proste jak ich odczytywanie. Tym razem użyjemy punktu kontrolnego tętna, aby zresetować wartość pola Wydatek energii na urządzeniu monitorującym tętno do 0.

Zapewniam, że nie ma tu żadnej magii. Więcej informacji znajdziesz na stronie z opisem funkcji pomiaru tętna w aplikacji Punkty kardio.

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

Odbieranie powiadomień GATT

Teraz zobaczmy, jak otrzymywać powiadomienia, gdy na urządzeniu zmieni się wartość Pomiar 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_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ład Powiadomienia pokazuje, jak zatrzymać powiadomienia za pomocą stopNotifications() i prawidłowo usunąć dodany odbiornik zdarzeń characteristicvaluechanged.

Rozłączanie z urządzeniem Bluetooth

Aby zapewnić większą wygodę użytkownikom, możesz nasłuchiwać zdarzeń rozłączenia i zapraszać użytkowników do ponownego nawiązania 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ć na numer device.gatt.disconnect(), aby odłączyć aplikację internetową od urządzenia Bluetooth. Spowoduje to wywołanie istniejących słuchaczy zdarzeń gattserverdisconnected. Pamiętaj, że nie zatrzyma to komunikacji z urządzeniem Bluetooth, jeśli inna aplikacja już z nim komunikuje się. Aby dowiedzieć się więcej, zapoznaj się z przykładem DeviceDisconnectSampleAutomaticReconnectSample.

Odczytywanie i zapisywanie w opisach Bluetooth

Deskryptory GATT Bluetootha to atrybuty opisujące wartość charakterystyczną. Można je odczytywać i zapisywać w sposób podobny do właściwości Bluetooth GATT.

Zobaczmy na przykład, jak odczytać z opisu urządzenia opis interwału pomiarowego termometru stanu urządzenia.

W przykładzie poniżej health_thermometer to usługa Termometr zdrowotny, measurement_interval to charakterystyka Interval pomiaru, a gatt.characteristic_user_description to opis tej charakterystyki (element danych 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); });

Teraz, gdy już znamy opis interwału pomiarowego w termometrze medycznym urządzenia, zobaczmy, jak go zaktualizować i wprowadzić 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, demonstracje i ćwiczenia z programowania

Wszystkie próbki Web Bluetooth zostały przetestowane. Aby w pełni korzystać z tych próbek, zalecam zainstalowanie [BLE Peripheral Simulator Android App], który symuluje urządzenie peryferyjne BLE z usługą Battery Service, Heart Rate Service lub Health Thermometer Service.

Początkujący

  • Informacje o urządzeniu – pobieranie podstawowych informacji o urządzeniu BLE.
  • Poziom naładowania baterii – pobieranie informacji o baterii z urządzenia BLE reklamującego informacje o baterii.
  • Reset Energy (Resetowanie energii) – zresetuj energię zużytą przez urządzenie BLE wyświetlające tętno.
  • Właściwości właściwości – wyświetla wszystkie właściwości określonej właściwości urządzenia BLE.
  • Powiadomienia – uruchamianie i zatrzymywanie powiadomień o charakterystyce z urządzenia BLE.
  • Rozłączanie urządzenia – rozłączanie i otrzymywanie powiadomienia o rozłączeniu urządzenia BLE po połączeniu z nim.
  • Get Characteristics (Uzyskiwanie cech) – umożliwia uzyskanie wszystkich cech reklamowanej usługi z urządzenia BLE.
  • Get Descriptors (Uzyskaj opisy) – pobiera wszystkie opisy cech reklamowanej usługi z urządzenia BLE.
  • Filtr danych producenta – pobiera podstawowe informacje o urządzeniu BLE, które pasują do danych producenta.
  • Filtry wykluczeń – pobieranie podstawowych informacji o urządzeniu BLE z użyciem podstawowych filtrów wykluczeń.

Łączenie wielu operacji

Zapoznaj się też z wybranymi demonstracjami Bluetooth w internecie i z oficjalnymi laboratoriami kodu Bluetooth w internecie.

Biblioteki

  • web-bluetooth-utils to moduł npm, który dodaje do interfejsu API kilka przydatnych funkcji.
  • Interfejs API Web Bluetooth jest dostępny w noble, najpopularniejszym module Node.js dla BLE. Dzięki temu możesz używać webpacka lub browserify w Noble bez konieczności korzystania z serwera WebSocket ani innych wtyczek.
  • angular-web-bluetooth to moduł dla Angular, który abstrahuje wszystkie szablony potrzebne do konfiguracji interfejsu Web Bluetooth API.

Narzędzia

  • Wprowadzenie do Bluetootha internetowego to prosta aplikacja internetowa, która wygeneruje cały kod JavaScript, aby umożliwić interakcję z urządzeniem Bluetooth. Wpisz nazwę urządzenia, usługę, cechę, zdefiniuj ich właściwości i gotowe.
  • Jeśli jesteś już deweloperem Bluetooth, wtyczka Web Bluetooth Developer Studio wygeneruje również kod JavaScriptu Web Bluetooth na Twoje urządzenie Bluetooth.

Wskazówki

W Chrome na stronie Bluetooth Internals (about://bluetooth-internals) możesz sprawdzić wszystko o blisko znajdujących się urządzeniach Bluetooth: stan, usługi, cechy i deskryptory.

Zrzut ekranu z wewnętrzną stroną do debugowania Bluetootha w Chrome
Wewnętrzna strona w Chrome do debugowania urządzeń Bluetooth.

Zalecamy też zapoznanie się z oficjalną stroną Jak zgłosić błąd Bluetooth w przeglądarce internetowej, ponieważ debugowanie Bluetooth może być czasami trudne.

.

Co dalej?

Aby dowiedzieć się, które części interfejsu Web Bluetooth API są obecnie wdrażane, sprawdź najpierw stan wdrożenia przeglądarki i platformy.

Chociaż lista jest jeszcze niekompletna, przedstawiamy kilka przykładów tego, czego możesz się spodziewać w najbliższej przyszłości:

  • navigator.bluetooth.requestLEScan() będzie skanować reklamy BLE w pobliżu.
  • Nowe zdarzenie serviceadded będzie rejestrować nowo wykryte usługi GATT Bluetooth, a zdarzenie serviceremoved – usunięte. Nowe zdarzenie servicechanged zostanie wywołane, gdy jakakolwiek cecha lub opis zostanie dodany lub usunięty z usługi GATT Bluetooth.

Pokaż informacje o pomocy dotyczącej interfejsu API

Zamierzasz używać interfejsu Web Bluetooth API? Twoja publiczna pomoc pomaga zespołowi Chrome ustalać priorytety funkcji i pokazuje innym dostawcom przeglądarek, jak ważne jest wspieranie tych funkcji.

Wyślij tweeta do @ChromiumDev, używając hashtaga #WebBluetooth i podaj, gdzie i jak go używasz.

Zasoby

Podziękowania

Dziękujemy Kayce Basques za sprawdzenie tego artykułu. Obraz główny firmy SparkFun Electronics z Boulder w USA.