Kommunikation mit Bluetooth-Geräten über JavaScript

Die Web Bluetooth API ermöglicht Websites die Kommunikation mit Bluetooth-Geräten.

François Beaufort
François Beaufort

Was wäre, wenn Websites sicher und datenschutzfreundlich mit Bluetooth-Geräten in der Nähe kommunizieren könnten? Auf diese Weise können Herzfrequenzmesser, 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 stellt sie auch in Webbrowsern bereit.

Bevor es losgeht

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

Obwohl die Spezifikation der Web Bluetooth API noch nicht finalisiert ist, suchen die Ersteller der Spezifikation aktiv nach begeisterten Entwicklern, die diese API ausprobieren und Feedback zur Spezifikation und Feedback zur Implementierung geben können.

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, Sie sollten in der Lage sein, Bluetooth Low Energy-Geräte in der Nähe anzufordern und zu verbinden, Bluetooth-Eigenschaften zu lesen/zu schreiben, GATT-Benachrichtigungen zu erhalten, zu erfahren, wenn Bluetooth-Geräte getrennt werden, und sogar in Bluetooth-Deskriptoren zu lesen und zu schreiben. Weitere Informationen finden Sie in der Tabelle Browserkompatibilität des MDN.

Aktivieren Sie für Linux und frühere Versionen von Windows das Flag #experimental-web-platform-features in about://flags.

Verfügbar für Ursprungstests

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

Der Testzeitraum ist im Januar 2017 abgelaufen.

Sicherheitsanforderungen

Um die Vor- und Nachteile der Sicherheit zu verstehen, empfehle ich den Beitrag Web Bluetooth Security Model von Jeffrey Yasskin, einem Softwareentwickler im Chrome-Team, der an der Spezifikation der Web Bluetooth API arbeitet.

Nur HTTPS

Da es sich bei dieser experimentellen API um eine leistungsstarke neue Funktion handelt, steht sie nur sicheren Kontexten zur Verfügung. Das bedeutet, dass Sie bei der Entwicklung TLS berücksichtigen müssen.

Nutzergeste erforderlich

Als Sicherheitsfunktion muss die Erkennung von Bluetooth-Geräten mit navigator.bluetooth.requestDevice durch eine Nutzergeste ausgelöst werden, z. B. durch Berührung oder einen Mausklick. Gemeint ist das Abhören von pointerup-, click- und touchend-Ereignissen.

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

In den Code einbauen

Die Web Bluetooth API stützt sich stark auf JavaScript-Promise-Objekte. Wenn Sie mit ihnen nicht vertraut sind, sehen Sie sich diese hilfreiche Anleitung für Promise-Objekte an. Außerdem sind () => {} die Pfeilfunktionen von ECMAScript 2015.

Bluetooth-Geräte anfordern

Mit dieser Version der Web Bluetooth API-Spezifikation können Websites mit der Rolle „Central“ über eine BLE-Verbindung eine Verbindung zu Remote-GATT-Servern herstellen. Sie unterstützt die Kommunikation zwischen Geräten, auf denen Bluetooth 4.0 oder höher implementiert ist.

Wenn eine Website mit navigator.bluetooth.requestDevice Zugriff auf Geräte in der Nähe anfordert, fordert der Browser den Nutzer auf, ein Gerät auszuwählen oder die Anfrage abzubrechen.

Nutzeraufforderung für Bluetooth-Gerät.

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

Dienstfilter

So fordern Sie beispielsweise Bluetooth-Geräte an, mit denen für den Bluetooth GATT-Akkudienst geworben wird:

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

Wenn Ihr Bluetooth GATT-Dienst jedoch nicht in der Liste der standardisierten Bluetooth-GATT-Dienste aufgeführt 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); });

Namensfilter

Sie können Bluetooth-Geräte auch basierend auf dem Gerätenamen anfordern, der mit dem Filterschlüssel name beworben wird, oder sogar anhand eines Präfixes dieses Namens mit dem Filterschlüssel namePrefix. Beachten Sie, dass Sie in diesem Fall auch den Schlüssel optionalServices definieren müssen, um auf Dienste zugreifen zu können, die nicht in einem Dienstfilter enthalten sind. Andernfalls wird später beim Versuch, darauf zuzugreifen, 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); });

Filter für Herstellerdaten

Es ist auch möglich, Bluetooth-Geräte auf Grundlage der herstellerspezifischen Daten anzufordern, die mit dem Filterschlüssel manufacturerData beworben werden. Dieser Schlüssel ist ein Array von Objekten mit dem obligatorischen Schlüssel für die Bluetooth-Unternehmenskennung companyIdentifier. Sie können auch ein Datenpräfix angeben, um Herstellerdaten von Bluetooth-Geräten, die mit diesem Präfix beginnen, zu filtern. Außerdem müssen Sie 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 Versuch, darauf zuzugreifen, 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 findest du in der Erläuterung zu Bluetooth-Datenfiltern.

Ausschlussfilter

Mit der Option exclusionFilters in navigator.bluetooth.requestDevice() können Sie einige Geräte aus der Browserauswahl ausschließen. Damit lassen sich Geräte ausschließen, die einem breiter gefassten 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); });

Keine Filter anwenden

Statt filters kannst du den Schlüssel acceptAllDevices verwenden, um alle Bluetooth-Geräte in der Nähe aufzurufen. Außerdem müssen Sie den Schlüssel optionalServices definieren, um auf einige Dienste zugreifen zu können. Andernfalls erhalten Sie später eine Fehlermeldung.

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

Verbindung zu einem Bluetooth-Gerät herstellen

Was machen Sie jetzt, nachdem Sie ein BluetoothDevice haben? Wir stellen eine Verbindung zum Bluetooth Remote GATT Server her, der den Dienst und die Merkmalsdefinitionen enthält.

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-Eigenschaften 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 Merkmal dieses Dienstes lesen. Versuchen wir beispielsweise, den aktuellen Akkuladestand des Geräts zu lesen.

Im folgenden Beispiel ist battery_level die standardisierte Batterielebenscharakteristik.

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 du eine benutzerdefinierte Bluetooth-GATT-Eigenschaft verwendest, kannst du entweder die vollständige Bluetooth-UUID oder ein kurzes 16- oder 32-Bit-Format für service.getCharacteristic angeben.

Sie können einer Eigenschaft auch einen characteristicvaluechanged-Event-Listener hinzufügen, um das Lesen ihres Werts zu verarbeiten. Im Beispiel zu Änderungen des charakteristischen Werts lesen erfahren Sie, wie Sie optional auch anstehende GATT-Benachrichtigungen handhaben können.

…
.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 einer Bluetooth-GATT-Eigenschaft ist so einfach wie das Lesen. Diesmal setzen wir mit dem Herzfrequenz-Kontrollpunkt den Wert des Felds Energieverbrauch auf einem Gerät zur Herzfrequenzmessung auf 0 zurück.

Ich verspreche, hier gibt es keine Magie. All dies wird auf der Seite Herzfrequenz-Kontrollpunkt-Eigenschaften erklärt.

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
}

Das Benachrichtigungsbeispiel zeigt, wie Sie Benachrichtigungen mit stopNotifications() stoppen und den hinzugefügten Ereignis-Listener characteristicvaluechanged ordnungsgemäß entfernen.

Trennen der Verbindung zu einem Bluetooth-Gerät

Um die Nutzerfreundlichkeit zu verbessern, sollten Sie auf Trennungsereignisse warten und den Nutzer einladen, die Verbindung wiederherzustellen:

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() aufrufen, um die Verbindung zwischen Ihrer Web-App und dem Bluetooth-Gerät zu trennen. Dadurch werden vorhandene gattserverdisconnected-Event-Listener ausgelöst. Beachten Sie, dass die Bluetooth-Gerätekommunikation NICHT gestoppt wird, wenn bereits eine andere App mit dem Bluetooth-Gerät kommuniziert. Sehen Sie sich die Beispiele zum Trennen von Geräten und Automatic Reconnect Sample an, um mehr zu erfahren.

Bluetooth-Deskriptoren lesen und schreiben

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

Sehen wir uns zum Beispiel an, wie man die Nutzerbeschreibung des Messintervalls des Gesundheitsthermometers eines Geräts liest.

Im folgenden Beispiel ist health_thermometer der Gesundheitsthermometer-Dienst, measurement_interval das Messintervall-Merkmal und gatt.characteristic_user_description die Beschreibung der charakteristischen Nutzerbeschreibung.

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

Du hast jetzt die Nutzerbeschreibung des Messintervalls des Gesundheitsthermometers des Geräts gelesen. Als Nächstes aktualisierst du es und schreibst einen benutzerdefinierten Wert.

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

Beispiele, Demos und Codelabs

Alle unten aufgeführten Web-Bluetooth-Beispiele wurden erfolgreich getestet. Damit Sie diese Proben optimal nutzen können, empfehlen wir Ihnen die Installation der [BLE-Peripheral-Simulator-Android-App], die ein BLE-Peripheriegerät mit einem Akku-, Herzfrequenz- oder Gesundheitsthermometer-Dienst simuliert.

Anfänger

  • Geräteinformationen – rufen Sie grundlegende Geräteinformationen von einem BLE-Gerät ab.
  • Akkustand: Rufen Sie Informationen zum Akku von einem BLE-Gerät ab, das Informationen zum Akku anzeigt.
  • Reset Energy (Energie zurücksetzen): Sie können den Energieverbrauch zurücksetzen, den Sie von einem BLE-Gerät ausgegeben haben, das für die Herzfrequenz wirbt.
  • Merkmalseigenschaften: Zeigt alle Eigenschaften eines bestimmten Merkmals eines BLE-Geräts an.
  • Benachrichtigungen – Starten und Stoppen charakteristischer Benachrichtigungen von einem BLE-Gerät.
  • Verbindung zum Gerät trennen: Trennen Sie die Verbindung zu einem BLE-Gerät und erhalten Sie eine Benachrichtigung, sobald die Verbindung zu einem BLE-Gerät getrennt wird.
  • Get Characteristics (Merkmale abrufen): Rufen Sie alle Merkmale eines beworbenen Dienstes von einem BLE-Gerät ab.
  • Deskriptoren abrufen: Rufen Sie alle Deskriptoren der Merkmale eines beworbenen Dienstes von einem BLE-Gerät ab.
  • Hersteller-Datenfilter: Hiermit können Sie grundlegende Geräteinformationen von einem BLE-Gerät abrufen, die mit den Herstellerdaten übereinstimmen.
  • Ausschlussfilter – rufen Sie grundlegende Geräteinformationen von einem BLE-Gerät mit einfachen Ausschlussfiltern ab.

Mehrere Vorgänge kombinieren

Sehen Sie sich auch unsere kuratierten 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 BLE-Zentralmodul von Node.js. Auf diese Weise können Sie Noble Webpacks erstellen bzw. im Browser darstellen, ohne dass ein WebSocket-Server oder andere Plug-ins erforderlich sind.
  • angular-web-bluetooth ist ein Modul für Angular, das alle Textbausteine wegzieht, die für die Konfiguration der Web Bluetooth API erforderlich sind.

Tools

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

Tipps

Die Seite Bluetooth Internals ist in Chrome unter about://bluetooth-internals verfügbar, auf der Sie alles über Bluetooth-Geräte in der Nähe untersuchen können: Status, Dienste, Merkmale und Deskriptoren.

Screenshot der internen Seite für die Bluetooth-Fehlerbehebung in Chrome
Interne Seite in Chrome für die Fehlerbehebung bei Bluetooth-Geräten.

Sieh dir auch die offizielle Seite zum Melden von Web Bluetooth-Fehlern an, da die Fehlerbehebung für Bluetooth manchmal schwierig sein kann.

Nächste Schritte

Prüfen Sie zuerst den Status der Browser- und Plattformimplementierung, um zu erfahren, welche Teile der Web Bluetooth API derzeit implementiert sind.

Auch wenn sie noch unvollständig ist, können Sie hier einen Vorgeschmack darauf geben, was Sie in naher Zukunft erwartet:

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

Unterstützung für die API zeigen

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

Sende einen Tweet mit dem Hashtag #WebBluetooth an @ChromiumDev und teile uns mit, wo und wie du den Dienst verwendest.

Ressourcen

Danksagungen

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