Comunicazione con dispositivi Bluetooth tramite JavaScript

L'API Web Bluetooth consente ai siti web di comunicare con i dispositivi Bluetooth.

François Beaufort
François Beaufort

E se ti dicessi che i siti web potrebbero comunicare con i dispositivi Bluetooth nelle vicinanze in modo sicuro e incentrato sulla tutela della privacy? In questo modo, i rilevatori del battito cardiaco, le lampadine che suonano e persino le tartaruga potrebbero interagire direttamente con un sito web.

Finora, la possibilità di interagire con i dispositivi Bluetooth era possibile solo per le app specifiche della piattaforma. L'API Web Bluetooth mira a cambiare questo concetto e lo offre anche ai browser web.

Prima di cominciare

Questo documento presuppone che tu disponga di una conoscenza di base del funzionamento di Bluetooth LowEnergy (BLE) e del profilo di attributi generico.

Anche se la specifica dell'API Web Bluetooth non è ancora stata finalizzata, gli autori delle specifiche cercano attivamente sviluppatori appassionati che provino questa API e diano feedback sulle specifiche e feedback sull'implementazione.

Un sottoinsieme dell'API Web Bluetooth è disponibile in ChromeOS, Chrome per Android 6.0, Mac (Chrome 56) e Windows 10 (Chrome 70). Ciò significa che dovresti essere in grado di richiedere e connetterti a dispositivi Bluetooth Low Energy nelle vicinanze, leggere/scrivere le caratteristiche Bluetooth, ricevere notifiche GATT, sapere quando un dispositivo Bluetooth viene disconnesso e persino leggere e scrivere sui descrittori Bluetooth. Per ulteriori informazioni, consulta la tabella Compatibilità del browser di MDN.

Per Linux e versioni precedenti di Windows, abilita il flag #experimental-web-platform-features in about://flags.

Disponibile per le prove dell'origine

Per ricevere il maggior numero possibile di feedback dagli sviluppatori che utilizzano l'API Web Bluetooth sul campo, in precedenza Chrome ha aggiunto questa funzionalità in Chrome 53 come prova delle origini per ChromeOS, Android e Mac.

La prova è terminata con successo a gennaio 2017.

Requisiti di sicurezza

Per comprendere i compromessi in termini di sicurezza, ti consiglio il post sul Web Bluetooth Security Model di Jeffrey Yasskin, software engineer del team di Chrome, che sta lavorando alla specifica dell'API Web Bluetooth.

Solo HTTPS

Poiché questa API sperimentale è una nuova e potente funzionalità aggiunta al web, viene resa disponibile solo per contesti sicuri. Ciò significa che dovrai creare il modello TLS.

Gesto dell'utente richiesto

Come funzionalità di sicurezza, il rilevamento di dispositivi Bluetooth con navigator.bluetooth.requestDevice deve essere attivato da un gesto dell'utente, ad esempio un tocco o un clic del mouse. Parliamo di ascoltare gli eventi di pointerup, click e touchend.

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

Scopri il codice

L'API Web Bluetooth fa molto affidamento sulle promesse di JavaScript. Se non le conosci, guarda questo fantastico tutorial di Promise. Un'altra cosa: () => {} sono le funzioni a freccia di ECMAScript 2015.

Richiedere dispositivi Bluetooth

Questa versione della specifica dell'API Web Bluetooth consente ai siti web che hanno il ruolo Central di connettersi a server GATT remoti tramite una connessione BLE. Supporta la comunicazione tra dispositivi che implementano Bluetooth 4.0 o versioni successive.

Quando un sito web richiede l'accesso ai dispositivi nelle vicinanze utilizzando navigator.bluetooth.requestDevice, il browser chiede all'utente di scegliere un dispositivo, dove può scegliere un dispositivo o annullare la richiesta.

Richiesta dell'utente di un dispositivo Bluetooth.

La funzione navigator.bluetooth.requestDevice() accetta un oggetto obbligatorio che definisce i filtri. Questi filtri vengono utilizzati per restituire solo i dispositivi che corrispondono ad alcuni servizi Bluetooth GATT pubblicizzati e/o al nome del dispositivo.

Filtro servizi

Ad esempio, per richiedere dispositivi Bluetooth che pubblicizzano il servizio batteria Bluetooth GATT:

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

Se il tuo servizio Bluetooth GATT non è presente nell'elenco dei servizi Bluetooth GATT standardizzati, puoi fornire l'UUID Bluetooth completo o un breve modulo a 16 o 32 bit.

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

Filtro nome

Puoi richiedere dispositivi Bluetooth anche in base al nome del dispositivo pubblicizzato con la chiave dei filtri name o anche un prefisso di questo nome con la chiave dei filtri namePrefix. Tieni presente che, in questo caso, dovrai anche definire la chiave optionalServices per poter accedere a tutti i servizi non inclusi in un filtro di servizio. In caso contrario, riceverai un messaggio di errore in seguito quando tenterai di accedere.

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

Filtro dati del produttore

È anche possibile richiedere dispositivi Bluetooth in base ai dati specifici del produttore pubblicizzati con la chiave filtri manufacturerData. Questa chiave è un array di oggetti con una chiave obbligatoria ID azienda Bluetooth denominata companyIdentifier. Puoi anche fornire un prefisso dati che filtra i dati del produttore dai dispositivi Bluetooth che lo iniziano. Tieni presente che dovrai anche definire la chiave optionalServices per poter accedere a tutti i servizi non inclusi in un filtro di servizi. In caso contrario, verrà visualizzato un errore in un secondo momento quando provi ad accedere.

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

È possibile utilizzare una maschera anche con un prefisso dati per trovare corrispondenze con alcuni pattern nei dati del produttore. Per scoprire di più, consulta il messaggio esplicativo sui filtri dati Bluetooth.

Filtri di esclusione

L'opzione exclusionFilters in navigator.bluetooth.requestDevice() ti consente di escludere alcuni dispositivi dal selettore del browser. Può essere usata per escludere i dispositivi che corrispondono a un filtro più ampio, ma che non sono supportati.

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

Nessun filtro

Infine, anziché filters puoi utilizzare il tasto acceptAllDevices per mostrare tutti i dispositivi Bluetooth nelle vicinanze. Dovrai inoltre definire la chiave optionalServices per poter accedere ad alcuni servizi. In caso contrario, verrà visualizzato un errore in un secondo momento quando tenterai di accedere.

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

Connessione a un dispositivo Bluetooth

Cosa fai ora che hai un BluetoothDevice? Connettiamoci al server GATT remoto Bluetooth che contiene il servizio e le definizioni caratteristiche.

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

Lettura di una caratteristica Bluetooth

Qui ci connettiamo al server GATT del dispositivo Bluetooth remoto. Ora vogliamo ottenere un servizio GATT principale e leggere una caratteristica che appartiene a questo servizio. Proviamo, ad esempio, a leggere l'attuale livello di carica della batteria del dispositivo.

Nell'esempio che segue, battery_level è la caratteristica del livello della batteria standardizzata.

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

Se usi una caratteristica GATT Bluetooth personalizzata, puoi fornire a service.getCharacteristic l'UUID Bluetooth completo o un breve formato a 16 o 32 bit.

Tieni presente che puoi anche aggiungere un listener di eventi characteristicvaluechanged su una caratteristica per gestire la lettura del relativo valore. Consulta l'esempio di lettura del valore modificato della caratteristica per vedere come facoltativamente gestire anche le notifiche GATT successive.

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

Scrittura in una caratteristica Bluetooth

Scrivere a una caratteristica GATT Bluetooth è facile come leggerla. Questa volta, utilizziamo il punto di controllo della frequenza cardiaca per reimpostare il valore del campo Energia spesa su 0 su un dispositivo di monitoraggio della frequenza cardiaca.

Prometto che non c'è magia qui. Tutte le informazioni sono spiegate nella pagina relativa alla caratteristica del punto di controllo della frequenza cardiaca.

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

Ricezione di notifiche GATT

Ora vediamo come ricevere una notifica quando la caratteristica di Misurazione della frequenza cardiaca cambia sul dispositivo:

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'esempio di notifica mostra come interrompere le notifiche con stopNotifications() e rimuovere correttamente il listener di eventi characteristicvaluechanged aggiunto.

Disconnettersi da un dispositivo Bluetooth

Per offrire una migliore esperienza utente, ti consigliamo di ascoltare gli eventi di disconnessione e invitare l'utente a riconnettersi:

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

Puoi anche chiamare il numero device.gatt.disconnect() per disconnettere la tua app web dal dispositivo Bluetooth. Questa operazione attiverà gli ascoltatori di eventi gattserverdisconnected esistenti. Tieni presente che la comunicazione del dispositivo Bluetooth NON verrà interrotta se un'altra app comunica già con il dispositivo Bluetooth. Per saperne di più, consulta l'esempio di disconnessione dei dispositivi e l'campione di riconnessione automatica.

Lettura e scrittura sui descrittori Bluetooth

I descrittori GATT del Bluetooth sono attributi che descrivono un valore caratteristico. Puoi leggerli e scriverli in modo simile alle caratteristiche del GATT del Bluetooth.

Vediamo ad esempio come leggere la descrizione utente dell'intervallo di misurazione del termometro per l'integrità del dispositivo.

Nell'esempio seguente, health_thermometer è il servizio Termometro salute, measurement_interval la caratteristica dell'intervallo di misurazione e gatt.characteristic_user_description il descrittore della descrizione utente della caratteristica.

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

Ora che abbiamo letto la descrizione utente dell'intervallo di misurazione del termometro di salute del dispositivo, vediamo come aggiornarlo e scrivere un valore personalizzato.

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

Esempi, demo e codelab

Tutti i campioni di Bluetooth web riportati di seguito sono stati testati correttamente. Per sfruttare al meglio questi esempi, ti consiglio di installare l'[app Android del simulatore di periferiche BLE], che simula una periferica BLE con un servizio batteria, un servizio per la rilevazione del battito cardiaco o un servizio di termometro per la salute.

Principiante

  • Info dispositivo: recupera le informazioni di base del dispositivo da un dispositivo BLE.
  • Livello della batteria: recupera le informazioni sulla batteria da un dispositivo BLE che pubblicizza informazioni sulla batteria.
  • Resetta energia: reimposta l'energia spesa da un dispositivo BLE che pubblicizza il battito cardiaco.
  • Proprietà caratteristiche: mostrano tutte le proprietà di una caratteristica specifica di un dispositivo BLE.
  • Notifiche: avvia e interrompi notifiche caratteristiche da un dispositivo BLE.
  • Disconnessione dispositivo: disconnettiti e ricevi una notifica in caso di disconnessione di un dispositivo BLE dopo la connessione al dispositivo.
  • Acquisisci caratteristiche: ottieni tutte le caratteristiche di un servizio pubblicizzato da un dispositivo BLE.
  • Ottieni descrittori: ottieni i descrittori di tutte le caratteristiche di un servizio pubblicizzato da un dispositivo BLE.
  • Filtro dati del produttore: recupera le informazioni di base del dispositivo da un dispositivo BLE che corrispondono ai dati del produttore.
  • Filtri di esclusione: recupera le informazioni di base del dispositivo da un dispositivo BLE con filtri di esclusione di base.

Combinare più operazioni

Dai un'occhiata anche alle nostre demo Bluetooth web selezionate e ai codelab web Bluetooth ufficiali.

Librerie

  • web-bluetooth-utils è un modulo npm che aggiunge alcune funzioni di convenienza all'API.
  • Uno shim API Web Bluetooth è disponibile in noble, il modulo centrale BLE Node.js più popolare. Ciò ti consente di creare pacchetti Web/browserify nobili senza bisogno di un server WebSocket o di altri plug-in.
  • angular-web-bluetooth è un modulo per Angular che astrae tutto il boilerplate necessario per configurare l'API Web Bluetooth.

Strumenti

  • Inizia a utilizzare Web Bluetooth è una semplice app web che genera tutto il codice boilerplate JavaScript per iniziare a interagire con un dispositivo Bluetooth. Inserisci il nome di un dispositivo, un servizio o una caratteristica, definisci le proprietà e il gioco è fatto.
  • Se sei già uno sviluppatore Bluetooth, il plug-in Studio per sviluppatori Bluetooth web genererà anche il codice JavaScript web Bluetooth per il tuo dispositivo Bluetooth.

Suggerimenti

In Chrome all'indirizzo about://bluetooth-internals è disponibile una pagina Componenti interni Bluetooth che ti consente di esaminare tutti i dettagli dei dispositivi Bluetooth nelle vicinanze: stato, servizi, caratteristiche e descrittori.

Screenshot della pagina interna per eseguire il debug del Bluetooth in Chrome
Pagina interna in Chrome per il debug dei dispositivi Bluetooth.

Ti consiglio anche di consultare la pagina ufficiale Come segnalare bug relativi al Bluetooth sul web, dato che a volte il debug del Bluetooth può essere difficile.

Passaggi successivi

Controlla prima lo stato di implementazione del browser e della piattaforma per sapere quali parti dell'API Web Bluetooth sono attualmente implementate.

Sebbene sia ancora incompleta, ecco un'anteprima di cosa aspettarsi nel prossimo futuro:

  • L'analisi di annunci BLE nelle vicinanze verrà eseguita con navigator.bluetooth.requestLEScan().
  • Un nuovo evento serviceadded monitorerà i servizi GATT Bluetooth appena rilevati, mentre l'evento serviceremoved monitorerà quelli rimossi. Un nuovo evento servicechanged viene attivato quando una caratteristica e/o un descrittore viene aggiunta o rimossa da un servizio Bluetooth GATT.

Mostra il supporto dell'API

Intendi utilizzare l'API Web Bluetooth? Il tuo supporto pubblico aiuta il team di Chrome a dare la priorità alle funzionalità e mostra ad altri fornitori di browser quanto è fondamentale supportarle.

Invia un tweet a @ChromiumDev usando l'hashtag #WebBluetooth e facci sapere dove e come lo stai usando.

Risorse

Ringraziamenti

Grazie a Kayce Basques per aver letto questo articolo. Immagine hero di SparkFun Electronics da Boulder, USA.