Communiquer avec des appareils Bluetooth via JavaScript

L'API Web Bluetooth permet aux sites Web de communiquer avec les appareils Bluetooth.

François Beaufort
François Beaufort

Et si je disais que les sites Web pourraient communiquer avec les appareils Bluetooth à proximité de manière sécurisée et protégeant la confidentialité ? Ainsi, les moniteurs de fréquence cardiaque, les ampoules chantantes et même les tortues peuvent interagir directement avec un site Web.

Jusqu'à présent, la possibilité d'interagir avec des appareils Bluetooth n'était possible que pour les applications spécifiques à une plate-forme. L'API Web Bluetooth vise à changer cela et à l'apporter également aux navigateurs Web.

Avant de commencer

Dans ce document, nous partons du principe que vous possédez des connaissances de base sur le fonctionnement de la technologie Bluetooth à basse consommation (BLE) et du profil d'attribut générique.

Même si la spécification de l'API Web Bluetooth n'est pas encore finalisée, les auteurs des spécifications recherchent activement des développeurs enthousiastes pour essayer cette API et faire part de leurs commentaires sur la spécification et de leurs commentaires sur l'implémentation.

Un sous-ensemble de l'API Web Bluetooth est disponible dans ChromeOS, Chrome pour Android 6.0, Mac (Chrome 56) et Windows 10 (Chrome 70). Cela signifie que vous devez pouvoir demander et vous connecter aux appareils Bluetooth à basse consommation à proximité, lire/écrire les caractéristiques Bluetooth, recevoir des notifications GATT, savoir quand un appareil Bluetooth est déconnecté, et même lire et écrire des descripteurs Bluetooth. Pour en savoir plus, consultez le tableau Compatibilité du navigateur de MDN.

Pour Linux et les versions antérieures de Windows, activez l'option #experimental-web-platform-features dans about://flags.

Disponible pour les phases d'évaluation

Afin de recueillir autant de commentaires que possible de la part des développeurs qui utilisent l'API Web Bluetooth sur le terrain, Chrome a déjà ajouté cette fonctionnalité à Chrome 53 en tant qu'évaluation d'origine pour ChromeOS, Android et Mac.

L'essai a pris fin en janvier 2017.

Exigences de sécurité

Pour comprendre les compromis en matière de sécurité, je vous recommande le post sur le modèle de sécurité Web Bluetooth de Jeffrey Yasskin, ingénieur logiciel de l'équipe Chrome, qui travaille sur la spécification de l'API Web Bluetooth.

HTTPS uniquement

Cette API expérimentale est une nouvelle fonctionnalité puissante ajoutée au Web. Elle n'est donc disponible que pour les contextes sécurisés. Vous devez donc tenir compte du protocole TLS lors de la compilation.

Geste de l'utilisateur requis

Par mesure de sécurité, la détection d'appareils Bluetooth avec navigator.bluetooth.requestDevice doit être déclenchée par un geste de l'utilisateur, tel qu'une pression ou un clic de souris. Il s'agit d'écouter des événements pointerup, click et touchend.

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

Saisissez le code

L'API Web Bluetooth s'appuie en grande partie sur les promesses JavaScript. Si vous ne les connaissez pas, consultez cet excellent tutoriel sur les promesses. Une dernière chose : () => {} sont les fonctions fléchées d'ECMAScript 2015.

Demander des appareils Bluetooth

Cette version de la spécification de l'API Web Bluetooth permet aux sites Web, s'exécutant dans le rôle central, de se connecter à des serveurs GATT distants via une connexion BLE. Il permet la communication entre les appareils qui implémentent le Bluetooth 4.0 ou version ultérieure.

Lorsqu'un site Web demande l'accès à des appareils à proximité à l'aide de navigator.bluetooth.requestDevice, le navigateur invite l'utilisateur à afficher un sélecteur d'appareil qui lui permet de choisir un appareil ou d'annuler la demande.

Invite de l'utilisateur de l'appareil Bluetooth.

La fonction navigator.bluetooth.requestDevice() utilise un objet obligatoire qui définit des filtres. Ces filtres permettent de ne renvoyer que les appareils correspondant au nom de l'appareil et/ou à certains services Bluetooth GATT annoncés.

Filtre de services

Par exemple, pour demander des appareils Bluetooth faisant la promotion du Bluetooth GATT Battery Service, procédez comme suit:

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

Toutefois, si votre service Bluetooth GATT ne figure pas dans la liste des services Bluetooth GATT standardisés, vous pouvez fournir l'UUID Bluetooth complet ou un format court 16 ou 32 bits.

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

Filtre par nom

Vous pouvez également demander des appareils Bluetooth en fonction du nom de l'appareil annoncé avec la clé de filtres name, ou même un préfixe de ce nom avec la clé de filtres namePrefix. Notez que dans ce cas, vous devez également définir la clé optionalServices pour pouvoir accéder aux services non inclus dans un filtre de service. Si vous ne le faites pas, vous obtiendrez une erreur plus tard lorsque vous tenterez d'y accéder.

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

Filtre de données du fabricant

Il est également possible de demander des appareils Bluetooth en fonction des données spécifiques au fabricant annoncées avec la clé de filtre manufacturerData. Cette clé est un tableau d'objets avec une clé d'identifiant Bluetooth d'entreprise obligatoire nommée companyIdentifier. Vous pouvez également fournir un préfixe de données qui filtre les données du fabricant des appareils Bluetooth qui commencent par celui-ci. Notez que vous devrez également définir la clé optionalServices pour pouvoir accéder à tous les services non inclus dans un filtre de service. Si vous ne le faites pas, vous obtiendrez une erreur plus tard lorsque vous tenterez d'y accéder.

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

Un masque peut également être utilisé avec un préfixe de données pour correspondre à certains modèles dans les données du fabricant. Pour en savoir plus, consultez la présentation des filtres de données Bluetooth.

Filtres d'exclusion

L'option exclusionFilters dans navigator.bluetooth.requestDevice() vous permet d'exclure certains appareils du sélecteur de navigateur. Elle permet d'exclure les appareils qui correspondent à un filtre plus large, mais qui ne sont pas compatibles.

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

Évitez les filtres

Enfin, au lieu d'utiliser filters, vous pouvez utiliser la touche acceptAllDevices pour afficher tous les appareils Bluetooth à proximité. Vous devez également définir la clé optionalServices pour pouvoir accéder à certains services. Sinon, vous obtiendrez un message d'erreur plus tard lorsque vous tenterez d'y accéder.

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

Se connecter à un périphérique Bluetooth

Que faire maintenant que vous disposez d'un BluetoothDevice ? Connectons-nous au serveur GATT distant Bluetooth qui contient les définitions du service et des caractéristiques.

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

Lire une caractéristique Bluetooth

Ici, nous nous connectons au serveur GATT de l'appareil Bluetooth distant. Nous souhaitons maintenant obtenir un service GATT principal et lire une caractéristique qui lui appartient. Essayons, par exemple, de lire le niveau de charge actuel de la batterie de l'appareil.

Dans l'exemple suivant, battery_level correspond à la caractéristique standardisée du niveau de batterie.

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

Si vous utilisez une caractéristique GATT Bluetooth personnalisée, vous pouvez fournir l'UUID Bluetooth complet ou un court format 16 ou 32 bits à service.getCharacteristic.

Notez que vous pouvez également ajouter un écouteur d'événements characteristicvaluechanged au niveau d'une caractéristique pour gérer la lecture de sa valeur. Consultez l'exemple de modification de la valeur caractéristique de lecture pour savoir comment gérer également les notifications GATT à venir.

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

Écrire sur une caractéristique Bluetooth

Écrire dans une caractéristique Bluetooth GATT est aussi facile que de la lire. Cette fois, utilisons le point de contrôle de la fréquence cardiaque pour réinitialiser la valeur du champ "Énergie dépensée" sur 0 sur un moniteur de fréquence cardiaque.

Je vous promets qu'il n'y a pas de magie ici. Tout est expliqué sur la page Caractéristiques du point de contrôle de la fréquence cardiaque.

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

Recevoir des notifications GATT

Voyons maintenant comment être averti lorsque les caractéristiques de mesure de la fréquence cardiaque changent sur l'appareil:

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
}

L'exemple de notification vous montre comment arrêter les notifications avec stopNotifications() et supprimer correctement l'écouteur d'événements characteristicvaluechanged ajouté.

Se déconnecter d'un appareil Bluetooth

Pour offrir une meilleure expérience utilisateur, vous pouvez écouter les événements de déconnexion et inviter l'utilisateur à se reconnecter:

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

Vous pouvez également appeler device.gatt.disconnect() pour déconnecter votre application Web de l'appareil Bluetooth. Cela déclenchera les écouteurs d'événements gattserverdisconnected existants. Notez que cela n'interrompt PAS la communication avec l'appareil Bluetooth si une autre application communique déjà avec l'appareil Bluetooth. Consultez l'exemple de déconnexion de l'appareil et l'exemple de reconnexion automatique pour en savoir plus.

Lire et écrire des descripteurs Bluetooth

Les descripteurs GATT Bluetooth sont des attributs qui décrivent une valeur caractéristique. Vous pouvez les lire et les écrire de la même manière que les caractéristiques Bluetooth GATT.

Voyons, par exemple, comment lire la description de l'intervalle de mesure du thermomètre de santé de l'appareil.

Dans l'exemple ci-dessous, health_thermometer correspond au service Thermomètre, measurement_interval à la caractéristique de l'intervalle de mesure et gatt.characteristic_user_description au descripteur de description caractéristique.

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

Maintenant que nous avons lu la description par l'utilisateur de l'intervalle de mesure du thermomètre de santé de l'appareil, voyons comment le mettre à jour et écrire une valeur personnalisée.

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

Exemples, démonstrations et ateliers de programmation

Tous les exemples Web Bluetooth ci-dessous ont été testés avec succès. Pour profiter pleinement de ces exemples, nous vous recommandons d'installer l'[application Android Peripheral Simulator BLE], qui simule un périphérique BLE avec un service de batterie, de fréquence cardiaque ou de thermomètre de santé.

Débutant

  • Informations sur l'appareil : permet de récupérer des informations de base sur un appareil à partir d'un appareil BLE.
  • Niveau de la batterie : récupère des informations sur la batterie à partir d'un appareil BLE en diffusant des informations sur la batterie.
  • Réinitialiser l'énergie : réinitialisez l'énergie dépensée depuis un appareil BLE faisant la promotion de la fréquence cardiaque.
  • Caractéristiques : affiche toutes les propriétés d'une caractéristique spécifique à partir d'un appareil BLE.
  • Notifications : permet de lancer et d'arrêter les notifications de caractéristiques à partir d'un appareil BLE.
  • Déconnexion de l'appareil : déconnectez un appareil BLE et recevez une notification le cas échéant.
  • Obtenir les caractéristiques : obtenez toutes les caractéristiques d'un service annoncé à partir d'un appareil BLE.
  • Obtenir les descripteurs : obtenez les descripteurs de tous les descripteurs des caractéristiques d'un service annoncé à partir d'un appareil BLE.
  • Filtre de données fabricant : récupérez des informations de base sur un appareil BLE qui correspondent aux données du fabricant.
  • Exclusion Filters (Filtres d'exclusion) : récupérez les informations de base d'un appareil BLE doté de filtres d'exclusion de base.

Combiner plusieurs opérations

Consultez notre sélection de démonstrations Web Bluetooth et les ateliers de programmation Web officiels sur le Bluetooth.

Bibliothèques

  • web-bluetooth-utils est un module npm qui ajoute des fonctions pratiques à l'API.
  • Un shim d'API Web Bluetooth est disponible dans noble, le module central BLE de Node.js le plus populaire. Cela vous permet de créer des packages ou des navigateurs Web sans avoir besoin d'un serveur WebSocket ou d'autres plug-ins.
  • angular-web-bluetooth est un module pour Angular qui élimine tout le code récurrent nécessaire à la configuration de l'API Web Bluetooth.

Outils

  • Premiers pas avec le Bluetooth Web est une application Web simple qui génère tout le code récurrent JavaScript pour commencer à interagir avec un appareil Bluetooth. Saisissez un nom d'appareil, un service ou une caractéristique, définissez ses propriétés, et vous êtes prêt à l'utiliser.
  • Si vous êtes déjà un développeur Bluetooth, le plug-in Web Bluetooth Developer Studio génère également le code JavaScript Web Bluetooth pour votre appareil Bluetooth.

Conseils

Une page Internes Bluetooth est disponible dans Chrome à l'adresse about://bluetooth-internals pour vous permettre de tout inspecter sur les appareils Bluetooth à proximité: état, services, caractéristiques et descripteurs.

Capture d'écran de la page interne permettant de déboguer le Bluetooth dans Chrome
Page interne de Chrome pour le débogage des appareils Bluetooth.

Je vous recommande également de consulter la page officielle Comment signaler des bugs Web Bluetooth, car le débogage Bluetooth peut être difficile.

Étapes suivantes

Vérifiez d'abord l'état d'implémentation du navigateur et de la plate-forme pour savoir quelles parties de l'API Web Bluetooth sont en cours d'implémentation.

Bien qu'il ne soit pas encore terminé, voici un aperçu de ce qui vous attend dans un avenir proche:

  • La recherche de publicités BLE à proximité s'effectue avec navigator.bluetooth.requestLEScan().
  • Un nouvel événement serviceadded suivra les services GATT Bluetooth récemment détectés, tandis que l'événement serviceremoved suivra ceux qui ont été supprimés. Un nouvel événement servicechanged se déclenche lorsqu'une caractéristique et/ou un descripteur est ajouté ou supprimé d'un service Bluetooth GATT.

Afficher la compatibilité avec l'API

Comptez-vous utiliser l'API Web Bluetooth ? Votre assistance publique aide l'équipe Chrome à prioriser les fonctionnalités et montre aux autres fournisseurs de navigateurs à quel point il est essentiel de les prendre en charge.

Envoyez un tweet à @ChromiumDev avec le hashtag #WebBluetooth, et indiquez-nous où et comment vous l'utilisez.

Ressources

Remerciements

Merci à Kayce Basques d'avoir donné son avis sur cet article. Image principale par SparkFun Electronics, Boulder, États-Unis.