Communiquer avec des appareils Bluetooth via JavaScript

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

François Beaufort
François Beaufort

Que diriez-vous si je vous disais que les sites Web peuvent communiquer avec les appareils Bluetooth à proximité de manière sécurisée et respectueuse de la confidentialité ? De cette façon, les cardiofréquencemètres, les ampoules chantantes et même les tortues peuvent interagir directement avec un site Web.

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

Avant de commencer

Dans ce document, nous partons du principe que vous disposez de connaissances de base sur le fonctionnement du 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 de la spécification recherchent activement des développeurs enthousiastes pour tester cette API et donner des commentaires sur la spécification et des 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 devriez pouvoir demander et vous connecter aux appareils Bluetooth à basse consommation à proximité, lire/write des caractéristiques Bluetooth, recevoir des notifications GATT, savoir quand un appareil Bluetooth se déconnecte et même lire et écrire dans les descripteurs Bluetooth. Pour en savoir plus, consultez le tableau Compatibilité des navigateurs de MDN.

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

Disponible pour les essais d'origine

Afin de recueillir autant de commentaires que possible de la part des développeurs utilisant l'API Web Bluetooth sur le terrain, Chrome a précédemment ajouté cette fonctionnalité dans Chrome 53 en tant que phase d'évaluation pour ChromeOS, Android et Mac.

Le test a bien pris fin en janvier 2017.

Exigences de sécurité

Pour comprendre les compromis de sécurité, je vous recommande de lire l'article Web Bluetooth Security Model (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

Étant donné que cette API expérimentale est une nouvelle fonctionnalité puissante ajoutée au Web, elle n'est disponible que pour les contextes sécurisés. Cela signifie que vous devrez compiler en tenant compte de TLS.

Geste de l'utilisateur requis

En tant que fonctionnalité de sécurité, la découverte d'appareils Bluetooth avec navigator.bluetooth.requestDevice doit être déclenchée par un geste utilisateur, tel qu'un appui ou un clic de souris. Il s'agit d'écouter les événements pointerup, click et touchend.

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

Se plonger dans le code

L'API Web Bluetooth s'appuie fortement sur les promesses JavaScript. Si vous ne les connaissez pas, consultez ce tutoriel sur les promesses. Autre point important : () => {} sont des fonctions fléchées ECMAScript 2015.

Demander des appareils Bluetooth

Cette version de la spécification de l'API Web Bluetooth permet aux sites Web, exécutés dans le rôle de maître, 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 aux appareils à proximité à l'aide de navigator.bluetooth.requestDevice, le navigateur affiche un sélecteur d'appareils permettant à l'utilisateur de choisir un appareil ou d'annuler la demande.

Invite 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 à certains services GATT Bluetooth annoncés et/ou au nom de l'appareil.

Filtre de services

Par exemple, pour demander des appareils Bluetooth diffusant le service de batterie GATT Bluetooth:

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

Si votre service Bluetooth GATT ne figure pas sur 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 filtre name, ou même d'un préfixe de ce nom avec la clé de filtre namePrefix. Notez que dans ce cas, vous devrez également définir la clé optionalServices pour pouvoir accéder à tous les services non inclus dans un filtre de service. Sinon, un message d'erreur s'affichera 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 des données du fabricant

Il est également possible de demander des appareils Bluetooth en fonction des données spécifiques au fabricant diffusées avec la clé de filtre manufacturerData. Cette clé est un tableau d'objets avec une clé identifiant de l'entreprise Bluetooth obligatoire nommée companyIdentifier. Vous pouvez également fournir un préfixe de données qui filtre les données du fabricant des appareils Bluetooth commençant par celui-ci. Notez que vous devez également définir la clé optionalServices pour pouvoir accéder à tous les services non inclus dans un filtre de service. Sinon, une erreur s'affichera 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); });

Vous pouvez également utiliser un masque 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. Il peut être utilisé pour 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); });

Aucun filtre

Enfin, au lieu de filters, vous pouvez utiliser la touche acceptAllDevices pour afficher tous les appareils Bluetooth à proximité. Vous devrez également définir la clé optionalServices pour pouvoir accéder à certains services. Sinon, un message d'erreur s'affichera 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 appareil Bluetooth

Que faites-vous maintenant que vous avez un BluetoothDevice ? Connectons-nous au serveur GATT distant Bluetooth qui contient les définitions de service et de caractéristique.

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

Nous nous connectons ici au serveur GATT de l'appareil Bluetooth distant. Nous voulons maintenant obtenir un service GATT principal et lire une caractéristique appartenant à ce service. 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 la 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 Bluetooth GATT personnalisée, vous pouvez fournir l'UUID Bluetooth complet ou une forme courte 16 ou 32 bits à service.getCharacteristic.

Notez que vous pouvez également ajouter un écouteur d'événements characteristicvaluechanged sur une caractéristique pour gérer la lecture de sa valeur. Consultez l'exemple de lecture de la valeur de la caractéristique modifiée pour découvrir 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 dans une caractéristique Bluetooth

Écrire dans une caractéristique GATT Bluetooth est aussi simple que de la lire. Cette fois, nous allons utiliser 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. Tout est expliqué sur la page Caractéristique 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 recevoir une notification lorsque la caractéristique Mesure de la fréquence cardiaque change 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 notifications vous montre comment arrêter les notifications avec stopNotifications() et supprimer correctement l'écouteur d'événement 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 qu'il n'arrête PAS la communication de 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 dans les descripteurs Bluetooth

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

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

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

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 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 Bluetooth Web ci-dessous ont été testés avec succès. Pour profiter pleinement de ces exemples, nous vous recommandons d'installer l'[application Android du simulateur de périphérique BLE] qui simule un périphérique BLE avec un service de batterie, un service de fréquence cardiaque ou un service de thermomètre de santé.

Débutant

  • Informations sur l'appareil : permet de récupérer des informations de base provenant d'un appareil BLE.
  • Niveau de la batterie : récupérez des informations sur la batterie à partir d'un appareil BLE qui annonce des informations sur la batterie.
  • Réinitialiser l'énergie : réinitialisez l'énergie dépensée par un appareil BLE diffusant la fréquence cardiaque.
  • Propriétés caractéristiques : affiche toutes les propriétés d'une caractéristique spécifique à partir d'un appareil BLE.
  • Notifications : démarrez et arrêtez les notifications de caractéristiques à partir d'un appareil BLE.
  • Déconnexion de l'appareil : déconnectez-vous d'un appareil BLE après vous y être connecté et recevez une notification de déconnexion.
  • Obtenir les caractéristiques : obtenez toutes les caractéristiques d'un service annoncé à partir d'un appareil BLE.
  • Get Descriptors (Obtenir des descripteurs) : permet d'obtenir tous les descripteurs de caractéristiques d'un service annoncé à partir d'un appareil BLE.
  • Filtre de données fabricant : récupérez des informations de base sur l'appareil à partir d'un appareil BLE correspondant aux données du fabricant.
  • Filtres d'exclusion : récupérez des informations de base sur un appareil BLE à l'aide de filtres d'exclusion de base.

Combiner plusieurs opérations

Découvrez également nos démonstrations Web Bluetooth sélectionnées et nos ateliers de programmation Web Bluetooth officiels.

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 Node.js le plus populaire. Vous pouvez ainsi webpack/browserify noble sans avoir besoin d'un serveur WebSocket ni d'autres plug-ins.
  • angular-web-bluetooth est un module pour Angular qui élimine tout le code standard nécessaire pour configurer l'API Web Bluetooth.

Outils

  • Get Started with Web Bluetooth (Premiers pas avec Web Bluetooth) 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, une caractéristique, définissez ses propriétés, et le tour est joué.
  • 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. Elle vous permet d'inspecter tous les éléments concernant les appareils Bluetooth à proximité: état, services, caractéristiques et descripteurs.

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

Nous vous recommandons également de consulter la page officielle How to file Web Bluetooth bugs (Comment signaler des bugs Bluetooth Web), car le débogage du Bluetooth peut parfois être difficile.

Étape suivante

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

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

  • La recherche d'annonces BLE à proximité se fera avec navigator.bluetooth.requestLEScan().
  • Un nouvel événement serviceadded permet de suivre les services GATT Bluetooth nouvellement découverts, tandis que l'événement serviceremoved permet de suivre ceux qui ont été supprimés. Un nouvel événement servicechanged se déclenche lorsqu'une caractéristique et/ou un descripteur sont ajoutés ou supprimés d'un service GATT Bluetooth.

Afficher la compatibilité avec l'API

Comptez-vous utiliser l'API Web Bluetooth ? Votre soutien public aide l'équipe Chrome à hiérarchiser 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 relu cet article. Image principale de SparkFun Electronics, à Boulder (États-Unis).