Kommunikation mit Bluetooth-Geräten über JavaScript

Die Web Bluetooth API ermöglicht es Websites, mit Bluetooth-Geräten zu kommunizieren.

François Beaufort
François Beaufort

Was wäre, wenn ich Ihnen sagen würde, dass Websites auf sichere und datenschutzfreundliche Weise mit Bluetooth-Geräten in der Nähe kommunizieren könnten? So können Herzfrequenzmesser, singende Glühbirnen und sogar Schildkröten direkt mit einer Website interagieren.

Bisher war die Interaktion mit Bluetooth-Geräten nur für plattformspezifische Apps möglich. Die Web Bluetooth API möchte dies ändern und unterstützt sie auch in Webbrowsern.

Bevor es losgeht

In diesem Dokument wird davon ausgegangen, dass Sie Grundkenntnisse zur Funktionsweise von Bluetooth Low Energy (BLE) und dem Generic Attribute Profile haben.

Auch wenn die Web Bluetooth API-Spezifikation noch nicht endgültig ist, suchen die Ersteller der Spezifikationen aktiv nach begeisterten Entwicklern, die diese API testen, Feedback zur Spezifikation und Feedback zur Implementierung geben.

Ein Teil der Web Bluetooth API ist in ChromeOS, Chrome für Android 6.0, Mac (Chrome 56) und Windows 10 (Chrome 70) verfügbar. Das bedeutet, dass Sie in der Lage sein sollten, Bluetooth Low Energy-Geräte in der Nähe anzufordern und zu verbinden, Bluetooth-Attribute zu lesen/write, GATT-Benachrichtigungen zu empfangen, zu erkennen, wann eine Bluetooth-Verbindung getrennt wird, und sogar Bluetooth-Beschreibungen zu lesen und zu schreiben. Weitere Informationen finden Sie in der Tabelle Browserkompatibilität der MDN.

Aktivieren Sie unter Linux und früheren Windows-Versionen das Flag #experimental-web-platform-features in about://flags.

Verfügbar für Ursprungstests

Um so viel Feedback wie möglich von Entwicklern zu erhalten, die die Web Bluetooth API verwenden, hat Chrome diese Funktion zuvor in Chrome 53 als Ursprungstest für ChromeOS, Android und Mac hinzugefügt.

Der Test wurde im Januar 2017 erfolgreich abgeschlossen.

Sicherheitsanforderungen

Zum besseren Verständnis der Sicherheitsrisiken empfehle ich den Beitrag zum Web Bluetooth-Sicherheitsmodell von Jeffrey Yasskin, Softwareentwickler im Chrome-Team, der an der Web Bluetooth API-Spezifikation arbeitet.

Nur HTTPS

Da diese experimentelle API eine leistungsstarke neue Funktion für das Web ist, wird sie nur für sichere Kontexte verfügbar gemacht. Das bedeutet, dass Sie Ihre App mit TLS entwickeln müssen.

Nutzergeste erforderlich

Als Sicherheitsfunktion muss die Erkennung von Bluetooth-Geräten mit navigator.bluetooth.requestDevice durch eine Nutzergeste wie eine Berührung oder einen Mausklick ausgelöst werden. Hier geht es um das Beobachten von Ereignissen vom Typ pointerup, click und touchend.

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

Code

Die Web Bluetooth API basiert stark auf JavaScript-Promise-Objekten. Wenn Sie mit Promises nicht vertraut sind, sehen Sie sich dieses Tutorial zu Promises an. Noch etwas: () => {} sind ECMAScript 2015-Funktionen.

Bluetooth-Geräte anfordern

Mit dieser Version der Web Bluetooth API-Spezifikation können Websites, die in der Rolle „Zentrale“ ausgeführt werden, über eine BLE-Verbindung eine Verbindung zu Remote-GATT-Servern herstellen. Sie unterstützt die Kommunikation zwischen Geräten, die Bluetooth 4.0 oder höher implementieren.

Wenn eine Website über navigator.bluetooth.requestDevice Zugriff auf Geräte in der Nähe anfordert, wird der Nutzer vom Browser aufgefordert, ein Gerät auszuwählen oder die Anfrage abzubrechen.

Aufforderung für Nutzer eines Bluetooth-Geräts

Für die Funktion navigator.bluetooth.requestDevice() ist ein obligatorisches Objekt erforderlich, das Filter definiert. Mit diesen Filtern werden nur Geräte zurückgegeben, die mit einigen beworbenen Bluetooth-GATT-Diensten und/oder dem Gerätenamen übereinstimmen.

Dienstfilter

So fordern Sie beispielsweise Bluetooth-Geräte an, die den Bluetooth GATT-Akkudienst anbieten:

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

Wenn Ihr Bluetooth GATT-Dienst nicht in der Liste der standardisierten Bluetooth-GATT-Dienste enthalten ist, können Sie entweder die vollständige Bluetooth-UUID oder eine kurze 16- oder 32-Bit-Form angeben.

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

Name filter

Sie können Bluetooth-Geräte auch anhand des angegebenen Gerätenamens mit dem Filterschlüssel name oder sogar eines Präfixes dieses Namens mit dem Filterschlüssel namePrefix anfordern. Beachten Sie, dass Sie in diesem Fall auch den Schlüssel optionalServices definieren müssen, um auf alle Dienste zugreifen zu können, die nicht in einem Dienstfilter enthalten sind. Andernfalls wird beim Zugriff auf die Daten später eine Fehlermeldung angezeigt.

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

Herstellerdatenfilter

Es ist auch möglich, Bluetooth-Geräte anhand der herstellerspezifischen Daten anzufordern, die mit dem Filterschlüssel manufacturerData beworben werden. Dieser Schlüssel ist ein Array von Objekten mit einem obligatorischen Schlüssel Bluetooth-Unternehmenskennung mit dem Namen companyIdentifier. Sie können auch ein Datenpräfix angeben, mit dem Herstellerdaten von Bluetooth-Geräten gefiltert werden, die damit beginnen. Sie müssen auch den Schlüssel optionalServices definieren, um auf Dienste zugreifen zu können, die nicht in einem Dienstfilter enthalten sind. Andernfalls erhalten Sie später beim Zugriff auf die Dateien eine Fehlermeldung.

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

Eine Maske kann auch mit einem Datenpräfix verwendet werden, um bestimmte Muster in Herstellerdaten abzugleichen. Weitere Informationen finden Sie im Hilfeartikel Erläuterung zu Bluetooth-Datenfiltern.

Ausschlussfilter

Mit der Option exclusionFilters in navigator.bluetooth.requestDevice() können Sie einige Geräte von der Browserauswahl ausschließen. So können Sie Geräte ausschließen, die zwar einem umfassenderen Filter entsprechen, aber nicht unterstützt werden.

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

Ohne Filter

Stattdessen können Sie die Taste acceptAllDevices verwenden, um alle Bluetooth-Geräte in der Nähe anzeigen zu lassen.filters Außerdem müssen Sie den Schlüssel optionalServices definieren, um auf einige Dienste zugreifen zu können. Andernfalls wird beim Zugriff auf die Dateien später eine Fehlermeldung angezeigt.

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

Herstellen von Verbindungen zu Bluetooth-Geräten

Was tun Sie jetzt, nachdem Sie eine BluetoothDevice haben? Stellen Sie eine Verbindung zum Bluetooth-Remote-GATT-Server her, auf dem die Dienst- und Eigenschaftendefinitionen gespeichert sind.

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-Attribut lesen

Hier wird eine Verbindung zum GATT-Server des Remote-Bluetooth-Geräts hergestellt. Jetzt möchten wir einen primären GATT-Dienst abrufen und ein Attribut lesen, das zu diesem Dienst gehört. Versuchen wir beispielsweise, den aktuellen Akkustand des Geräts abzurufen.

Im folgenden Beispiel ist battery_level die standardisierte Akkustand-Eigenschaft.

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

Wenn Sie eine benutzerdefinierte Bluetooth-GATT-Eigenschaft verwenden, können Sie entweder die vollständige Bluetooth-UUID oder ein kurzes 16- oder 32-Bit-Formular an service.getCharacteristic senden.

Sie können einem Merkmal auch einen characteristicvaluechanged-Event-Listener hinzufügen, um seinen Wert zu lesen. Im Beispiel Wert der Eigenschaft „Read Characteristic“ geändert erfahren Sie, wie Sie optional auch anstehende GATT-Benachrichtigungen verarbeiten.


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

In eine Bluetooth-Eigenschaft schreiben

Das Schreiben in eine Bluetooth-GATT-Eigenschaft ist genauso einfach wie das Lesen. Verwenden wir diesmal den Kontrollpunkt „Herzfrequenz“, um den Wert des Felds „Verbostene Energie“ auf einem Herzfrequenzmesser auf 0 zurückzusetzen.

Ich bin mir sicher, dass es hier keine Magie gibt. Weitere Informationen finden Sie auf der Seite Punkteigenschaft „Herzfrequenzsteuerung“.

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

GATT-Benachrichtigungen erhalten

Sehen wir uns nun an, wie du benachrichtigt wirst, wenn sich die Eigenschaft der Herzfrequenzmessung auf dem Gerät ändert:

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
}

Im Beispiel für Benachrichtigungen wird gezeigt, wie Sie Benachrichtigungen mit stopNotifications() beenden und den hinzugefügten characteristicvaluechanged-Ereignis-Listener ordnungsgemäß entfernen.

Verbindung zu einem Bluetooth-Gerät trennen

Für eine bessere Nutzerfreundlichkeit solltest du auf Verbindungsabbrüche achten und den Nutzer auffordern, sich wieder zu verbinden:

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

Sie können auch device.gatt.disconnect() eingeben, um die Verbindung Ihrer Webanwendung mit dem Bluetooth-Gerät zu trennen. Dadurch werden vorhandene gattserverdisconnected-Ereignis-Listener ausgelöst. Hinweis: Die Bluetooth-Kommunikation wird NICHT beendet, wenn bereits eine andere App mit dem Bluetooth-Gerät kommuniziert. Weitere Informationen finden Sie in den Beispielen Device Disconnect Sample (Beispiel für die Gerätetrennung) und Automatic Reconnect Sample (Beispiel für die automatische Wiederverbindung).

Lese- und Schreibzugriff auf Bluetooth-Deskriptoren

Bluetooth-GATT-Deskriptoren sind Attribute, die einen charakteristischen Wert beschreiben. Sie können sie ähnlich wie Bluetooth-GATT-Attribute lesen und darauf schreiben.

Sehen wir uns beispielsweise an, wie Sie die Nutzerbeschreibung des Messintervalls des Gerätestatus-Thermometers lesen.

Im folgenden Beispiel ist health_thermometer der Gesundheitsthermometer-Dienst, measurement_interval das Messintervall-Attribut und gatt.characteristic_user_description der Beschreibungs-Descriptor für das Nutzerattribut.

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

Nachdem wir nun die Nutzerbeschreibung des Messintervalls des Gesundheitsthermometers des Geräts gelesen haben, sehen wir uns an, wie wir es aktualisieren und einen benutzerdefinierten Wert schreiben können.

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

Samples, Demos und Codelabs

Alle folgenden Web Bluetooth-Beispiele wurden erfolgreich getestet. Damit Sie diese Samples optimal nutzen können, empfehle ich Ihnen, die [Android-App „BLE Peripheral Simulator“] zu installieren. Diese simuliert ein BLE-Peripheriegerät mit einem Akkudienst, einem Herzfrequenzdienst oder einem Gesundheitsthermometerdienst.

Anfänger

  • Geräteinformationen: Hiermit können Sie grundlegende Geräteinformationen von einem BLE-Gerät abrufen.
  • Akkustand: Hiermit werden Akkuinformationen von einem BLE-Gerät abgerufen, das Akkuinformationen sendet.
  • Energie zurücksetzen: Energie, die von einem BLE-Gerät verbraucht wurde, das die Herzfrequenz sendet, wird zurückgesetzt.
  • Merkmalseigenschaften - zeigen alle Eigenschaften einer bestimmten Eigenschaft eines BLE-Geräts an.
  • Benachrichtigungen: Sie können damit Eigenschaftenbenachrichtigungen von einem BLE-Gerät starten und beenden.
  • Verbindung des Geräts getrennt: Wenn die Verbindung zu einem BLE-Gerät getrennt wurde, wird eine Benachrichtigung gesendet.
  • Eigenschaften abrufen: Rufen Sie alle Merkmale eines beworbenen Dienstes von einem BLE-Gerät ab.
  • Descriptors abrufen: Hiermit werden alle Descriptors der Eigenschaften eines beworbenen Dienstes von einem BLE-Gerät abgerufen.
  • Herstellerdatenfilter: Hiermit werden grundlegende Geräteinformationen von einem BLE-Gerät abgerufen, die mit den Herstellerdaten übereinstimmen.
  • Ausschlussfilter: Hiermit können Sie grundlegende Geräteinformationen von einem BLE-Gerät mit einfachen Ausschlussfiltern abrufen.

Mehrere Vorgänge kombinieren

  • GAP-Eigenschaften: Hiermit werden alle GAP-Eigenschaften eines BLE-Geräts abgerufen.
  • Geräteinformationen-Eigenschaften: Hiermit werden alle Geräteinformationen-Eigenschaften eines BLE-Geräts abgerufen.
  • Link Loss: Legen Sie die „Alert Level“-Eigenschaft eines BLE-Geräts fest (readValue und writeValue).
  • Dienste und Merkmale entdecken – Entdecken Sie alle zugänglichen primären Dienste und ihre Merkmale von einem BLE-Gerät.
  • Automatische Wiederverbindung: Mit einem exponentiellen Backoff-Algorithmus wird eine Verbindung zu einem getrennten BLE-Gerät wiederhergestellt.
  • Read Characteristic Value Changed (Wert der Eigenschaft wurde geändert lesen): Akkustand lesen und über Änderungen von einem BLE-Gerät benachrichtigt werden.
  • Deskriptoren lesen: Lesen aller Merkmalsdeskriptoren eines Dienstes aus einem BLE-Gerät.
  • Write Descriptor: Schreibt auf dem BLE-Gerät in den Descriptor „Characteristic User Description“.

Sehen Sie sich auch unsere ausgewählten Web Bluetooth-Demos und offiziellen Web Bluetooth-Codelabs an.

Bibliotheken

  • web-bluetooth-utils ist ein npm-Modul, das der API einige praktische Funktionen hinzufügt.
  • Ein Web Bluetooth API-Shim ist in noble verfügbar, dem beliebtesten Node.js-BLE-Zentralmodul. Auf diese Weise kannst du Adels mithilfe von Webpacks/Browsern verbessern, ohne dass ein WebSocket-Server oder andere Plug-ins erforderlich sind.
  • angular-web-bluetooth ist ein Modul für Angular, das die gesamte Boilerplate abstrahiert, die zum Konfigurieren der Web Bluetooth API erforderlich ist.

Tools

  • Erste Schritte mit Web Bluetooth ist eine einfache Webanwendung, die den gesamten JavaScript-Boilerplate-Code generiert, um mit einem Bluetooth-Gerät zu interagieren. Geben Sie einen Gerätenamen, einen Dienst und eine Eigenschaft ein, definieren Sie die Eigenschaften und schon kann es losgehen.
  • Wenn Sie bereits Bluetooth-Entwickler sind, generiert das Web Bluetooth Developer Studio-Plug-in auch den Web Bluetooth-JavaScript-Code für Ihr Bluetooth-Gerät.

Tipps

In Chrome ist unter about://bluetooth-internals die Seite Bluetooth Internals verfügbar. Dort finden Sie alle Informationen zu Bluetooth-Geräten in der Nähe: Status, Dienste, Eigenschaften und Descriptors.

Screenshot der internen Seite zum Debuggen von Bluetooth in Chrome
Interne Seite in Chrome zur Fehlerbehebung bei Bluetooth-Geräten.

Weitere Informationen findest du auf der offiziellen Seite Web Bluetooth-Fehler melden, da das Beheben von Bluetooth-Fehlern manchmal schwierig sein kann.

Nächste Schritte

Prüfen Sie zuerst den Implementierungsstatus von Browser und Plattform, um zu erfahren, welche Teile der Web Bluetooth API derzeit implementiert werden.

Die Funktion ist noch nicht vollständig, aber hier ist schon mal ein kleiner Vorgeschmack auf die nächsten Schritte:

  • Mit navigator.bluetooth.requestLEScan() wird nach BLE-Anzeigen in der Nähe gesucht.
  • Ein neues serviceadded-Ereignis erfasst neu erkannte Bluetooth-GATT-Dienste, während das serviceremoved-Ereignis entfernte Dienste erfasst. Ein neues servicechanged-Ereignis wird ausgelöst, wenn einem Bluetooth-GATT-Dienst eine Eigenschaft und/oder ein Descriptor hinzugefügt oder daraus entfernt wird.

Unterstützung für die API anzeigen

Beabsichtigen Sie, die Web Bluetooth API zu verwenden? Ihre öffentliche Unterstützung hilft dem Chrome-Team, Funktionen zu priorisieren, und zeigt anderen Browseranbietern, wie wichtig es ist, sie zu unterstützen.

Senden Sie einen Tweet an @ChromiumDev mit dem Hashtag #WebBluetooth und teilen Sie uns mit, wo und wie Sie ihn verwenden.

Ressourcen

Danksagungen

Vielen Dank an Kayce Basques für die Überprüfung dieses Artikels. Hero-Image von SparkFun Electronics aus Boulder, USA.