Communiceren met Bluetooth-apparaten via JavaScript

De Web Bluetooth API stelt websites in staat om te communiceren met Bluetooth-apparaten.

François Beaufort
François Beaufort

Wat als ik je vertelde dat websites op een veilige en privacyvriendelijke manier met Bluetooth-apparaten in de buurt kunnen communiceren? Op die manier zouden hartslagmeters, zingende lampen en zelfs schildpadden rechtstreeks met een website kunnen interageren.

Tot nu toe was het alleen mogelijk om met Bluetooth-apparaten te communiceren via platformspecifieke apps. De Web Bluetooth API wil hier verandering in brengen en maakt deze functionaliteit ook beschikbaar voor webbrowsers.

Voordat we beginnen

Dit document gaat ervan uit dat u enige basiskennis hebt van de werking van Bluetooth Low Energy (BLE) en het Generic Attribute Profile .

Hoewel de specificatie voor de Web Bluetooth API nog niet definitief is, zijn de auteurs van de specificatie actief op zoek naar enthousiaste ontwikkelaars die deze API willen uitproberen en feedback willen geven op de specificatie en de implementatie .

Een deel van de Web Bluetooth API is beschikbaar in ChromeOS, Chrome voor Android 6.0, Mac (Chrome 56) en Windows 10 (Chrome 70). Dit betekent dat u Bluetooth Low Energy-apparaten in de buurt kunt opvragen en ermee verbinding kunt maken, Bluetooth-kenmerken kunt lezen / schrijven , GATT-meldingen kunt ontvangen , kunt weten wanneer een Bluetooth-apparaat de verbinding verbreekt en zelfs Bluetooth-beschrijvingen kunt lezen en schrijven . Zie de compatibiliteitstabel voor browsers van MDN voor meer informatie.

Voor Linux en oudere versies van Windows kunt u de vlag #experimental-web-platform-features inschakelen via about://flags .

Beschikbaar voor oorsprongsproeven

Om zoveel mogelijk feedback te krijgen van ontwikkelaars die de Web Bluetooth API in de praktijk gebruiken, heeft Chrome deze functie eerder in Chrome 53 als proefversie toegevoegd voor ChromeOS, Android en Mac.

Het proces is in januari 2017 succesvol afgerond.

Beveiligingsvereisten

Om de afwegingen op het gebied van beveiliging te begrijpen, raad ik het artikel 'Web Bluetooth Security Model' aan van Jeffrey Yasskin, een software engineer in het Chrome-team die werkt aan de specificatie van de Web Bluetooth API.

Alleen HTTPS

Omdat deze experimentele API een krachtige nieuwe functie is die aan het web is toegevoegd, is deze alleen beschikbaar in beveiligde omgevingen . Dit betekent dat u bij het ontwikkelen rekening moet houden met TLS .

Gebruikersgebaar vereist

Als beveiligingsmaatregel moet het detecteren van Bluetooth-apparaten met navigator.bluetooth.requestDevice worden geactiveerd door een gebruikersgebaar, zoals een aanraking of een muisklik. We hebben het hier over het luisteren naar de gebeurtenissen pointerup , click en touchend .

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

Ga aan de slag met de code.

De Web Bluetooth API maakt veelvuldig gebruik van JavaScript Promises . Als je er niet bekend mee bent, bekijk dan deze uitstekende Promises-tutorial . Nog iets: () => {} zijn ECMAScript 2015 Arrow-functies .

Bluetooth-apparaten aanvragen

Deze versie van de Web Bluetooth API-specificatie maakt het voor websites, die de centrale rol vervullen, mogelijk om via een BLE-verbinding verbinding te maken met externe GATT-servers. Het ondersteunt communicatie tussen apparaten die Bluetooth 4.0 of later implementeren.

Wanneer een website toegang vraagt ​​tot apparaten in de buurt met behulp van navigator.bluetooth.requestDevice , toont de browser een apparaatkiezer waarmee de gebruiker een apparaat kan selecteren of het verzoek kan annuleren.

Gebruikersprompt voor Bluetooth-apparaten.

De functie navigator.bluetooth.requestDevice() vereist een verplicht object dat filters definieert. Deze filters worden gebruikt om alleen apparaten te retourneren die overeenkomen met bepaalde geadverteerde Bluetooth GATT-services en/of de apparaatnaam.

Servicefilter

Bijvoorbeeld om Bluetooth-apparaten op te vragen die de Bluetooth GATT-batterijservice adverteren:

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

Als uw Bluetooth GATT-service niet in de lijst met gestandaardiseerde Bluetooth GATT-services staat, kunt u de volledige Bluetooth UUID of een verkorte 16- of 32-bits variant opgeven.

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

Naamfilter

Je kunt ook Bluetooth-apparaten opvragen op basis van de geadverteerde apparaatnaam met de sleutel name , of zelfs een voorvoegsel van deze naam met de sleutel namePrefix . Houd er rekening mee dat je in dit geval ook de sleutel optionalServices moet definiëren om toegang te krijgen tot services die niet in een servicefilter zijn opgenomen. Doe je dit niet, dan krijg je later een foutmelding wanneer je probeert ze te benaderen.

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

Fabrikantgegevensfilter

Het is ook mogelijk om Bluetooth-apparaten op te vragen op basis van de fabrikantspecifieke gegevens die worden geadverteerd met de sleutel manufacturerData . Deze sleutel is een array van objecten met een verplichte Bluetooth-bedrijfsidentificatiesleutel genaamd companyIdentifier . U kunt ook een gegevensvoorvoegsel opgeven dat fabrikantgegevens filtert van Bluetooth-apparaten die ermee beginnen. Houd er rekening mee dat u ook de sleutel optionalServices moet definiëren om toegang te krijgen tot services die niet in een servicefilter zijn opgenomen. Als u dit niet doet, krijgt u later een foutmelding wanneer u deze probeert te benaderen.

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

Een masker kan ook worden gebruikt met een dataprefix om bepaalde patronen in fabrikantgegevens te herkennen. Bekijk de uitleg over Bluetooth-datafilters voor meer informatie.

Uitsluitingsfilters

Met de optie exclusionFilters in navigator.bluetooth.requestDevice() kunt u bepaalde apparaten uitsluiten van de apparaatkiezer in de browser. Deze optie kan worden gebruikt om apparaten uit te sluiten die weliswaar aan een breder filter voldoen, maar niet worden ondersteund.

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

Geen filters

Ten slotte kunt u in plaats van filters de sleutel ` acceptAllDevices gebruiken om alle Bluetooth-apparaten in de buurt weer te geven. U moet ook de sleutel optionalServices definiëren om toegang te krijgen tot bepaalde services. Als u dit niet doet, krijgt u later een foutmelding wanneer u deze probeert te benaderen.

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

Verbinden met een Bluetooth-apparaat

Wat doe je nu je een BluetoothDevice hebt? Laten we verbinding maken met de externe Bluetooth GATT-server, die de service- en kenmerkdefinities bevat.

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

Een Bluetooth-karakteristiek uitlezen

Hier maken we verbinding met de GATT-server van het externe Bluetooth-apparaat. Nu willen we een primaire GATT-service verkrijgen en een kenmerk uitlezen dat bij deze service hoort. Laten we bijvoorbeeld proberen het huidige laadniveau van de batterij van het apparaat uit te lezen.

In het onderstaande voorbeeld is battery_level de gestandaardiseerde Battery Level Characteristic .

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

Als u een aangepaste Bluetooth GATT-karakteristiek gebruikt, kunt u de volledige Bluetooth UUID of een verkorte 16- of 32-bits vorm opgeven aan service.getCharacteristic .

Houd er rekening mee dat u ook een eventlistener characteristicvaluechanged aan een kenmerk kunt toevoegen om het uitlezen van de waarde ervan af te handelen. Bekijk het voorbeeld `Read Characteristic Value Changed` om te zien hoe u optioneel ook aankomende GATT-notificaties kunt afhandelen.


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

Schrijf naar een Bluetooth-karakteristiek.

Schrijven naar een Bluetooth GATT-karakteristiek is net zo eenvoudig als lezen. Laten we deze keer het hartslagcontrolepunt gebruiken om de waarde van het veld 'Energieverbruik' op een hartslagmeter op 0 te zetten.

Ik beloof je dat er geen magie aan te pas komt. Alles wordt uitgelegd op de pagina 'Kenmerken van het hartslagcontrolepunt' .

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

Ontvang GATT-meldingen

Laten we nu eens kijken hoe je een melding kunt ontvangen wanneer de hartslagmeting op het apparaat verandert:

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
}

Het voorbeeld 'Notifications' laat zien hoe je meldingen kunt stoppen met stopNotifications() en hoe je de toegevoegde eventlistener characteristicvaluechanged correct kunt verwijderen.

Verbinding met een Bluetooth-apparaat verbreken

Om een ​​betere gebruikerservaring te bieden, kunt u luisteren naar gebeurtenissen die leiden tot een verbroken verbinding en de gebruiker uitnodigen om opnieuw verbinding te maken:

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

Je kunt ook device.gatt.disconnect() aanroepen om je web-app los te koppelen van het Bluetooth-apparaat. Dit activeert bestaande gattserverdisconnected gebeurtenislisteners. Houd er rekening mee dat dit de Bluetooth-communicatie NIET stopt als een andere app al met het Bluetooth-apparaat communiceert. Bekijk de voorbeelden `Device Disconnect` en ` Automatic Reconnect` voor meer informatie.

Bluetooth-beschrijvingen lezen en schrijven.

Bluetooth GATT-descriptors zijn attributen die een karakteristieke waarde beschrijven. Je kunt ze lezen en schrijven op een vergelijkbare manier als Bluetooth GATT-karakteristieken.

Laten we bijvoorbeeld eens kijken hoe we de gebruikersbeschrijving van het meetinterval van de gezondheidsthermometer van het apparaat kunnen lezen.

In het onderstaande voorbeeld is health_thermometer de Health Thermometer-service , measurement_interval de Measurement Interval-karakteristiek en gatt.characteristic_user_description de Characteristic User Description-descriptor .

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

Nu we de gebruikersbeschrijving van het meetinterval van de gezondheidsthermometer van het apparaat hebben gelezen, gaan we kijken hoe we dit kunnen bijwerken en een aangepaste waarde kunnen invoeren.

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

Voorbeelden, demo's en codelabs

Alle onderstaande Web Bluetooth-voorbeelden zijn succesvol getest. Om optimaal van deze voorbeelden te kunnen genieten, raad ik u aan de [BLE Peripheral Simulator Android-app] te installeren. Deze app simuleert een BLE-randapparaat met een batterijservice, een hartslagservice of een gezondheidsthermometerservice.

Beginner

  • Apparaatinfo - haal basisapparaatinformatie op van een BLE-apparaat.
  • Batterijniveau - haal batterij-informatie op van een BLE-apparaat dat batterij-informatie uitzendt.
  • Energieverbruik resetten - hiermee wordt het energieverbruik van een BLE-apparaat dat hartslaggegevens verzendt, gereset.
  • Karakteristieke eigenschappen - toon alle eigenschappen van een specifieke karakteristiek van een BLE-apparaat.
  • Meldingen - start en stop karakteristieke meldingen van een BLE-apparaat.
  • Apparaatverbinding verbroken - ontvang een melding wanneer de verbinding met een BLE-apparaat verbroken is nadat u er verbinding mee hebt gemaakt.
  • Kenmerken ophalen - haal alle kenmerken van een geadverteerde service op van een BLE-apparaat.
  • Descriptors ophalen - haal alle kenmerkdescriptors op van een geadverteerde service van een BLE-apparaat.
  • Filter voor fabrikantgegevens - haal basisapparaatinformatie op van een BLE-apparaat die overeenkomt met de fabrikantgegevens.
  • Uitsluitingsfilters - hiermee kunt u basisapparaatinformatie ophalen van een BLE-apparaat met behulp van standaard uitsluitingsfilters.

Het combineren van meerdere bewerkingen

Bekijk ook onze zorgvuldig geselecteerde Web Bluetooth-demo's en officiële Web Bluetooth-codelabs .

Bibliotheken

  • web-bluetooth-utils is een npm-module die een aantal handige functies aan de API toevoegt.
  • Een Web Bluetooth API-shim is beschikbaar in noble , de populairste Node.js BLE-module. Hiermee kunt u noble via webpack/browserify implementeren zonder dat u een WebSocket-server of andere plugins nodig hebt.
  • angular-web-bluetooth is een module voor Angular die alle standaardcode voor het configureren van de Web Bluetooth API abstraheert.

Hulpmiddelen

  • Met 'Aan de slag met Web Bluetooth' maak je een eenvoudige webapplicatie die alle benodigde JavaScript-code genereert om met een Bluetooth-apparaat te communiceren. Voer een apparaatnaam, een service en een kenmerk in, definieer de eigenschappen ervan en je kunt direct aan de slag.
  • Als je al een Bluetooth-ontwikkelaar bent, genereert de Web Bluetooth Developer Studio Plugin ook de Web Bluetooth JavaScript-code voor je Bluetooth-apparaat.

Tips

In Chrome is een pagina met interne informatie over Bluetooth beschikbaar op about://bluetooth-internals , waar u alles kunt bekijken over Bluetooth-apparaten in de buurt: status, services, kenmerken en beschrijvingen.

Screenshot van de interne pagina voor het debuggen van Bluetooth in Chrome
Interne pagina in Chrome voor het debuggen van Bluetooth-apparaten.

Ik raad je ook aan om de officiële pagina 'Web Bluetooth-bugs melden' te raadplegen, aangezien het debuggen van Bluetooth soms lastig kan zijn.

Wat volgt?

Controleer eerst de implementatiestatus van de browser en het platform om te weten welke onderdelen van de Web Bluetooth API momenteel worden geïmplementeerd.

Hoewel het nog niet af is, geven we hier alvast een voorproefje van wat je in de nabije toekomst kunt verwachten:

  • Het scannen naar nabijgelegen BLE-advertenties gebeurt met navigator.bluetooth.requestLEScan() .
  • Een nieuwe serviceadded gebeurtenis registreert nieuw ontdekte Bluetooth GATT-services, terwijl een serviceremoved gebeurtenis de verwijderde services registreert. Een nieuwe servicechanged -gebeurtenis wordt geactiveerd wanneer een kenmerk en/of descriptor wordt toegevoegd aan of verwijderd uit een Bluetooth GATT-service.

Toon je steun voor de API

Ben je van plan de Web Bluetooth API te gebruiken? Jouw publieke steun helpt het Chrome-team bij het prioriteren van functies en laat andere browserleveranciers zien hoe belangrijk het is om deze te ondersteunen.

Stuur een tweet naar @ChromiumDev met de hashtag #WebBluetooth en laat ons weten waar en hoe je het gebruikt.

Bronnen

Dankbetuigingen

Met dank aan Kayce Basques voor de recensie.