Communiceren met Bluetooth-apparaten via JavaScript

Met de Web Bluetooth API kunnen websites communiceren met Bluetooth-apparaten.

François Beaufort
François Beaufort

Wat als ik je vertelde dat websites op een veilige en privacybeschermende manier kunnen communiceren met Bluetooth-apparaten in de buurt? Op deze manier kunnen hartslagmeters, zingende gloeilampen en zelfs schildpadden rechtstreeks communiceren met een website.

Tot nu toe was de mogelijkheid om te communiceren met Bluetooth-apparaten alleen mogelijk voor platformspecifieke apps. De Web Bluetooth API wil dit veranderen en brengt dit ook naar webbrowsers.

Voor we beginnen

In dit document wordt ervan uitgegaan dat u enige basiskennis hebt van hoe Bluetooth Low Energy (BLE) en het Generieke Attribuutprofiel werken.

Hoewel de Web Bluetooth API-specificatie nog niet definitief is, zijn de spec-auteurs actief op zoek naar enthousiaste ontwikkelaars om deze API uit te proberen en feedback te geven over de specificaties en feedback over de implementatie .

Een subset 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 er verbinding mee kunt maken, Bluetooth-kenmerken kunt lezen / schrijven , GATT-meldingen kunt ontvangen , kunt weten wanneer de verbinding met een Bluetooth-apparaat wordt verbroken en zelfs Bluetooth-descriptors kunt lezen en schrijven . Zie de browsercompatibiliteitstabel van MDN voor meer informatie.

Voor Linux en eerdere versies van Windows schakelt u de vlag #experimental-web-platform-features in about://flags in.

Beschikbaar voor herkomstproeven

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

De proef is in januari 2017 succesvol afgerond.

Beveiligingsvereisten

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

Alleen HTTPS

Omdat deze experimentele API een krachtige nieuwe functie is die aan internet is toegevoegd, wordt deze alleen beschikbaar gesteld om contexten te beveiligen . Dit betekent dat u moet bouwen met TLS in gedachten.

Gebruikersgebaar vereist

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

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

Verdiep je in de code

De Web Bluetooth API is sterk afhankelijk van JavaScript- beloften . Als je er niet bekend mee bent, bekijk dan deze geweldige Promises-tutorial . Nog een ding, () => {} zijn ECMAScript 2015 Arrow-functies .

Bluetooth-apparaten aanvragen

Met deze versie van de Web Bluetooth API-specificatie kunnen websites die in de centrale rol draaien, verbinding maken met externe GATT-servers via een BLE-verbinding. Het ondersteunt communicatie tussen apparaten die Bluetooth 4.0 of hoger implementeren.

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

Gebruikersprompt Bluetooth-apparaat.

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

Dienstenfilter

Om bijvoorbeeld Bluetooth-apparaten aan te vragen die reclame maken voor de Bluetooth GATT Battery Service :

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

Als uw Bluetooth GATT-service echter niet op de lijst met gestandaardiseerde Bluetooth GATT-services staat, kunt u de volledige Bluetooth UUID of een korte 16- of 32-bits vorm opgeven.

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

Naamfilter

U kunt ook Bluetooth-apparaten aanvragen op basis van de apparaatnaam waarvoor reclame wordt gemaakt met de name , of zelfs een voorvoegsel van deze naam met de namePrefix filtersleutel. Houd er rekening mee dat u in dit geval ook de optionalServices sleutel moet definiëren om toegang te krijgen tot services die niet in een servicefilter zijn opgenomen. Als u dat niet doet, krijgt u later een foutmelding wanneer u probeert toegang te krijgen.

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 aan 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 met de naam companyIdentifier . U kunt ook een gegevensvoorvoegsel opgeven waarmee fabrikantgegevens worden gefilterd van Bluetooth-apparaten die daarmee beginnen. Houd er rekening mee dat u ook de optionalServices sleutel moet definiëren om toegang te krijgen tot services die niet in een servicefilter zijn opgenomen. Als u dat niet doet, krijgt u later een foutmelding wanneer u probeert toegang te krijgen.

// 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 gegevensvoorvoegsel om bepaalde patronen in de gegevens van de fabrikant te matchen. Bekijk de uitleg over Bluetooth-gegevensfilters voor meer informatie.

Uitsluitingsfilters

Met de optie exclusionFilters in navigator.bluetooth.requestDevice() kunt u bepaalde apparaten uitsluiten van de browserkiezer. Het kan worden gebruikt om apparaten uit te sluiten die 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 optionalServices sleutel definiëren om toegang te krijgen tot bepaalde services. Als u dat niet doet, krijgt u later een foutmelding wanneer u probeert toegang te krijgen.

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

Maak verbinding met een Bluetooth-apparaat

Dus wat doe je nu je een BluetoothDevice hebt? Laten we verbinding maken met de Bluetooth-afstandsbediening GATT-server die de service en karakteristieke definities 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); });

Lees een Bluetooth-kenmerk

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

In het volgende voorbeeld is battery_level de gestandaardiseerde karakteristiek voor het batterijniveau .

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 aangepast Bluetooth GATT-kenmerk gebruikt, kunt u de volledige Bluetooth-UUID of een korte 16- of 32-bits vorm aan service.getCharacteristic opgeven.

Houd er rekening mee dat u ook een characteristicvaluechanged gebeurtenislistener aan een kenmerk kunt toevoegen om het lezen van de waarde ervan af te handelen. Bekijk het voorbeeld van Read Characteristic Value Changed om te zien hoe u optioneel ook aankomende GATT-meldingen 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-kenmerk

Schrijven naar een Bluetooth GATT-kenmerk is net zo eenvoudig als lezen. Laten we deze keer het hartslagcontrolepunt gebruiken om de waarde van het veld Verbruikte energie op een hartslagmeter opnieuw in te stellen op 0.

Ik beloof dat er hier geen magie is. Het wordt allemaal uitgelegd op de pagina Hartslagcontrolepuntkarakteristieken .

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 u op de hoogte kunt worden gesteld wanneer de hartslagmetingskarakteristiek 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
}

In het meldingenvoorbeeld ziet u hoe u meldingen kunt stoppen met stopNotifications() en hoe u de toegevoegde characteristicvaluechanged gebeurtenislistener op de juiste manier kunt verwijderen.

Verbinding met een Bluetooth-apparaat verbreken

Voor een betere gebruikerservaring kunt u luisteren naar gebeurtenissen waarbij de verbinding is verbroken 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.`);
}

U kunt ook device.gatt.disconnect() aanroepen om uw webapp los te koppelen van het Bluetooth-apparaat. Hierdoor worden bestaande gattserverdisconnected gebeurtenislisteners geactiveerd. Houd er rekening mee dat de communicatie met het Bluetooth-apparaat NIET wordt gestopt als een andere app al met het Bluetooth-apparaat communiceert. Bekijk het voorbeeld van het ontkoppelen van het apparaat en het voorbeeld van automatisch opnieuw verbinden om dieper te duiken.

Lezen en schrijven naar Bluetooth-descriptors

Bluetooth GATT-descriptors zijn attributen die een karakteristieke waarde beschrijven. U kunt ze op dezelfde manier lezen en schrijven als Bluetooth GATT-kenmerken.

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 het meetintervalkenmerk en gatt.characteristic_user_description de karakteristieke gebruikersbeschrijving .

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 deze kunnen bijwerken en een aangepaste waarde kunnen schrijven.

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 met succes getest. Om optimaal van deze voorbeelden te kunnen genieten, raad ik u aan de [BLE Peripheral Simulator Android App] te installeren, die een BLE-randapparaat simuleert 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 adverteert.
  • Energie resetten - reset de verbruikte energie van een BLE-apparaat dat hartslag adverteert.
  • Karakteristieke eigenschappen - toon alle eigenschappen van een specifiek kenmerk van een BLE-apparaat.
  • Meldingen - start en stop karakteristieke meldingen van een BLE-apparaat.
  • Apparaat loskoppelen - verbreek de verbinding en ontvang een melding wanneer een BLE-apparaat wordt verbroken nadat er verbinding mee is gemaakt.
  • Kenmerken verkrijgen - verkrijg alle kenmerken van een geadverteerde service van een BLE-apparaat.
  • Haal descriptors op - haal de descriptors van alle kenmerken van een geadverteerde service op van een BLE-apparaat.
  • Fabrikantgegevensfilter - haal basisapparaatinformatie op van een BLE-apparaat dat overeenkomt met de gegevens van de fabrikant.
  • Uitsluitingsfilters - haal basisapparaatinformatie op van een BLE-apparaat met basisuitsluitingsfilters.

Het combineren van meerdere operaties

Bekijk ook onze samengestelde Web Bluetooth-demo's en officiële Web Bluetooth Codelabs .

Bibliotheken

  • web-bluetooth-utils is een npm-module die enkele gemaksfuncties aan de API toevoegt.
  • Een Web Bluetooth API-shim is beschikbaar in noble , de meest populaire Node.js BLE centrale module. Hierdoor kunt u noble webpacken/browserifyen zonder dat u een WebSocket-server of andere plug-ins nodig hebt.
  • angular-web-bluetooth is een module voor Angular die al het standaardwerk wegneemt dat nodig is om de Web Bluetooth API te configureren.

Hulpmiddelen

  • Aan de slag met Web Bluetooth is een eenvoudige webapp die alle JavaScript-standaardcode genereert om interactie met een Bluetooth-apparaat te starten. Voer een apparaatnaam, een service en een kenmerk in, definieer de eigenschappen ervan en u bent klaar om te gaan.
  • Als u al een Bluetooth-ontwikkelaar bent, genereert de Web Bluetooth Developer Studio Plugin ook de Web Bluetooth JavaScript-code voor uw Bluetooth-apparaat.

Tips

Er is een Bluetooth Internals- pagina beschikbaar in Chrome op about://bluetooth-internals , zodat u alles kunt bekijken over Bluetooth-apparaten in de buurt: status, services, kenmerken en beschrijvingen.

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

Ik raad ook aan om de officiële pagina met Web Bluetooth-bugs te archiveren, omdat het debuggen van Bluetooth soms moeilijk kan zijn.

Wat is het volgende

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

Hoewel het nog steeds onvolledig is, is hier een voorproefje van wat u in de nabije toekomst kunt verwachten:

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

Toon ondersteuning voor de API

Bent u van plan de Web Bluetooth API te gebruiken? Uw publieke steun helpt het Chrome-team prioriteiten te stellen voor 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 u deze gebruikt.

Bronnen

Dankbetuigingen

Met dank aan Kayce Basques voor het beoordelen van dit artikel. Heldenafbeelding van SparkFun Electronics uit Boulder, VS.