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 na konkretne 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 profil atrybutów ogólnych.

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, Mac (Chrome w wersji 56) i Windows 10 (Chrome w wersji 70). Oznacza to, że powinna mieć możliwość wysyłanianawiązywania połączeń z urządzeniami Bluetooth Low Energy w pobliżu, odczytywania/write właściwości Bluetooth, odbierania powiadomień GATT, sprawdzania, kiedy urządzenie Bluetooth zostało odłączone, a nawet odczytywania i zapisu do opisów 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 API Bluetooth w sieci, w Chrome 53 dodaliśmy tę funkcję 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ć brak zgodności z bezpieczeństwem, polecam post Web Bluetooth Security Model przygotowany przez 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 pamiętać o 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ą. Chodzi o odbieranie 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 Web Bluetooth API umożliwia witrynom działającym w roli głównej łączenie się ze zdalnymi serwerami GATT przez połączenie BLE. Obsługuje komunikację między urządzeniami, które implementują Bluetooth 4.0 lub nowszy.

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ę.

Potwierdzenie od 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 za pomocą klucza filtrów name lub nawet prefiksu tej nazwy za pomocą klucza filtrów namePrefix. Pamiętaj, że w takim przypadku musisz też zdefiniować klucz optionalServices, aby uzyskać dostęp do usług, których nie ma w filtrze 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 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 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 dopasować niektóre wzorce w danych producenta. Aby dowiedzieć się więcej, zapoznaj się z wyjaśnieniem filtrów danych Bluetooth.

Filtry wykluczania

Opcja exclusionFilters w narzędziu navigator.bluetooth.requestDevice() umożliwia wykluczenie niektórych urządzeń z selektora przeglądarki. 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 albo krótki 16- lub 32-bitowy formularz 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żyjmy punktu kontrolnego tętna, aby zresetować wartość pola Zużywana energia do 0 w trackerze.

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 powiadomień pokazuje, jak zatrzymać powiadomienia przy użyciu usługi stopNotifications() i prawidłowo usunąć dodany detektor 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 podobny sposób jak w przypadku parametrów GATT Bluetootha.

Zobaczmy na przykład, jak odczytać opis interwału pomiaru przy użyciu termometru zdrowotnego na urządzeniu.

W poniższym przykładzie health_thermometer to usługa termometru zdrowotnego, measurement_interval charakter interwału pomiaru i gatt.characteristic_user_description deskryptor opisu 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); });

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 poniższe przykłady 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 z urządzenia 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 charakterystyczne – wyświetlanie wszystkich właściwości określonych cech 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 (Uzyskaj właściwości) – pobieranie wszystkich właściwości 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 z urządzenia BLE podstawowe informacje o urządzeniu zgodne z danymi producenta.
  • Filtry wykluczania – pobieranie podstawowych informacji o urządzeniu BLE z podstawowymi filtrami wykluczania.

Łączenie wielu operacji

Sprawdź też nasze wyselekcjonowane wersje demonstracyjne Web Bluetooth i oficjalne ćwiczenia z programowania Web Bluetooth.

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żna używać pakietu Webpack/browserify bez konieczności stosowania 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ę, określ 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 potrzeby Twojego urządzenia 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.

Warto też zajrzeć na oficjalną stronę Jak zgłosić błąd w Bluetooth na stronie 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, podpowiadamy, 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 będzie uruchamiane po dodaniu lub usunięciu cechy lub deskryptora do usługi GATT Bluetooth.

Pokaż informacje o pomocy dotyczącej interfejsu API

Czy planujesz korzystanie z interfejsu API Web Bluetooth? 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 na adres @ChromiumDev, używając hashtagu #WebBluetooth, i daj nam znać, gdzie i w jaki sposób go używasz.

Zasoby

Podziękowania

Dziękujemy Kayce Basques za przeczytanie tego artykułu. Baner powitalny: SparkFun Electronics z Boulder, USA.