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é ? Ainsi, les cardiofréquencemètres, les ampoules chantantes et même les tortues peuvent interagir directement avec un site Web.

Jusqu'à présent, il n'était possible d'interagir avec des appareils Bluetooth que pour les applications spécifiques à la 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/écrire des caractéristiques Bluetooth, recevoir des notifications GATT, savoir quand un appareil Bluetooth est déconnecté, 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 d'obtenir autant de commentaires que possible des développeurs qui utilisent l'API Web Bluetooth sur le terrain, Chrome a déjà ajouté cette fonctionnalité dans Chrome 53 en tant que test d'origine 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. Vous devrez donc 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 "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); });

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 une forme courte de 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 sur le 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 à partir 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. 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); });

Un masque peut également être utilisé avec un préfixe de données pour faire 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

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 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 GATT Bluetooth personnalisée, vous pouvez fournir l'UUID Bluetooth complet ou une forme courte de 16 ou 32 bits à service.getCharacteristic.

Notez que vous pouvez également ajouter un écouteur d'événements characteristicvaluechanged à 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, utilisons le point de contrôle de la fréquence cardiaque pour réinitialiser la valeur du champ "Énergie dépensée" sur un appareil de cardiofréquencemètre.

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. Pour en savoir plus, consultez l'exemple de déconnexion d'appareil et l'exemple de reconnexion automatique.

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 de l'utilisateur de l'intervalle de mesure du thermomètre de santé de l'appareil.

Dans l'exemple ci-dessous, health_thermometer correspond au service de thermomètre de santé, measurement_interval à la caractéristique Intervalle de mesure et gatt.characteristic_user_description au descripteur de description utilisateur de la 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 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

  • Device Info (Informations sur l'appareil) : permet de récupérer des informations de base sur un appareil BLE.
  • Niveau de la batterie : récupérez les informations sur la batterie à partir d'un appareil BLE qui en fait la promotion.
  • Réinitialiser l'énergie : réinitialisez l'énergie dépensée par un appareil BLE diffusant la fréquence cardiaque.
  • Propriétés de la caractéristique : affichez toutes les propriétés d'une caractéristique spécifique 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.
  • Get Characteristics (Obtenir les caractéristiques) : récupérez 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 du fabricant : permet de récupérer des informations de base sur 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

  • Caractéristiques GAP : récupérez toutes les caractéristiques GAP d'un appareil BLE.
  • Caractéristiques des informations sur l'appareil : obtenez toutes les caractéristiques des informations sur l'appareil d'un appareil BLE.
  • Perte de liaison : définissez la caractéristique de niveau d'alerte d'un appareil BLE (readValue et writeValue).
  • Discover Services & Characteristics (Découvrir les services et les caractéristiques) : découvrez tous les services principaux accessibles et leurs caractéristiques à partir d'un appareil BLE.
  • Automatic Reconnect (Reconnexion automatique) : reconnectez-vous à un appareil BLE déconnecté à l'aide d'un algorithme de délai avant réessai exponentiel.
  • Read Characteristic Value Changed (Lire la valeur de la caractéristique modifiée) : lisez le niveau de la batterie et recevez une notification de toute modification apportée par un appareil BLE.
  • Lire les descripteurs : lisez tous les descripteurs de caractéristiques d'un service à partir d'un appareil BLE.
  • Écrire le descripteur : écrivez dans le descripteur "Description utilisateur de la caractéristique" sur un appareil BLE.

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

  • Premiers pas avec le Web Bluetooth est une application Web simple qui génère tout le code JavaScript standard 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 Bluetooth Internals (Fonctionnalités internes du Bluetooth) est disponible dans Chrome à l'adresse about://bluetooth-internals. Elle vous permet d'inspecter tous 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.

Nous vous recommandons également de consulter la page officielle How to file Web Bluetooth bugs (Déposer un bug 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 que cette liste ne soit pas encore complète, voici un aperçu de ce à quoi vous pouvez vous attendre dans un avenir proche:

  • La recherche d'annonces BLE à proximité se fait 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 héros de SparkFun Electronics, Boulder (États-Unis).